using System.Collections;
using System.Collections.Generic;
using Godot;
using System;
using System.Threading.Tasks;



namespace Rokojori
{
  /** <summary for="class FoliageRenderLayer">
      
      <title>
        Is used by the FoliageRenderer to book keep and render a layer defined by the FoliageData.
        
      </title>
      
      <description>
               

      </description>

    </summary>  
  */

  public class FoliageRenderLayer
  {
    public FoliageRenderer renderer;
    public FoliageData data;
    public int subLayerIndex = -1;
    public GpuParticles3D gpuParticles3D;
    public GPUFoliageShaderMaterial gpuFoliageShaderMaterial;
    public Node3D collidersContainer;

    float _lastCellSize = -1;
    float _lastMaxVisibility = -1;
    
    Vector2I lastCameraPosition;

    
    public void UpdateColliders()
    {
      if ( ! data.collidersEnabled )
      {
        return;
      }

      var cameraPosition = gpuFoliageShaderMaterial.cameraPosition.GetCached(); 
      var camXZ = cameraPosition.XZ();
      var floored = (Vector2I) Math2D.SnapFloored( camXZ, data.colliderCellSize * Vector2.One );

      if ( floored == lastCameraPosition && ! data.updateCollidersAlways )
      {
        return;
      }

      lastCameraPosition = floored;

      // collidersContainer.LogInfo( "Updating Colliders:", lastCameraPosition );

      var side = numParticlesPerSide();

      var num = side * side;
      var list = new List<Transform3D>( num );
      var centerDistance = new Dictionary<Transform3D,float>();

      var numInvalid = 0;
      
      for ( int i = 0; i < num; i ++ )
      {
        var trsf = ComputeTransform( i );

        if ( ! trsf.IsValid() )
        {
          if ( numInvalid == 0 )
          {
            RJLog.Log( trsf );
          }

          numInvalid ++;

          
          continue;
        }

        list.Add( trsf );   

        var distance = ( trsf.Origin.XZ() - camXZ ).Length();
        centerDistance[ trsf ] = distance;     
      }

      list.Sort(
        ( a, b )=>
        {
          var dA = centerDistance[ a ];
          var dB = centerDistance[ b ];

          return Mathf.Sign( dA - dB );
        }
      );

      if ( numInvalid > 0 )
      {
        RJLog.Log( "Invalud:", numInvalid );
      }

      CreateCollidersPool();
      
      for ( int i = 0; i < data.numMaxColliders; i++ )
      {
        var child = (StaticBody3D) collidersContainer.GetChild( i );

        NodeState.SetEnabledState( child, i < list.Count );  

        if ( i < list.Count )
        {
          child.GlobalTransform = list[ i ];
        }
      }


      
      


    }

    void CreateCollidersPool()
    {
      if ( data.numMaxColliders == collidersContainer.GetChildCount() )
      {
        return;
      }

      collidersContainer.DestroyChildren();

      for ( int i = 0; i < data.numMaxColliders; i++ )
      {
        var staticBody = collidersContainer.CreateChild<StaticBody3D>( "Collider Body " + i );
        var shape3D = staticBody.CreateChild<CollisionShape3D>( "Collision Shape " + i );
        shape3D.Shape = data.colliderShape;
      }
    }


    

    public static FoliageRenderLayer Create( FoliageRenderer renderer, FoliageData data, int subIndex = -1 )
    {
      var rl = new FoliageRenderLayer();
      rl.renderer = renderer;
      rl.data = data;
      rl.subLayerIndex = subIndex;
     

      return rl;
    }

    bool _isEnabled = false;

    public void Update( double delta )
    {
      if ( gpuFoliageShaderMaterial == null || data == null )
      {
        return;
      }

      if ( data.enabled != _isEnabled)
      {
        _isEnabled = data.enabled;
        gpuParticles3D.SetEnabledState( _isEnabled );
        return;
      }

      if ( ! _isEnabled )
      {
        return;
      }

      gpuFoliageShaderMaterial.cameraPosition.SetCached( renderer.GetAssignedCamera().GlobalPosition );

      if ( ! data.updateSettings )
      {
        return;
      }

      UpdateProcessMaterial();

      

      data.materialOverride?.UpdateFoliageOverideMaterial( this );

      var material = data.materialOverride != null ? 
                      data.materialOverride.GetOverrideMaterial( this ) : 
                      gpuParticles3D?.DrawPass1?.SurfaceGetMaterial( 0 );

      if ( material == null )
      {
        return;
      }

      data.materialOverride?.translucencySettings?.ApplyTranslucency( material, this );

      UpdateColliders();

      // if ( data.overwriteAlphaScissorToDepthPrepass && gpuParticles3D.DrawPass1.SurfaceGetMaterial( 0 ) is StandardMaterial3D standardDrawMaterial )
      // {
      //   if ( BaseMaterial3D.TransparencyEnum.AlphaScissor == standardDrawMaterial.Transparency  )
      //   {
      //     var overrideMaterial = (StandardMaterial3D) standardDrawMaterial.Duplicate();
      //     overrideMaterial.Transparency = BaseMaterial3D.TransparencyEnum.AlphaDepthPrePass;
      //     gpuParticles3D.MaterialOverride = overrideMaterial;
      //     overrideMaterial.RenderPriority = data.renderPriority;
      //   }         

      // }

      // if ( gpuParticles3D.DrawPass1.SurfaceGetMaterial( 0 ) is ShaderMaterial drawMaterial )
      // {
      //   drawMaterial.RenderPriority = data.renderPriority;

      //   drawMaterial.SetShaderParameter( "obstacle1", renderer.GetObstacleData( 0 ) );
      //   drawMaterial.SetShaderParameter( "obstacle2", renderer.GetObstacleData( 1 ) );
      //   drawMaterial.SetShaderParameter( "obstacle3", renderer.GetObstacleData( 2 ) );
      //   drawMaterial.SetShaderParameter( "obstacle4", renderer.GetObstacleData( 3 ) );

      //   // RJLog.Log( drawMaterial, renderer.GetObstacleData( 0 ) );
      // }

      
    } 

    public int numParticlesPerSide()
    {
      var cellSize = FoliageQualitySettings.GetCellSize( renderer.quality, renderer.qualitySettingsAll, data.qualitySettings, data.GetCellSize( subLayerIndex ) );
      var visibilityRange =  FoliageQualitySettings.GetVisibilityRange( renderer.quality, renderer.qualitySettingsAll, data.qualitySettings, data.GetVisibilityRange( subLayerIndex ) );
      

      return Mathf.CeilToInt( visibilityRange / cellSize ) * 2;
    }
    
    void UpdateProcessMaterial()
    {
      var cellSize = FoliageQualitySettings.GetCellSize( renderer.quality, renderer.qualitySettingsAll, data.qualitySettings, data.GetCellSize( subLayerIndex ) );
      var visibilityRange =  FoliageQualitySettings.GetVisibilityRange( renderer.quality, renderer.qualitySettingsAll, data.qualitySettings, data.GetVisibilityRange( subLayerIndex ) );
      
      var sizeInt = Mathf.CeilToInt( visibilityRange / cellSize ) * 2;

      if ( _lastCellSize != cellSize || _lastMaxVisibility != visibilityRange )
      {
        _lastCellSize = cellSize;
        _lastMaxVisibility = visibilityRange;

        gpuFoliageShaderMaterial.cellSize.SetCached( cellSize );    

        gpuParticles3D.Amount = sizeInt * sizeInt;
        gpuFoliageShaderMaterial.width.SetCached( sizeInt );
        gpuFoliageShaderMaterial.height.SetCached( sizeInt );

      }

      var hideStart = visibilityRange - ( visibilityRange * data.GetVisibilityFadeRelative( subLayerIndex ) + data.GetVisibilityFadeAbsolute( subLayerIndex ) );
      gpuFoliageShaderMaterial.hideMax.SetCached( visibilityRange );
      gpuFoliageShaderMaterial.hideStart.SetCached( hideStart );
      gpuFoliageShaderMaterial.hideOffset.SetCached( data.GetVisibilityFadeHidingOffset( subLayerIndex ) ); 

      gpuFoliageShaderMaterial.mapCenter.SetCached( renderer.globalMapSizeXZ );
      gpuFoliageShaderMaterial.mapSize.SetCached( renderer.globalMapSizeXZ );

      gpuFoliageShaderMaterial.heightMap.SetCached( renderer.heightMap );
      gpuFoliageShaderMaterial.minHeight.SetCached( renderer.minHeight );
      gpuFoliageShaderMaterial.maxHeight.SetCached( renderer.maxHeight );

      gpuFoliageShaderMaterial.coverageMap.SetCached( renderer.coverageMap );


      gpuFoliageShaderMaterial.positionVariance.SetCached( renderer.noise );
      gpuFoliageShaderMaterial.rotationVariance.SetCached( renderer.noise );
      gpuFoliageShaderMaterial.scaleVariance.SetCached( renderer.noise );
      gpuFoliageShaderMaterial.occupancyVariance.SetCached( renderer.noise );

      gpuFoliageShaderMaterial.maxPositionOffset.SetCached( data.positionVarianceAbsoluteOffset + data.positionVarianceCellSizeRelativeOffset * cellSize );
      gpuFoliageShaderMaterial.positionOffset.SetCached( data.GetPositionOffset( subLayerIndex ) );
      gpuFoliageShaderMaterial.positionUvScale.SetCached( Vector2.One * data.positionVarianceScale );
      gpuFoliageShaderMaterial.positionUvOffset.SetCached( Vector2.One * data.positionVarianceOffset );

      gpuFoliageShaderMaterial.minRotation.SetCached( data.rotationMin );
      gpuFoliageShaderMaterial.maxRotation.SetCached( data.rotationMax );
      gpuFoliageShaderMaterial.rotationUvScale.SetCached( Vector2.One * data.rotationVarianceScale );
      gpuFoliageShaderMaterial.rotationUvOffset.SetCached( Vector2.One * data.rotationVarianceOffset );

      gpuFoliageShaderMaterial.minScale.SetCached( data.scaleVarianceMinScale );
      gpuFoliageShaderMaterial.maxScale.SetCached( data.scaleVarianceMaxScale );

      gpuFoliageShaderMaterial.uniScaleMin.SetCached( data.uniScaleMin );
      gpuFoliageShaderMaterial.uniScaleMax.SetCached( data.uniScaleMax );
      gpuFoliageShaderMaterial.scaleVarianceContrastScale.SetCached( data.uniScaleVarianceContrastScale );
      gpuFoliageShaderMaterial.scaleVarianceContrastCenter.SetCached( data.uniScaleVarianceContrastCenter );
      // gpuFoliageShaderMaterial.uniScaleMax.Set( data.uniScaleVarianceOffset );

      gpuFoliageShaderMaterial.uniScaleUvOffset.SetCached( data.uniScaleVarianceOffset );

      gpuFoliageShaderMaterial.scaleUvScale.SetCached( Vector2.One * data.scaleVarianceScale );
      gpuFoliageShaderMaterial.scaleUvOffset.SetCached( Vector2.One * data.scaleVarianceOffset );

      gpuFoliageShaderMaterial.occupancyAmount.SetCached( data.occupancyVarianceAmount );
      gpuFoliageShaderMaterial.occupancyPower.SetCached( data.occupancyVariancePower );
      gpuFoliageShaderMaterial.occupancyTreshold.SetCached( data.occupancyTreshold );

      gpuFoliageShaderMaterial.occupancyHideOffset.SetCached( data.occupancyHideOffset );
      gpuFoliageShaderMaterial.occupancyHideScale.SetCached( data.occupancyHideScale );

      gpuFoliageShaderMaterial.occupancyUvScale.SetCached( Vector2.One * data.occupancyVarianceScale );
      gpuFoliageShaderMaterial.occupancyUvOffset.SetCached( Vector2.One * data.occupancyVarianceOffset );

      if ( data.GetSort( subLayerIndex ) )
      {
        gpuParticles3D.DrawOrder = GpuParticles3D.DrawOrderEnum.ViewDepth;
      }
    }

    Vector2 IndexToPosition( int index, int width, int height )
    {
      int x = index % width;
      int y = index / height;
      
      return new Vector2( x, y );  
    }

    Vector2 rotate_v2( Vector2 uv, float angle )
    {
      float s = Mathf.Sin( angle );
      float c = Mathf.Cos( angle );

        
      float x = uv.X;
      float y = uv.Y; 
    
      uv.Y = c * x - s * y;
      uv.Y = s * x + c * y;

      return uv;
    }

    Vector2 tilingOffset( Vector2 uv, Vector2 scale, Vector2 offset )
    {
      return uv * scale + offset;
    }

    
    public Vector4 textureLod( Texture2D texture2D, Vector2 uv, int lod )
    {
      if ( texture2D == null )
      {
        return Vector4.One;
      }

      var image = texture2D.GetImage();    
      var color = image.SampleNearest( uv );
      
      return color.ToVector4();
      // return color.LinearToSRGB().ToVector4();
    }
    
    public Transform3D ComputeTransform( int INDEX )
    {

      // gpuFoliageShaderMaterial.(\w+).+;
      // var $1 = m.$1.GetCached();
      var m = gpuFoliageShaderMaterial;

      var yaw = m.yaw.GetCached();
      var width = m.width.GetCached();
      var height = m.height.GetCached();
      var cellSize = m.cellSize.GetCached();
      var cameraPosition = m.cameraPosition.GetCached();

      var heightOffset = m.heightOffset.GetCached();

      var hideStart = m.hideStart.GetCached();
      var hideMax = m.hideMax.GetCached();
      var hideOffset = m.hideOffset.GetCached();

      var mapSize = m.mapSize.GetCached();
      var mapCenter = m.mapCenter.GetCached();

      var positionUVScale = m.positionUvScale.GetCached();
      var positionUVOffset = m.positionUvOffset.GetCached();
      var maxPositionOffset = m.maxPositionOffset.GetCached();

      var positionVariance = m.positionVariance.Get();
      var positionOffset = m.positionOffset.GetCached();

      var minRotation = m.minRotation.GetCached();
      var maxRotation = m.maxRotation.GetCached();
      var rotationUVScale = m.rotationUvScale.GetCached();
      var rotationUVOffset = m.rotationUvOffset.GetCached();

      var rotationVariance = m.rotationVariance.Get();

      var scaleVariance = m.scaleVariance.Get();

      var minScale = m.minScale.GetCached();
      var maxScale = m.maxScale.GetCached();

      var uniScaleMin = m.uniScaleMin.GetCached();
      var uniScaleMax = m.uniScaleMax.GetCached();
      var scaleVarianceContrastScale = m.scaleVarianceContrastScale.GetCached();
      var scaleVarianceContrastCenter = m.scaleVarianceContrastCenter.GetCached();

      var uniScaleUVOffset = m.uniScaleUvOffset.GetCached();

      var scaleUVScale = m.scaleUvScale.GetCached();
      var scaleUVOffset = m.scaleUvOffset.GetCached();

      var occupancyAmount = m.occupancyAmount.GetCached();
      var occupancyPower = m.occupancyPower.GetCached();
      var occupancyTreshold = m.occupancyTreshold.GetCached();

      var occupancyHideOffset = m.occupancyHideOffset.GetCached();
      var occupancyHideScale = m.occupancyHideScale.GetCached();

      var occupancyUVScale = m.occupancyUvScale.GetCached();
      var occupancyUVOffset = m.occupancyUvOffset.GetCached();

      var heightMap = renderer.heightMap;
      var occupancyVariance = renderer.noise;
      var coverageMap = renderer.coverageMap;

      var minHeight = renderer.minHeight;
      var maxHeight = renderer.maxHeight;
      
      var discardTreshold =  0.01f;
      var discardOffset = 1000000f;
      
      Vector2 position = IndexToPosition( INDEX, width, height );
      float rotation = Mathf.Round( 4.0f * yaw / 360.0f ) * Mathf.Pi / 2.0f;

      Vector3 snappedCameraPosition = new Vector3( 
        Mathf.Floor( cameraPosition.X / cellSize ) * cellSize, 
        0,
        Mathf.Floor( cameraPosition.Z/ cellSize ) * cellSize
      );
      
      Vector2 origin = new Vector2( width/2.0f, height/2.0f );
      position -= origin;
      
      position = rotate_v2( position, rotation ) * cellSize;
    
      
      Vector3 position3D = new Vector3( position.X, heightOffset, position.Y ) + snappedCameraPosition;
      
      float d = ( position3D - cameraPosition ).Length();
      float yOffset = MathX.RemapClamped( d, hideStart, hideMax, 0, hideOffset );
      position3D.Y += yOffset;
      
      
      Vector2 mapOffset = mapCenter - mapSize/2.0f;
      Vector2 uv = ( new Vector2( position3D.X, position3D.Z ) - mapOffset ) / mapSize;

      

      Vector3 offset = (textureLod( positionVariance, tilingOffset( uv, positionUVScale, positionUVOffset ), 0 ).rgb() - new Vector3(0.5f,0.5f,0.5f) )  * maxPositionOffset;
      position3D += offset + positionOffset;
      Vector3 rotSampled = textureLod( rotationVariance, tilingOffset( uv, rotationUVScale, rotationUVOffset ), 0 ).rgb();
      Vector3 rot = ( rotSampled * ( maxRotation - minRotation ) + minRotation ) * Mathf.Pi * 2.0f;
      // rot.Y = round( 4.0 * rot.Y / PI * 2.0 ) / 4.0 * PI * 2.0;
      Vector3 sca = textureLod( scaleVariance, tilingOffset( uv, scaleUVScale, scaleUVOffset ), 0 ).rgb();
      sca = ( sca - Vector3.One * scaleVarianceContrastCenter ) * scaleVarianceContrastScale + Vector3.One * scaleVarianceContrastCenter;
      sca = Math3D.Clamp( sca, Vector3.Zero, Vector3.One );
      sca = sca * ( maxScale - minScale ) + minScale;

      float uniScale = textureLod( scaleVariance, tilingOffset( uv + uniScaleUVOffset, scaleUVScale, scaleUVOffset ), 0 ).r();
      uniScale = ( uniScale - scaleVarianceContrastCenter ) * scaleVarianceContrastScale + scaleVarianceContrastCenter;
      uniScale = Mathf.Clamp( uniScale, 0.0f, 1.0f );
      uniScale = uniScale * ( uniScaleMax - uniScaleMin ) + uniScaleMin;

      sca *= uniScale;
      Vector2 uv2 = ( new Vector2( position3D.X, position3D.Z ) - mapOffset ) / mapSize;
      float sampledHeight = textureLod( heightMap, uv2, 0 ).r();
      float occ = textureLod( occupancyVariance, tilingOffset( uv2, occupancyUVScale, occupancyUVOffset ), 0 ).r();
      float sampledCoverage = textureLod( coverageMap, uv2, 0 ).r();

      occ = Mathf.Clamp( ( occ - occupancyTreshold ) * occupancyPower + occupancyTreshold, 0.0f, 1.0f );
      occ = 1.0f - occ;
      
      occ = Mathf.Min( occ, 1.0f - sampledCoverage );

      sca *= Mathf.Lerp( 1.0f, occupancyHideScale , occ * occupancyAmount );  
      
      float combinedOcc = occ * occupancyAmount; ;
      position3D.Y += combinedOcc * occupancyHideOffset;
      position3D.Y += Mathf.Lerp( minHeight, maxHeight, sampledHeight );
      
      if ( combinedOcc >= ( 1.0f - MathX.Clamp01( discardTreshold ) ) )
      {
        position3D.Y += discardOffset;
      }

      position3D += data.colliderOffset;     

      return Math3D.TRS( position3D, rot, sca );
    }
  }
}