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

[Tool, GlobalClass]
public partial class LevelGenerator : Action
{
  [Export]
  public int numWalls = -1;
  
  [Export]
  public int numRooms = 100;

  [Export]
  public int numRoomsEditor = 20;

  [Export]
  public float minRadius = 200;

  [Export]
  public float maxRadius = 4000;

  [Export]
  public Material roomCenterMaterial;

  [Export]
  public Material lowPolyMaterial;

  [Export]
  public int seed = -1;

  [Export]
  public float wallThickness = 2f;

  [Export]
  public float boundaryDensity = 10;

  [Export]
  public float boundarySize = 10;

  [Export]
  public PackedScene wall;

  [Export]
  public float wallSize = 1f;

  [Export]
  public PackedScene roomCenter;

  [Export]
  public RoomGeneratorEntry[] rooms;

  [Export]
  public TextureAttributes textureAttributes;

  [Export]
  public Node3D[] walls = [];
  
  protected override void _OnTrigger()
  {
    numWalls = 0;
    this.DestroyChildren();

    var points = new List<Vector2>();


    var boundaryPointSize = Circle.WithRadius( maxRadius ).circumference / boundaryDensity;

    this.LogInfo( "Num Boundary Points", boundaryPointSize );

    for ( int i = 0; i < boundaryPointSize; i++ )
    {
      var t = (float) i / ( float ) ( boundaryPointSize );

      var angle = t * Mathf.Pi * 2f;

      var boundaryPoint = Math2D.Circle( angle, maxRadius + boundarySize );

      points.Add( boundaryPoint );
    }
    
    points.Add( Vector2.Zero );

    var random = seed == -1 ? LCG.Randomized() : LCG.WithSeed( seed );

    var rooms = Engine.IsEditorHint() ? numRoomsEditor : numRooms;

    for ( int i = 0; i < rooms; i++ )
    { 
      var p = random.InRectangle( Vector2.One * -maxRadius, Vector2.One * maxRadius );

      if ( p.Length() < minRadius || p.Length() >= maxRadius )
      {
        p = random.InRectangle( Vector2.One * -maxRadius, Vector2.One * maxRadius );
      }

      if ( p.Length() < minRadius || p.Length() >= maxRadius )
      {
        p = random.InRectangle( Vector2.One * -maxRadius, Vector2.One * maxRadius );
      }

      if ( p.Length() < minRadius || p.Length() >= maxRadius )
      {
        if ( p.Length() == 0 )
        {
          p = Vector2.One;
        }

        p = p.Normalized() * random.Range( minRadius, maxRadius ); 
      }

      points.Add( p );
    }


    var voronoi = Voronoi2D.CreateFromCellPoints( points );

    var lowMG = new MeshGeometry();
    lowMG.colors = new List<Color>();

    var wallIndex = 0;
    var wallsList =new List<Node3D>();
    voronoi.edges.ForEach(
      ( e )=>
      {
        var w = CreateWallsAtEdge( e, voronoi, lowMG, wallIndex );
        wallsList.Add( w );
        wallIndex ++;

      }
    );

    walls = wallsList.ToArray();

    // wallActi

    var lowPolyMI = this.CreateChild<MeshInstance3D>( "LowPoly" );

    lowPolyMI.Mesh = lowMG.GenerateMesh();
    lowPolyMI.Mesh.SurfaceSetMaterial( 0, lowPolyMaterial );

    voronoi.cells.ForEach(
      ( vc )=>
      {
        if ( vc.index < boundaryPointSize )
        {
          return;
        }

        var rg = GetRoomGenerator( random );
        rg.GenerateRoom( this, vc );
        // var p = voronoi.cellPoints[ vc.index ];
        // var center = p.To3DXZ();

        // var room = this.CreateChild<Node3D>( roomCenter, "Center" + vc.index );
        // room.GlobalPosition = center;
        
      }

    );   
  

  }

  RoomGenerator GetRoomGenerator( RandomEngine random )
  {
    var index = random.IndexFromUnnormalizedWeights( rooms.Map( r => r.probability ).ToList() );

    return rooms[ index ].room;
  }
  public override void _Ready()
  {
    this.LogInfo( "Ready", this.walls.Length );

    InitializeWalls();
  }

  async Task InitializeWalls()
  {
    var frames = 0;

    await this.RequestNextFrame();
    


    var index = 0;

    this.ForEachDirectChild<Node3D>( 
      ( n )=>
      {
        if ( ! n.Name.ToString().Contains( "Wall" ) )
        {
          return;
        }

        var setAtt = n.GetAll<SetTextureAttributeChannel>();

        setAtt.ForEach( s => s.index = index );
        index ++;

        // this.LogInfo( "SetAtt", HierarchyName.Of( n ), setAtt.index );
      }
    );
  }

  public Node3D CreateWallsAtEdge( Voronoi2D.Edge edge, Voronoi2D v, MeshGeometry output, int wallIndex )
  {
    var p0 = v.boundaryPoints[ edge.start ];
    var p1 = v.boundaryPoints[ edge.end ];
    var center = p0.Lerp( p1, 0.5f ).To3DXZ();

    var yaw = Math3D.GlobalYaw( p0.To3DXZ() - p1.To3DXZ() );

    var length = ( p1 - p0 ).Length();

    var numWalls = length / wallSize;

    var wallObject = this.CreateChild<Node3D>( wall, "Wall " + wallIndex );
    wallObject.GlobalPosition = center;
    wallObject.SetGlobalYaw( yaw + Mathf.Pi * 0.5f );
    wallObject.Scale = new Vector3( numWalls, 1, 1 );
    
    var setTexturesAttributes= wallObject.GetAll<SetTextureAttributeChannel>();

    this.LogInfo( "Num walls:", setTexturesAttributes.Count );

    setTexturesAttributes.ForEach(
      ( st )=>
      {
        st.index = wallIndex;
        st.textureAttributes = textureAttributes;

        this.LogInfo( "Set Index:", st.index, HierarchyName.Of( st ) );
      }
    );


    var lowPolyWall = MeshGeometry.BillboardQuad();
    var trsf = Math3D.TRS( center, Math3D.RotateY( yaw + Mathf.Pi * 0.5f ), new Vector3( length, 250, 1 ) );
    lowPolyWall.ApplyTransform( trsf );

    var colorIndex = MathX.FlatIndexToMultiIndex( wallIndex, new Vector2I( 255, 255 ) );

    var color = new Color( colorIndex.X / 255f, colorIndex.Y / 255f, 0 );

    // this.LogInfo( wallIndex, ">>", colorIndex.X, colorIndex.Y, color );

    lowPolyWall.SetColor( color ); 
    output.Add( lowPolyWall );

    this.numWalls ++;

    return wallObject;

    // for ( int i = 0; i < numWalls; i++ )
    // {
    //   var t = i / ( float )(numWalls );
    //   var p = p0.Lerp( p1, t );

    //   var wallObject = this.CreateChild<Node3D>( wall );
    //   wallObject.GlobalPosition = p.To3DXZ();
    //   wallObject.SetGlobalYaw( yaw );
    // }
  } 
}