using Godot;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace Rokojori
{  
  public class HeightMapData
  {
    int width;
    int height;
    float[] data = new float[ 0 ];
    
    public static HeightMapData Create( int w, int h, float[] data = null)
    {
      var hmd = new HeightMapData();
      hmd.width = w;
      hmd.height = h;

      hmd.data = data == null ? new float[ w * h ] : data;

      return hmd;
    }

    public float Get( int x, int y )
    {
      x = Mathf.Clamp( x, 0, width - 1 );
      y = Mathf.Clamp( y, 0, height - 1 ); 
      return data[ x + y * width ];
    }

    public float GetLerped( float x, float y )
    {
      var x0 = Mathf.FloorToInt( x );
      var x1 = x0 + 1;

      var y0 = Mathf.FloorToInt( y );
      var y1 = y0 + 1;

      var xL = x - x0;
      var yL = y - y0;

      //   xy xy
      //   00 10
      //   10 11

      var upValue   = Mathf.Lerp( Get( x0, y0 ), Get( x1, y0 ), xL );
      var downValue = Mathf.Lerp( Get( x0, y1 ), Get( x1, y1 ), xL );

      return Mathf.Lerp( upValue, downValue, yL );
    }

    public void Set( int x, int y, float value )
    {
      if ( x < 0 || y < 0 || x >= width || y >= height )
      {
        return;
      } 
      
      data[ x + y * width ] = value;
    }

    public Vector3 GetNormal( int x, int y, float strength = 8f )
    {
      var tl = Get( x-1, y-1 ); 
      var  l = Get( x-1, y );   
      var bl = Get( x-1, y+1) ; 
      
      var  t = Get( x, y-1 );   
      var  b = Get( x, y+1 );   

      var tr = Get( x+1, y-1 ); 
      var  r = Get( x+1, y );   
      var br = Get( x+1, y+1 ); 
      

      var nx = ( tr + 2.0f * r + br ) - ( tl + 2.0f * l + bl );
      var nz = ( bl + 2.0f * b + br ) - ( tl + 2.0f * t + tr );
      var ny = 1.0f / strength;

      return -new Vector3( nx, ny, nz ).Normalized();
    }

    public Vector3 GetNormal( float x, float y, float strength = 1f )
    {
      return GetNormal( (int)x, (int)y, strength );
    }


    public MeshGeometry GenerateMeshGeometry( Vector3 scale )
    {
      var func = ( Vector2 uv )=>
      {
        var p = new Pose();

        var x = uv.X * width;
        var z = uv.Y * height;
        var y = GetLerped( x, z );

        p.position = new Vector3( x * scale.X, y * scale.Y, z * scale.Z );
        p.up = GetNormal( x, z );
        return p;
      
      };

      return MeshGeometry.CreateFromUVFunction( func, width - 1, height - 1 );
    }

    public HeightMapData CreateLowerResolution( float averageVsMaxFilter = 0.5f )
    {
      var lw = width/2;
      var lh = height/2;

      var lower = Create( lw, lh );

      for ( int i = 0; i < lw; i++ )
      {
        for ( int j = 0; j < lh; j++ )
        {
          var x0 = i * 2;
          var x1 = x0 + 1;
          var y0 = j * 2;
          var y1 = y0 +1;

          var s0 = Get( x0, y0 );
          var s1 = Get( x1, y0 );
          var s2 = Get( x0, y1 );
          var s3 = Get( x1, y1 );

          var maxValue = MathX.Max( s0, s1, s2, s3 );
          var avgValue = (s0 + s1 + s2 + s3 ) / 4f;
          var value = Mathf.Lerp( avgValue, maxValue, averageVsMaxFilter );

          lower.Set( i, j, value );
        }
      }

      return lower;
    }
  }
}