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



namespace Rokojori
{
  public enum ClipMapCellConstraints
  {
    Rounded_Integer_Divisions,
    Only_MinLevel_PowersOfTwos
  }

  [Tool]
  [GlobalClass]
  public partial class ClipMapPlaneMeshType:__PlaneMeshType__
  { 
    [Export]
    public float maxCellSize = 100;

    [Export]
    public float minCellSize = 10;

    [Export]
    public float centerMeshRadius = 100;

    
    [Export]
    public ClipMapCellConstraints cellConstrains = ClipMapCellConstraints.Rounded_Integer_Divisions;


    [Export]
    public int meshDivisions = 8;

    

    [Export]
    public Curve cellSizeOverDistance = MathX.Curve( 0, 1 );

    

    float xUnits;
    float zUnits;
    float sizeX;
    float sizeZ;

    Vector2 size;
    Vector2 offset;

    float maxDistance;

    List<MeshGeometry> geometries;


    public override List<MeshGeometry> GetMeshGeometries( float sizeX, float sizeZ )
    {
      geometries = new List<MeshGeometry>();

      geometries.Add( new MeshGeometry( "Center" ) );

      for ( int i = 0; i < meshDivisions; i++ )
      {
        float angle = ( (float) i  / meshDivisions ) * 360f;
        geometries.Add( new MeshGeometry( "Angle " + angle ) );
      }

      this.sizeX = sizeX;
      this.sizeZ = sizeZ;

      xUnits = Mathf.Ceil( ( sizeX / 2 ) / maxCellSize ) * 2;
      zUnits = Mathf.Ceil( ( sizeZ / 2 ) / maxCellSize ) * 2;


      size = new Vector2( xUnits * maxCellSize, zUnits * maxCellSize );
      offset = size / -2;
      maxDistance = Mathf.Min( sizeX, sizeZ ) / 2f;

      this.LogInfo( xUnits, zUnits );
      
      for ( int x = 0; x < xUnits; x++ )
      {
        for ( int z = 0; z < zUnits; z++ )
        {
          Add( x, z );      
        } 
      }

      var g = geometries;
      geometries = null;
      return g;
    }

    static float edgeTreshold = 0.000001f;

    bool InRange( float value, float target )
    {
      var e = edgeTreshold;
      return Range.Contains( -e + target, e + target, value );
    }

    bool IsEdge( float value )
    {
      return InRange( value, 0 ) || InRange( value, 1 );
    }

    Vector2 CellOffset( int x, int z )
    {
      var c = offset + new Vector2( x * maxCellSize, z * maxCellSize );    

      return c;
    }
    

    float CellSize( int x, int z )
    {
      var mOffset = 0;
      var c = CellOffset( x + mOffset, z + mOffset);          
      var cL = c; // + new Vector2( 1, 1 ) * maxCellSize / 2;

      var s = cL.Length() / maxDistance;

      if ( cellSizeOverDistance != null )
      {
        s = cellSizeOverDistance.Sample( s );
      }

      s = Mathf.Lerp( minCellSize, maxCellSize, s );

      return s;
    }

    int CellDivisions( int x, int z )
    {
      var cellSize = CellSize( x, z );
      var numDivisions = maxCellSize / cellSize;
      var divisions = Mathf.CeilToInt( numDivisions );

      // this.LogInfo( "Computed divisions xz:", x, z, " >> ", numDivisions._FFF(), ">", divisions );

      if ( ClipMapCellConstraints.Rounded_Integer_Divisions == cellConstrains )
      {
        return divisions;
      }

      if ( divisions == 1 || divisions == 2 )
      {
        return divisions;
      }

      var v = MathX.NextPowerOfTwoExponent( divisions );

      return v * v;
    }

    bool IsEdge( Vector2 value )
    {
      return IsEdge( value.X ) || IsEdge( value.Y );
    }


    static float SnapToClosest( float value, int lowDivs )
    {
      float stepSize = 1.0f / lowDivs;

      float snappedValue = Mathf.Round( value / stepSize ) * stepSize;

      return snappedValue;
    }
   
    MeshGeometry GetMeshGeometry( Vector2 center )
    {
      if ( center.Length() < centerMeshRadius )
      {
        return geometries[ 0 ];
      }

      var angle = MathX.RadiansToDegrees * center.Angle();
      var index = Mathf.RoundToInt( angle / 360f * meshDivisions -0.5f );
      index = MathX.Repeat( index, meshDivisions ); 
      return geometries[ index + 1 ];
    }

    void Add( int x, int z )
    {       
      var center = CellOffset( x, z );
      var cellSize = CellSize( x, z );

      var mg = GetMeshGeometry( center );

      var numDivisions = CellDivisions( x, z );

      var leftDivisions  = x == 0 ? -1 : CellDivisions( x - 1, z );
      var rightDivisions = x == xUnits - 1 ? -1 : CellDivisions( x + 1, z );

      var topDivisions    = z == 0 ? -1 : CellDivisions( x, z - 1 );
      var bottomDivisions = z == zUnits - 1 ? -1 : CellDivisions( x, z + 1 );

      var snapToLeft  = leftDivisions != -1 && leftDivisions < numDivisions;
      var snapToRight = rightDivisions != -1 && rightDivisions < numDivisions;

      var snapToTop = topDivisions != -1 && topDivisions < numDivisions;
      var snapToBottom = bottomDivisions != -1 && bottomDivisions < numDivisions;
    
      // RJLog.Log( snapToLeft, snapToRight, snapToBottom, snapToTop );


      var sectionMG = MeshGeometry.CreateFromUVFunction( 
        ( uv ) =>
        {
          var pose = new Pose();
          pose.position = Math3D.XYasXZ( uv ) * maxCellSize + Math3D.XYasXZ( center );

          var isEdge = IsEdge( uv );

          if ( ! isEdge )
          {
            return pose;
          }

          var before = pose.position;

          var snappedUV = uv;

          if ( ! ( InRange( uv.Y, 0 ) || InRange( uv.Y, 1 ) ) ) 
          {
            if ( snapToLeft && InRange( uv.X, 0 ) )
            {
              snappedUV.Y = SnapToClosest( snappedUV.Y, leftDivisions ); 
            }

            if ( snapToRight && InRange( uv.X, 1 ) )
            {
              snappedUV.Y = SnapToClosest( snappedUV.Y, rightDivisions );
            }

          }

          if ( ! ( InRange( uv.X, 0 ) ||  InRange( uv.X, 1 ) ) ) 
          {
            if ( snapToTop && InRange( uv.Y, 0 ) )
            {
              snappedUV.X = SnapToClosest( snappedUV.X, topDivisions ); 
            }

            if ( snapToBottom && InRange( uv.Y, 1 ) )
            {
              snappedUV.X = SnapToClosest( snappedUV.X, bottomDivisions ); 
            }

          }

          pose.position = Math3D.XYasXZ( snappedUV ) * maxCellSize + Math3D.XYasXZ( center );

          // this.LogInfo( "Snapped", before, ">>", pose.position );

          return pose;

        },
        numDivisions, numDivisions
      );

      
      sectionMG.ApplyTranslation( new Vector3( -maxCellSize/2, 0, -maxCellSize/2 ));
      mg.Add( sectionMG );
    }
  }
}