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

namespace Rokojori
{  
  [Tool]
  [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Spline.svg") ]
  public partial class Cuboid : Node3D
  {
    [Export]
    public float size = 1;

    [Export]
    public float widthExtension = 0;

    [Export]
    public float heightExtension = 0;

    [Export]
    public float depthExtension = 0;

    [Export]
    public float borderSize = 0.1f;

    [Export]
    public MeshInstance3D output;

    
    [ExportToolButton( "Update")]
    public Callable UpdateButton => Callable.From( () => Create() );


    public float X()
    {
      return size + widthExtension;
    }

    public float Y()
    {
      return size + heightExtension;
    }

    public float Z()
    {
      return size + depthExtension;
    }

    MeshGeometry mg = null;

    public void Create()
    {
      var maxBorderSize = ( MathX.Min( widthExtension, heightExtension, depthExtension )  + size ) / 2f;
      borderSize = Mathf.Clamp( borderSize, 0, maxBorderSize );
      mg = new MeshGeometry();

      for ( int i = 0; i < 24; i++ )
      {
        var p = GetPointAt( i );
        mg.vertices.Add( p );

        if ( i < 4 )
        {
          mg.normals.Add( Vector3.Up );
        }
        else if ( i < 12 )
        {
          var cornerIndex = i - 4;
          mg.normals.Add( GetEdgeNormal( cornerIndex ) );
        }
        else if ( i < 20 )
        {
          var cornerIndex = i - 12;
          mg.normals.Add( GetEdgeNormal( cornerIndex ) );
        }
        else
        {
          mg.normals.Add( Vector3.Down );
        }

        
        mg.uvs.Add( Vector2.Zero );
      }

      mg.AddQuad( 0, 1, 2, 3 );

      // back 
      AddSide( 2, 1 );
      // right
      AddSide( 7, 3 );
      // left
      AddSide( 0, 4 );
      //front
      AddSide( 5, 6 );


      // side b,r
      AddSide( 3, 2 );
      // side f, r
      AddSide( 6, 7 );
      // side f, l
      AddSide( 4, 5 );
      // side b, l
      AddSide( 1, 0 );

      
      // left, back
      AddCorner( 0, 1, 0 );

      // back, right
      AddCorner( 2, 3, 1 );

      // left, front
      AddCorner( 5, 4, 2 );

      // front, right
      AddCorner( 7, 6, 3 );


      // back
      AddFaceSide( 0, 1, 2, 1 );

      // right
      AddFaceSide( 1, 3, 7, 3 );

      // front
      AddFaceSide( 3, 2, 5, 6 );

      // left
      AddFaceSide( 2, 0, 0, 4 );



      mg.AddQuad( 20, 21, 22, 23, true );

      if ( output == null )
      {
        output = this.CreateChild<MeshInstance3D>();
      }

      output.Mesh = mg.GenerateMesh();

    }

    void AddSide( int a, int b )
    {
      mg.AddQuad( te( a ), te( b ), be( a ), be( b ) );
    }

    void AddFaceSide( int fA, int fB, int eA, int eB )
    {
      mg.AddQuad( tf( fB ), tf( fA), te( eA ), te( eB ) );      
      mg.AddQuad( bf( fB ), bf( fA), be( eA ), be( eB ), true );
    }

    void AddCorner( int eA, int eB, int f )
    {
      mg.AddTriangle( te( eA ), te( eB ), tf( f ), true );
      mg.AddTriangle( be( eA ), be( eB ), bf( f ) );
    }

    int tf( int index ) 
    {
      return index;
    } 

    int te( int index )
    {
      return index + 4;
    }

    int be( int index )
    {
      return index + 12;
    }

    int bf( int index )
    {
      return index + 20;
    }


    public Vector3 GetEdgeNormal( int index )
    {
      switch ( index )
      {
        case 1: case 2: return Vector3.Back;
        case 3: case 7: return Vector3.Right;
        case 0: case 4: return Vector3.Left;
        case 5: case 6: return Vector3.Forward;
      }

      return Vector3.Zero;
    }

    public Vector3 GetOffsetFor( int left, int front )
    {
      var x = borderSize * Mathf.Sign( left );
      var z = borderSize * Mathf.Sign( front );

      return new Vector3( x, 0, z );
    }

    public Vector3 GetBoundingCornerAt( int index, bool top )
    { 
      //      -Z
      //    0    1 
      //    *----*
      //-X  |    | +X  
      //    *----*
      //    2    3  
      //      +Z    

      var y = Y() / ( top ? 2 : -2 );

      if ( 0 == index )
      {
        return new Vector3( -X()/2, y, +Z()/2 );
      }
      else if ( 1 == index )
      {
        return new Vector3( +X()/2, y, +Z()/2 );
      }
      else if ( 2 == index )
      {
        return new Vector3( -X()/2, y, -Z()/2 );
      }
      else if ( 3 == index )
      {
        return new Vector3( +X()/2, y, -Z()/2 );
      }

      return Vector3.Zero;

    }

    public Vector3 GetPointAt( int index )
    {
      // 00-03 4 Top Face 
      // 04-11 8 Top Edges
      // 12-19 8 Bottom Edges
      // 20-23 4 Bottom Face

      if ( index < 4 )
      {
        return GetFacePoint( index, true );
      }
      else if ( index < 12 )
      {
        return GetEdgePoint( index - 4, true );
      }
      else if ( index < 20 )
      {
        return GetEdgePoint( index - 12, false );
      }
      else if ( index < 24 )
      {
        return GetFacePoint( index - 20, false );
      }

      return Vector3.Zero;
    }

    Vector3 GetFacePoint( int index, bool top )
    {
      var cornerPoint = GetBoundingCornerAt( index, top );

      var offset = Vector3.Zero;

      if ( 0 == index )
      {
        offset = GetOffsetFor( 1, -1 );
      }
      else if ( 1 == index )
      {
        offset = GetOffsetFor( -1, -1 );
      }
      else if ( 2 == index )
      {
        offset = GetOffsetFor( 1, 1 );
      }
      else if ( 3 == index )
      {
        offset = GetOffsetFor( -1, 1 );
      }

      return cornerPoint + offset;
    }

    Vector3 GetEdgePoint( int index, bool top )
    {
      var cornerIndex = index / 2;
      var cornerPoint = GetBoundingCornerAt( cornerIndex, top );

      if ( top )
      {
        cornerPoint.Y -= borderSize;
      }
      else
      {
        cornerPoint.Y += borderSize;
      }

      var offset = Vector3.Zero;

      if ( 0 == cornerIndex )
      {
        offset = index == 0 ? GetOffsetFor( 0, -1 ) : GetOffsetFor( 1, 0 );
      }
      else if ( 1 == cornerIndex )
      {
        offset = index == 2 ? GetOffsetFor( -1, 0 ) : GetOffsetFor( 0, -1 );
      }
      else if ( 2 == cornerIndex )
      {
        offset = index == 4 ? GetOffsetFor( 0, 1 ) : GetOffsetFor( 1, 0 );
      }
      else if ( 3 == cornerIndex )
      {
        offset = index == 6 ? GetOffsetFor( -1, 0 ) : GetOffsetFor( 0, 1 );
      }

      return cornerPoint + offset;
    }

  }

}