using System.Collections;
using System.Collections.Generic;
using Godot;

namespace Rokojori
{
  public class Line2
  {   
    public Vector2 start = Vector2.Zero;
    public Vector2 end = Vector2.Zero;

    public Line2( Vector2 start, Vector2 end ) 
    {
      Set( start, end );
    }    

    public void Set( Vector2 start, Vector2 end ) 
    {
      this.start = start;
      this.end = end;
    }    

    public Line2(){}

    public float A{ get { return end.Y - start.Y; } }
    public float B{ get { return start.X - end.X; } }
    public float C{ get { return A * start.X + B * start.Y; } }

    public Vector2 direction { get { return end - start; } }
    public Vector2 reverseDirection { get { return start - end; } }

    public float angle
    {
      get {
        
        var lineDirection = direction;

        return Mathf.Atan2( lineDirection.Y, lineDirection.X );
      }
    }

    public float reverseAngle
    {
      get {
        
        var lineDirection = reverseDirection;

        return Mathf.Atan2( lineDirection.Y, lineDirection.X );
      }
    }

    public Vector2 center => ( end - start ) / 2f;


    public Vector2? InfiniteIntersectionOf( Line2 line2 )
    {
      var A1 = A;
      var B1 = B;
      var A2 = line2.A;
      var B2 = line2.B;

      var determinant = A1 * B2 - A2 * B1;

      if ( Mathf.Abs( determinant ) < Mathf.Epsilon )
      { 
        return null;
      }

      var inverseDeterminant = 1f / determinant;

      var C1 = A1 * start.X + B1 * start.Y;
      var C2 = A2 * line2.start.X + B2 * line2.start.Y;

      var x = ( B2 * C1 - B1 * C2 ) * inverseDeterminant;
      var y = ( A1 * C2 - A2 * C1 ) * inverseDeterminant;

      return new Vector2( x, y );
    }

    public Vector2? IntersectionOf( Line2 other )
    {
      var possibleIntersection = InfiniteIntersectionOf( other );
      
      if ( possibleIntersection == null )
      {
        return null;
      }

      var point = (Vector2) possibleIntersection;

      if ( ! IsValueInRange( point.X, start.X, end.X ) ) 
      {
        return null;
      }
      
      if ( ! IsValueInRange( point.Y, start.Y, end.Y ) )
      {
        return null;
      }

      if ( ! IsValueInRange( point.X, other.start.X, other.end.X ) ) 
      {
        return null;
      }
      
      if ( ! IsValueInRange( point.Y, other.start.Y, other.end.Y ) )
      {
        return null;
      }

      return point;
    }

    public bool IntersectsWith( Line2 other )
    {
      return IntersectionOf( other ) != null;
    }
   
    bool IsValueInRange( float value, float start, float end )
    {
      var min = Mathf.Min( start, end );
      var max = Mathf.Max( start, end );

      return min <= value && value <= max;    
    } 

    public Vector2 GetPointAtParameter( float t )
    {
      return start + direction * t;
    }

    public Vector2 ClosestPointToPoint( Vector2 point )
    {
      var parameter = MathX.Clamp01( ClostestParameterToPoint( point ) );
      return GetPointAtParameter( parameter );
    }

    public float ClostestParameterToPoint( Vector2 point )
    {
      var startP = point - start;
      var startEnd = end - start;
      var startEnd2 = Math2D.Dot( startEnd, startEnd );
      var startEnd_startP = Math2D.Dot( startEnd, startP );

      if ( startEnd2 == 0 )
      {
        return 0;
      }

      var t = startEnd_startP / startEnd2;

      return t;
        
    }

    public float DistanceToPoint( Vector2 point )
    {
      return ( point - ClosestPointToPoint( point ) ).Length();
    }

    public Vector2 GetAt( float t )
    {
      return direction * t + start;
    }

    public void ScaleFromCenter( float scale )
    {
      var center = GetAt( 0.5f );
      var dir = direction * 0.5f;

      start = center - dir * scale;
      end   = center + dir * scale;      
    }

    public void Translate( Vector2 translation )
    {
      start += translation;
      end += translation;
    }
  }
}