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

namespace Rokojori
{  
  
  public class SVGPathExtractor
  {
    Vector2 currentPoint = new Vector2();
    Vector2 lastControlPoint = new Vector2();
    Vector2 startPoint = new Vector2();
    SVGPathInstruction instruction = new SVGPathInstruction();
    public readonly EventSlot<SVGPathInstruction> onInstruction = new EventSlot<SVGPathInstruction>();

    void DispatchInstruction()
    {
      onInstruction.DispatchEvent( instruction );
    }

    public void Process( List<SVGPathCommand> commands )
    {
      commands.ForEach( c => ProcessNext( c ) );
    }

    public void ProcessNext( SVGPathCommand command )
    {
      switch ( command.type)
      {
        case "m": case "M": 
        { 
          ProcessMoveTo( command ); 
        } 
        break;

        case "l": case "L": 
        { 
          ProcessLineTo( command ); 
        } 
        break;

        case "v": case "V": 
        { 
          ProcessVerticalTo( command ); 
        } 
        break;

        case "h": case "H": 
        { 
          ProcessHorizontalTo( command ); 
        } 
        break;

        case "c": case "C": 
        { 
          ProcessCubicTo( command ); 
        } 
        break;

        case "q": case "Q": 
        { 
          ProcessQuadraticTo( command ); 
        } 
        break;

        case "a": case "A": 
        { 
          ProcessArcTo( command ); 
        } 
        break;

        case "z": case "Z":
        {
          ProcessClose( command );
        }
        break;

        
      }
    }   

    void ProcessClose( SVGPathCommand command ) 
    {
      instruction.sourceCommand = command;
      instruction.sourceCommandIndex = 0;

      instruction.type = SVGPathInstructionType.Close;
      instruction.startPoint = startPoint;
      instruction.endPoint   = startPoint;

      currentPoint = startPoint;

      DispatchInstruction();
    }

    void ProcessMoveTo( SVGPathCommand command )
    {
      var relative = command.type == "m";
      var parameters = command.paramaters;     

      var sourceCommandIndex = 0;
      instruction.sourceCommand = command;

      for ( int i = 0; i < parameters.Count; i+= 2 )
      {
        instruction.sourceCommandIndex = sourceCommandIndex ++;

        if ( i == 0 )
        {
          ProcessMoveToStartPoint( relative, SVGPathInstructionType.MoveTo, parameters[ i ], parameters[ i + 1 ] );
        }
        else
        {
          ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, parameters[ i ], parameters[ i + 1 ] );
        }
        
      }
    }

    void ProcessLineTo( SVGPathCommand command )
    {
      var relative = command.type == "l";
      var parameters = command.paramaters;

      var sourceCommandIndex = 0;
      instruction.sourceCommand = command;

      for ( int i = 0; i < parameters.Count; i+= 2 )
      {
        instruction.sourceCommandIndex = sourceCommandIndex ++;

        ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, parameters[ i ], parameters[ i + 1 ] );
        
      }
    }

    void ProcessVerticalTo( SVGPathCommand command )
    {
      var relative = command.type == "v";
      var parameters = command.paramaters;

      var sourceCommandIndex = 0;
      instruction.sourceCommand = command;

      for ( int i = 0; i < parameters.Count; i++ )
      {
        instruction.sourceCommandIndex = sourceCommandIndex ++;

        var x = relative ? 0 : currentPoint.X;
        var y = parameters[ i ];
        ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, x, y );
      }
    }

    void ProcessHorizontalTo( SVGPathCommand command )
    {
      var relative = command.type == "h";
      var parameters = command.paramaters;

      var sourceCommandIndex = 0;
      instruction.sourceCommand = command;

      for ( int i = 0; i < parameters.Count; i++ )
      {
        instruction.sourceCommandIndex = sourceCommandIndex ++;

        var x = parameters[ i ];
        var y = relative ? 0 : currentPoint.Y;
        ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, x, y );
      }
    } 

    void ProcessArcTo( SVGPathCommand command )
    {
      var relative = command.type == "a";
      var parameters = command.paramaters;

      var sourceCommandIndex = 0;
      instruction.sourceCommand = command;


      for ( int i = 0; i < parameters.Count; i+= 7 )
      {
        instruction.sourceCommandIndex = sourceCommandIndex ++;

        ProcessArc( relative, 
          parameters[ i ], parameters[ i + 1 ], 
          parameters[ i + 2 ], parameters[ i + 3 ], 
          parameters[ i + 4 ], parameters[ i + 5 ],
          parameters[ i + 6 ]
        );
      }
    }

    void ProcessArc( bool relative, float rx, float ry, float angle, float largeArc, float sweepArc, float x, float y )
    {
      instruction.type = SVGPathInstructionType.ArcTo;

      instruction.startPoint = currentPoint;

      instruction.radius.X = rx;
      instruction.radius.Y = ry;
      instruction.angle  = angle;   

      instruction.largeArcFlag = largeArc > 0;
      instruction.sweepFlag    = sweepArc > 0;
      
      

      instruction.endPoint.X = x;
      instruction.endPoint.Y = y;


      if ( relative )
      {
        instruction.endPoint += currentPoint;
      }

      currentPoint = instruction.endPoint;
      DispatchInstruction();
    }

    void ProcessCubicTo( SVGPathCommand command )
    {
      var relative = command.type == "c";
      var parameters = command.paramaters;

      var sourceCommandIndex = 0;
      instruction.sourceCommand = command;

      for ( int i = 0; i < parameters.Count; i+= 6 )
      {
        instruction.sourceCommandIndex = sourceCommandIndex ++;

        ProcessCubic( relative, 
          parameters[ i ], parameters[ i + 1 ], 
          parameters[ i + 2 ], parameters[ i + 3 ], 
          parameters[ i + 4 ], parameters[ i + 5 ]
        );
      }
    }

    void ProcessSCubicTo( SVGPathCommand command )
    {
      var relative = command.type == "s";
      var parameters = command.paramaters;

      var sourceCommandIndex = 0;
      instruction.sourceCommand = command;

      for ( int i = 0; i < parameters.Count; i+= 4 )
      {
        instruction.sourceCommandIndex = sourceCommandIndex ++;

        var cpDiff = currentPoint - lastControlPoint;
        var cpNext = relative ? cpDiff : ( currentPoint + cpDiff );

        ProcessCubic( relative, 
          cpNext.X, cpNext.Y,
          parameters[ i ], parameters[ i + 1 ], 
          parameters[ i + 2 ], parameters[ i + 3 ]
        );
      }
    }

    void ProcessCubic( bool relative, float cx1, float cy1,  float cx2, float cy2,  float x, float y  )
    {
      instruction.type = SVGPathInstructionType.CubicBezierTo;

      instruction.startPoint = currentPoint;

      instruction.controlPoint1.X = cx1;
      instruction.controlPoint1.Y = cy1;

      instruction.controlPoint2.X = cx2;
      instruction.controlPoint2.Y = cy2;

      instruction.endPoint.X = x;
      instruction.endPoint.Y = y;

      lastControlPoint = instruction.controlPoint2;

      if ( relative )
      {
        instruction.controlPoint1 += currentPoint;
        instruction.controlPoint2 += currentPoint;
        instruction.endPoint      += currentPoint;
      }

      currentPoint = instruction.endPoint;
      DispatchInstruction();
    }


    void ProcessQuadraticTo( SVGPathCommand command )
    {
      var relative = command.type == "q";
      var parameters = command.paramaters;

      var sourceCommandIndex = 0;
      instruction.sourceCommand = command;


      for ( int i = 0; i < parameters.Count; i+= 4 )
      {
        instruction.sourceCommandIndex = sourceCommandIndex ++;
        ProcessQuadratic( relative, 
          parameters[ i ], parameters[ i + 1 ], 
          parameters[ i + 2 ], parameters[ i + 3 ]
        );
      }
    }

    void ProcessTQuadraticTo( SVGPathCommand command )
    {
      var relative = command.type == "t";
      var parameters = command.paramaters;

      var sourceCommandIndex = 0;
      instruction.sourceCommand = command;

      for ( int i = 0; i < parameters.Count; i+= 2 )
      {
        instruction.sourceCommandIndex = sourceCommandIndex ++;
        var cpDiff = currentPoint - lastControlPoint;
        var cpNext = relative ? cpDiff : ( currentPoint + cpDiff );

        ProcessQuadratic( relative, 
          cpNext.X, cpNext.Y,
          parameters[ i ], parameters[ i + 1 ]
        );
      }
    }

    void ProcessQuadratic( bool relative, float cx1, float cy1, float x, float y  )
    {
      instruction.type = SVGPathInstructionType.QuadraticBezierTo;

      instruction.startPoint = currentPoint;

      instruction.controlPoint1.X = cx1;
      instruction.controlPoint1.Y = cy1;

      instruction.endPoint.X = x;
      instruction.endPoint.Y = y;


      lastControlPoint = instruction.controlPoint1;

      if ( relative )
      {
        instruction.controlPoint1 += currentPoint;
        instruction.endPoint      += currentPoint;
      }

      currentPoint = instruction.endPoint;
      DispatchInstruction();
    }


    void ProcessMoveToStartPoint( bool relative, SVGPathInstructionType type, float x, float y )
    {
      instruction.type = type;
      
      instruction.endPoint.X = x;
      instruction.endPoint.Y = y;

      if ( relative )
      {
        instruction.endPoint += currentPoint;
      }

      currentPoint = instruction.endPoint;
      instruction.startPoint = currentPoint;

      startPoint = currentPoint;

      DispatchInstruction();
      
    }

    void ProcessToEndpoint( bool relative, SVGPathInstructionType type, float x, float y )
    {
      instruction.type = type;

      instruction.startPoint = currentPoint;
      
      instruction.endPoint.X = x;
      instruction.endPoint.Y = y;

      if ( relative )
      {
        instruction.endPoint += currentPoint;
      }

      currentPoint = instruction.endPoint;
      DispatchInstruction();
      
    }

    

   

    
  }
}