using System.Collections;
using System.Collections.Generic;
using Godot;
using System;

using System.Threading.Tasks;

namespace Rokojori.PointClouds
{
  
  public class PointCloudSampler
  { 
    public enum SampleMode
    {
      Center,
      Vertices,
      CenterAndVertices,
      Density
    }

    public List<Material> materials;
    public List<Sampler2DPropertyName> albedoTexture;
    public List<ColorPropertyName> albedo;
    public float densityResolution = 0.2f;

    List<Image> albedoTextureImages;

    SampleMode mode;
    
    ArrayMesh mesh;    
    Node worker;

    PointCloud pointCloud;

    

    public PointCloud SampleFromMeshSync( SampleMode sampleMode, ArrayMesh mesh, Node worker )
    {      
      this.mode = sampleMode;     
      this.mesh = mesh;
      this.worker = worker;

      pointCloud = new PointCloud();

      GrabTextureImages();

      for ( int i = 0; i < mesh.GetSurfaceCount(); i++ )
      {
        SampleSurfaceSync( mesh, i );
      }

      return pointCloud;
      
    }

    public async Task<PointCloud> SampleFromMesh( SampleMode sampleMode, ArrayMesh mesh, Node worker )
    {      
      
      this.mode = sampleMode;     
      this.mesh = mesh;
      this.worker = worker;

      pointCloud = new PointCloud();

      try
      {


        GrabTextureImages();

        for ( int i = 0; i < mesh.GetSurfaceCount(); i++ )
        {
          await SampleSurface( mesh, i );
        }

      }
      catch ( System.Exception e )
      {
        worker.LogError( e );
      }

      return pointCloud;
      
    }

    

    void GrabTextureImages()
    {
      var index = -1;

      albedoTextureImages = new List<Image>();

      albedoTexture.ForEach(
        ( at )=>
        {
          index++;

          if ( at == null )
          {
            albedoTextureImages.Add( null );
          }

          var material = materials[ index ];
          var texture = at.Get( material );

          if ( texture == null )
          {
            albedoTextureImages.Add( null );
            
            return;

          }

          var image = texture.GetImage();

          albedoTextureImages.Add( image );
        }
      );

      // worker.LogInfo( "Added image:", albedoTextureImages.Count );
    }



    public void SampleSurfaceSync( ArrayMesh mesh, int surface )
    {
      if ( SampleMode.Density == mode )
      {
        SampleDensitySync( mesh, surface );
        return;
      }
      
      SampleSimpleSync( mesh, surface );

      return;
    }

    

    void SampleDensitySync( ArrayMesh mesh, int surface )
    {
      var mdt = new MeshDataTool();
      mdt.CreateFromSurface( mesh, surface );

      var nullTriangles = 0;
      var numFaces = mdt.GetFaceCount();
      var subDivs = 0;


      for ( int i = 0; i < mdt.GetFaceCount(); i++ )
      {
        // time = await Async.WaitIfExceeded( time, worker );

        var positions = new List<Vector3>();
        var normals = new List<Vector3>();
        var uvs = new List<Vector2>();
        
        for ( int j = 0; j < 3; j++ )
        {
          var index = mdt.GetFaceVertex( i, j );

          var p = mdt.GetVertex( index );
          var uv = mdt.GetVertexUV( index );
          var n  = mdt.GetVertexNormal( index );

          positions.Add( p );
          uvs.Add( uv );
          normals.Add( n );
        }

        var triangle = Triangle3.CreateFrom( positions );

        AddPoints( positions, uvs, normals );
        
        var minArea = densityResolution;

        var points = new List<Vector3>(); 
        triangle.GetSubdivisionPoints( points, minArea );

        
        // worker.LogInfo( triangle.area, points.Count  );
        

        points.ForEach(
          ( p )=>
          {
            var uv = (Vector2) triangle.Lerp( p, uvs[ 0 ], uvs[ 1 ], uvs[ 2 ] );
            var n  = (Vector3) triangle.Lerp( p, normals[ 0 ], normals[ 1 ], normals[ 2 ] );

            AddPoint( p, uv, n );
            subDivs ++;
          }
        );

        // if ( triangle.area > minArea || triangle.longestEdgeLength > densityResolution )
        // {
        //   worker.LogInfo( area );
        //   subDivs++;
        // }

        // triangle = triangle.Shrink( densityResolution );    

        // if ( triangle == null )
        // { 
        //   nullTriangles++;
        // }
        

        // var maxIterations = 1000;
        // var iteration = 0;

        // while ( iteration < maxIterations && triangle != null && ( triangle.area > minArea || triangle.longestEdgeLength > densityResolution ) )
        // {
        //   var newUVs = positions.Map( p => (Vector2) triangle.Lerp( p, uvs[ 0 ], uvs[ 1 ], uvs[ 2 ] ) );
        //   var newNormals = positions.Map( p => (Vector3) triangle.Lerp( p, normals[ 0 ], normals[ 1 ], normals[ 2 ] ) );

        //   positions = triangle.points;
        //   uvs = newUVs;
        //   normals = newNormals;

        //   AddPoints( positions, uvs, normals );

        //   triangle = triangle.Shrink( densityResolution );

        //   iteration ++;
        // }   
      }

      worker.LogInfo( "Tris:", numFaces, "Nulls:", nullTriangles, "Subs:", subDivs );
    }

    void AddPoints( List<Vector3> p, List<Vector2> u, List<Vector3> n )
    {
      for ( int i = 0; i < p.Count; i++ )
      {
        AddPoint( p[ i ], u[ i ], n[ i ] );
      }
    }

    void AddPoint( Vector3 p, Vector2 u, Vector3 n )
    {
      var point = new Point();
      point.color = Colors.White;
      point.position = p;
      point.normal = n;
      point.uv = u;
      pointCloud.points.Add( point );      
    }

    public async Task SampleSurface( ArrayMesh mesh, int surface )
    {      
      await SampleSimple( mesh, surface );

      return;
    }

    void SampleSimpleSync( ArrayMesh mesh, int surface )
    {
      var mdt = new MeshDataTool();
      mdt.CreateFromSurface( mesh, surface );


      var sampleVertices = SampleMode.Vertices == mode || SampleMode.CenterAndVertices == mode;
      var sampleCenter = SampleMode.Center == mode || SampleMode.CenterAndVertices == mode;

      for ( int i = 0; i < mdt.GetFaceCount(); i++ )
      {
        // time = await Async.WaitIfExceeded( time, worker );

        var center = new Vector3( 0, 0, 0 );
        var centerColor = new Vector4( 0, 0, 0, 0 );
        var centerNormal = new Vector3( 0, 0, 0 );
        var centerUV  = new Vector2( 0, 0 );

        for ( int j = 0; j < 3; j++ )
        {
          var index = mdt.GetFaceVertex( i, j );
          
          var p = mdt.GetVertex( index );
          var uv = mdt.GetVertexUV( index );
          var n  = mdt.GetVertexNormal( index );
          var color = Colors.White;

          var image = albedoTextureImages[ surface ];

          if ( image != null )
          {
            color = image.SampleLinear( uv, ImageExtensions.EdgeMode.Repeat );
          }

         

          center += p;
          centerColor += color.ToVector4();
          centerNormal += n;
          centerUV += uv;

          if ( sampleVertices )
          {
            var point = new Point();
            point.color = color;
            point.position = p;
            point.normal = n;
            point.uv = uv;
            pointCloud.points.Add( point );
          }          

        }

        if ( sampleCenter )
        {
          var point = new Point();
          point.color = ( centerColor / 3.0f ).ToColor();
          point.position = center / 3.0f;
          point.normal = centerNormal / 3.0f;
          point.uv = centerUV / 3.0f; 
          pointCloud.points.Add( point );
        }
      }
    }

    async Task SampleSimple( ArrayMesh mesh, int surface )
    {
      var mdt = new MeshDataTool();
      mdt.CreateFromSurface( mesh, surface );


      var sampleVertices = SampleMode.Vertices == mode || SampleMode.CenterAndVertices == mode;
      var sampleCenter = SampleMode.Center == mode || SampleMode.CenterAndVertices == mode;

      var time = Async.StartTimer();

      var faces = mdt.GetFaceCount();

      for ( int i = 0; i < faces; i++ )
      {
        time = await Async.WaitIfExceeded( time, worker );

        if ( i % 10000 == 0 )
        {
          worker.LogInfo( "Tris:", i + "/" + faces, RegexUtility._FFF( ( 100f * i ) / (float)faces ) );
        }

        var center = new Vector3( 0, 0, 0 );
        var centerColor = new Vector4( 0, 0, 0, 0 );
        var centerNormal = new Vector3( 0, 0, 0 );
        var centerUV  = new Vector2( 0, 0 );

        for ( int j = 0; j < 3; j++ )
        {
          var index = mdt.GetFaceVertex( i, j );
          
          var p = mdt.GetVertex( index );
          var uv = mdt.GetVertexUV( index );
          var n  = mdt.GetVertexNormal( index );
          var color = Colors.White;

          var image = albedoTextureImages[ surface ];

          if ( image != null )
          {
            color = image.SampleLinear( uv, ImageExtensions.EdgeMode.Repeat );
          }

         

          center += p;
          centerColor += color.ToVector4();
          centerNormal += n;
          centerUV += uv;

          if ( sampleVertices )
          {
            var point = new Point();
            point.color = color;
            point.position = p;
            point.normal = n;
            point.uv = uv;
            pointCloud.points.Add( point );
          }          

        }

        if ( sampleCenter )
        {
          var point = new Point();
          point.color = ( centerColor / 3.0f ).ToColor();
          point.position = center / 3.0f;
          point.normal = centerNormal / 3.0f;
          point.uv = centerUV / 3.0f; 
          pointCloud.points.Add( point );
        }

        if ( i == 0 )
        {
          pointCloud.boundingBox = Box3.Create( center, center );
        }
        else
        {
          pointCloud.boundingBox.IncludePoint( center );
        }
      }
    }
  }
}