//
//  Path.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace NosSmooth.Extensions.Pathfinding;
/// 
/// Represents a found walkable path.
/// 
public class Path
{
    /// 
    /// Initializes a new instance of the  class.
    /// 
    /// The map id.
    /// The current x.
    /// The current y.
    /// The target x.
    /// The target y.
    /// The parts that represent the path from the current to the target.
    public Path
    (
        int map,
        short x,
        short y,
        short targetX,
        short targetY,
        IReadOnlyList<(short X, short Y)> parts
    )
    {
        MapId = map;
        CurrentX = x;
        CurrentY = y;
        TargetX = targetX;
        TargetY = targetY;
        Parts = parts;
        CurrentPartIndex = 0;
    }
    /// 
    /// Gets the map id this path is for.
    /// 
    public int MapId { get; }
    /// 
    /// Gets whether the path has reached an end.
    /// 
    public bool ReachedEnd => CurrentPartIndex >= Parts.Count - 1;
    /// 
    /// Gets the current walk path index.
    /// 
    public int CurrentPartIndex { get; private set; }
    /// 
    /// Gets the list of the parts that have to be taken.
    /// 
    public IReadOnlyList<(short X, short Y)> Parts { get; }
    /// 
    /// Gets the target x coordinate.
    /// 
    public short TargetX { get; }
    /// 
    /// Gets the target y coordinate.
    /// 
    public short TargetY { get; }
    /// 
    /// Gets the current x coordinate.
    /// 
    public short CurrentX { get; private set; }
    /// 
    /// gets the current y coordinate.
    /// 
    public short CurrentY { get; private set; }
    /// 
    /// Take a path only in the same direction.
    /// 
    /// A position to walk to.
    public (short X, short Y) TakeForwardPath()
    {
        if (ReachedEnd)
        {
            return (TargetX, TargetY);
        }
        if (CurrentPartIndex + 2 >= Parts.Count)
        {
            CurrentPartIndex++;
            return (TargetX, TargetY);
        }
        var zeroTile = (CurrentX, CurrentY);
        var firstTile = Parts[++CurrentPartIndex];
        var currentTile = firstTile;
        var nextTile = Parts[CurrentPartIndex + 1];
        while (!ReachedEnd && IsInLine(zeroTile, firstTile, nextTile))
        {
            currentTile = nextTile;
            CurrentPartIndex++;
            if (!ReachedEnd)
            {
                nextTile = Parts[CurrentPartIndex + 1];
            }
        }
        return currentTile;
    }
    private bool IsInLine((short X, short Y) start, (short X, short Y) first, (short X, short Y) current)
    {
        var xFirstDiff = first.X - start.X;
        var yFirstDiff = first.Y - start.Y;
        var xCurrentDiff = current.X - start.X;
        var yCurrentDiff = current.Y - start.Y;
        if (xFirstDiff == 0 && yFirstDiff == 0)
        {
            throw new ArgumentException("The path went back to the start.");
        }
        if (xCurrentDiff == 0 && yCurrentDiff == 0)
        {
            throw new ArgumentException("The path went back to the start.");
        }
        if (xFirstDiff != 0)
        {
            var xRatio = xCurrentDiff / (float)xFirstDiff;
            return Math.Abs((yFirstDiff * xRatio) - yCurrentDiff) < float.Epsilon;
        }
        var yRatio = yCurrentDiff / (float)yFirstDiff;
        return Math.Abs((xFirstDiff * yRatio) - xCurrentDiff) < float.Epsilon;
    }
    /// 
    /// Take the given number of tiles and return the position we ended up at.
    /// 
    /// 
    /// If the count is greater than what is remaining, the end will be taken.
    /// 
    /// The count of parts to take.
    /// A position to walk to.
    public (short X, short Y) TakePath(uint partsCount)
    {
        if (ReachedEnd)
        {
            return (TargetX, TargetY);
        }
        if (CurrentPartIndex + partsCount >= Parts.Count - 1)
        {
            CurrentPartIndex = Parts.Count - 1;
        }
        else
        {
            CurrentPartIndex += (int)partsCount;
        }
        return Parts[CurrentPartIndex];
    }
}