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

namespace Rokojori
{  
  
  [Tool]
  [GlobalClass]
  public partial class WorldMap:Node
  {
    [Export]
    public WorldMapDefinition worldMapDefinition;

    [Export]
    public Node regionsContainer;

    [Export]
    public Node3D[] streamTargets = new Node3D[ 0 ];

    [Export]
    public float streamTargetRadius = 500;

    [Export]
    public WorldMapLayerSetting[] layerSettings = new WorldMapLayerSetting[ 0 ];

    [Export]
    public float hueScale = 0.01f;

    public enum LoadingStatus
    {
      Not_Loaded,
      Loading,
      Loaded,
      Unloading,
      Error
    }

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

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

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

    public override void _Process( double delta )
    {
      StreamRegions();
    } 

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

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

    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( worldMapDefinition.regionSize, 0, worldMapDefinition.regionSize );

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

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

    protected void StreamRegions()
    {
      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 );
      }
    }

    

    void GetStreamRegions( Node3D streamTarget, HashSet<string> regions )
    {
      var streamPosition = streamTarget.GlobalPosition;
      
      var streamMin = streamPosition - Vector3.One * streamTargetRadius;
      var streamMax = streamPosition + Vector3.One * streamTargetRadius;

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


      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 > streamTargetRadius )
          {
            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 = regionsContainer.CreateChild<WorldRegion>( regionID );
      worldRegion.indexX = regionIndex.X;
      worldRegion.indexZ = regionIndex.Y;

      var s = worldMapDefinition.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 );
    }


  }
}