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

namespace Rokojori
{  
  [Tool]
  [GlobalClass]
  public partial class GridWorldMapLayer:WorldMapLayerType
  {
    [Export]
    public float regionSize = 250;

    [Export]
    public float hueScale;

    public override void Update( WorldMapLayer layer, WorldMap map )
    {
      UpdateLayer( layer, map );
    }
    
    public enum LoadingStatus
    {
      Not_Loaded,
      Loading,
      Loaded,
      Unloading,
      Error
    }

    public class WorldMapRegionInfo
    {
      public float loadingTime;
      public string id;
      public LoadingStatus status;
      public GridWorldRegion region;
      public List<WorldMapLayerInfo> layerInfos = new List<WorldMapLayerInfo>();  
    }

    public class WorldMapLayerInfo
    {
      public LoadingStatus status;
      public WorldMapLayer layer;
    }

    public override void OnWorldEditorNodeUpdate( NodeUpdate update, WorldMapLayer mapLayer, Node node )
    {
      var position = GetPositionOfNode( node );
      var regionID = WorldToRegions( position );
      var parentRegion = node.FindParentThatIs<GridWorldRegion>();


      if ( NodeUpdate.Deleted == update )
      {
        node.RemoveFromParent();  

        if ( parentRegion != null )
        {
          parentRegion.UpdateRegion();
        }
        
        return;
      }
             
      var region = mapLayer.FindDirectChild<GridWorldRegion>( r => r.regionID == regionID );

      if ( region == null )
      {        
        region = mapLayer.CreateChild<GridWorldRegion>( "Region-" + regionID.X + "-" + regionID.Y );
      }

      if ( region == parentRegion )
      {
        parentRegion.UpdateRegion();
        return;
      }
      
      node.RemoveFromParent();
      parentRegion.UpdateRegion();
      region.AddChild( node );
      region.UpdateRegion();
    }

    public string CreateRegionID( Vector2 regionsPosition )
    {
      var x = Mathf.RoundToInt( regionsPosition.X );
      var y = Mathf.RoundToInt( regionsPosition.Y );

      return CreateRegionID( x, y );
    }

    public string CreateRegionID( int x, int y)
    {
      return x + "," + y; 
    }    

    public Vector2I ParseID( string id )
    {
      var numbers = id.Split( "," );

      var x = RegexUtility.ParseInt( numbers[ 0 ] );
      var y = RegexUtility.ParseInt( numbers[ 1 ] ); 

      return new Vector2I( x, y );
    }

    public Box2 GetXZWorldBoxOfRegion( int regionX, int regionY )
    {
      var boxStart = RegionsToWorld( new Vector2( regionX, regionY ) ) ;
      var boxEnd   = boxStart + new Vector3( regionSize, 0, regionSize );

      return new Box2( Math2D.XZ( boxStart ), Math2D.XZ( boxEnd ) );
    }

    


    protected Dictionary<string,WorldMapRegionInfo> _loadedRegions = new Dictionary<string, WorldMapRegionInfo>();
    protected WorldMapLayer _layer;

    protected void ClearRegions()
    {
      Nodes.RemoveAndDeleteChildren( _layer );
      _loadedRegions.Clear();
    } 

    void UpdateLayer( WorldMapLayer layer, WorldMap map )
    {
      _layer = layer;
      var streamTargets = map.streamers;

      var regionsToStream = new HashSet<string>();

      for ( int i = 0; i < streamTargets.Length; i++ )
      {
        GetStreamRegions( streamTargets[ i ], regionsToStream );
      }

      foreach ( var s in regionsToStream )
      {
        if ( _loadedRegions.ContainsKey( s ) )
        {
          continue;
        }

        LoadRegion( s );
      } 

      foreach ( var s in _loadedRegions )
      {
        if ( regionsToStream.Contains( s.Key ) )
        {
          continue;
        }

        UnloadRegion( s.Key );
      }
    }
    
    public Vector2 WorldToRegions( Vector3 worldPosition )
    {
      return Math2D.XZ( worldPosition / regionSize );
    }

    public Vector3 RegionsToWorld( Vector2 regionsPosition )
    {
      return Math3D.XYasXZ( regionsPosition ) * regionSize;
    }
    

    void GetStreamRegions( WorldStreamer streamer, HashSet<string> regions )
    {
      var streamPosition = streamer.GetStreamCenter();
      var streamRadius   = streamer.GetStreamRadius();
      
      var streamMin = streamPosition - Vector3.One * streamRadius;
      var streamMax = streamPosition + Vector3.One * streamRadius;

      var positionMin = WorldToRegions( streamPosition - Vector3.One * streamRadius );
      var positionMax = WorldToRegions( streamPosition + Vector3.One * streamRadius );


      positionMin = positionMin.Floor();
      positionMax = positionMax.Ceil();

      // RJLog.Log( "CheckRegions", streamMin, streamMax, positionMin, positionMax );

      var min = new Vector2I( (int)positionMin.X, (int)positionMin.Y );
      var max = new Vector2I( (int)positionMax.X, (int)positionMax.Y );

      var index = 0;

      var streamPositionXZ = Math2D.XZ( streamPosition );

      for ( int i = min.X; i <= max.X; i++ )
      {
        for ( int j = min.Y; j <= max.Y; j++ )
        {
          var regionID = CreateRegionID( i, j );

          if ( regions.Contains( regionID ) )
          {
            continue;
          }

          var boxXZ = GetXZWorldBoxOfRegion( i, j );

          var distance = boxXZ.DistanceTo( streamPositionXZ );

          if ( index < 3 )
          {
            // RJLog.Log( i, j, boxXZ.center, distance );
            index++;
          }
          

          if ( distance > streamRadius )
          {
            continue;
          }

          regions.Add( regionID );
        }
      }

    }

    void LoadRegion( string regionID )
    {
      var regionIndex = ParseID( regionID );

      // RJLog.Log( "Loading regionID:", regionID, regionIndex.X, regionIndex.Y );
      var regionInfo = new WorldMapRegionInfo();
      regionInfo.id = regionID;
      regionInfo.loadingTime = Time.GetTicksMsec() / 1000f;
    
      _loadedRegions[ regionID ] = regionInfo;

      var worldRegion = _layer.CreateChild<GridWorldRegion>( regionID );
      worldRegion.indexX = regionIndex.X;
      worldRegion.indexZ = regionIndex.Y;

      var s = regionSize;
      var h = s / 2f;
       var hue = Noise.PerlinXZ( RegionsToWorld( regionIndex ) * hueScale );

      var material = new StandardMaterial3D();
      material.AlbedoColor = new HSLColor( hue * 360, 1, 0.5f );
      material.Metallic = 0.2f;
      material.Roughness = 0.6f;

      worldRegion.GlobalPosition = RegionsToWorld( regionIndex ) + new Vector3( h, 0, h );

      // var lodHM = worldRegion.CreateChild<LODHeightMapGeometry>();
      // lodHM.material = material;
      // lodHM.Create();


      var box = worldRegion.CreateChild<CsgBox3D>( "Box" );
      box.Size = new Vector3( s, 1, s );  
      box.Material = material;
      
      

      regionInfo.region = worldRegion;

      

      
    } 

    void UnloadRegion( string regionID )
    {
      // RJLog.Log( "Unloading regionID:", regionID );

      var info = _loadedRegions[ regionID ];

      Nodes.RemoveAndDelete( info.region );

      _loadedRegions.Remove( regionID );
    }
  }
}