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

using System.Linq;
using TriangleNet.Geometry;

namespace Rokojori
{
  public class CPUTextureDilater
  {
    public static async Task<Texture2D> Dilate( Texture2D texture, Func<Task> waiting, float alphaTreshold = 250f/255f )
    {
      var dilater = new CPUTextureDilater();
      dilater.alphaTreshold = alphaTreshold;
      dilater.waiting = waiting;

      var t = await dilater._Dilate( texture );

      return t;
    }

    TextureCombinerBuffer _buffer;
    Func<Task> waiting;

    async Task<Texture2D> _Dilate( Texture2D texture )
    {
      _buffer = TextureCombinerBuffer.From( texture );

      await _Process();

      return _buffer.CreateImageTexture();
    }

    public float alphaTreshold = 250f/255f;

    HashSet<int> _processed = new HashSet<int>();
    HashSet<int> _unprocessed = new HashSet<int>();
    HashSet<int> _edges = new HashSet<int>();

    

    async Task _Process()
    {
      _buffer.UnblendBlack();

      GrabInitialPixels();
      

      if ( _unprocessed.Count == _buffer.numPixels )
      {
        return;
      }

      int maxMessages = 100;
      int messageCounter = 0;
      int messageLimit = 200000;

      int max = 1000 * 1000;
      int it = 0;

      var random = LCG.WithSeed( 1984 );

      while ( _edges.Count > 0 && it < max )
      {
        it++;

        if ( messageCounter >= messageLimit )
        {
          await waiting();
          messageCounter = 0;
          RJLog.Log( 
            RegexUtility._FF( 100f *_processed.Count / _buffer.numPixels ) + "%",
            "Valid: ", ( _processed.Count + _edges.Count + _unprocessed.Count ) == _buffer.numPixels,
            "P", _processed.Count, "E",_edges.Count, "U", _unprocessed.Count 
          );
        }
        else
        {
          messageCounter ++;
        }

        var edge = _edges.ElementAt( random.IntegerExclusive( _edges.Count ) );
        _edges.Remove( edge );

        if ( ! _processed.Contains( edge ) )
        {
          var edgeColor = ComputeColor( edge );
          _processed.Add( edge );
          _buffer.SetIndexed( edge, edgeColor );
        }

        AddEdgeNeighbors( edge );
        
      }

      if ( _edges.Count > 0 && it == max )
      {
        RJLog.Log( "Aborted, too many entries" );
      }
      
    }

    void GrabInitialPixels()
    {
      for ( int i = 0; i < _buffer.numPixels; i++ )
      {
        if ( ! HasColor( i ) )
        {
          _unprocessed.Add( i );          
        }
        else
        {
          if ( IsEdge( i ) )
          {
            _edges.Add( i );
          }

          _processed.Add( i );
        }
      }
    }

    void AddEdgeNeighbors( int i )
    {
      var position = _buffer.ComputePositionFromIndex( i );

      for ( int x = -1; x <= 1; x++ )
      {
        for ( int y = -1; y <= 1; y++ )
        {
          if ( x == 0 && y == 0 )
          {
            continue;
          }

          var index = _buffer.ComputeIndexFromPosition( position.X + x, position.Y + y );

          if ( index < 0 || index >= _buffer.numPixels )
          {
            continue;
          }

          if ( ! _unprocessed.Contains( index ) )
          {
            continue;
          }

          _unprocessed.Remove( index );
          _edges.Add( index );
        }
      }

    }

    bool HasColor( int x, int y  )
    {
      return HasColor( _buffer.ComputeIndexFromPosition( x, y ) );
    }

    bool HasColor( int i )
    {
      if ( i < 0 || i >= _buffer.numPixels )
      {
        return false;
      }

      var pixel = _buffer.GetIndexed( i );
      
      if ( pixel.A > alphaTreshold )
      {
        return true;
      }

      return _processed.Contains( i );
    } 



    bool IsEdge( int i )
    {
      if ( ! HasColor( i ) )
      {
        return false;
      }

      var position = _buffer.ComputePositionFromIndex( i );     

      for ( int x = -1; x <= 1; x++ )
      {
        for ( int y = -1; y <= 1; y++ )
        {
          if ( x == 0 && y == 0 )
          {
            continue;
          }

          if ( ! HasColor( position.X + x, position.Y + y ) )
          { 
            return true;
          }
        }
      }

      return false;

    }

    Color ComputeColor( int i )
    {
      var position = _buffer.ComputePositionFromIndex( i );

      var pixel = _buffer.GetIndexed( i ); 
      var distance = 10f;
      var closestPixel = -1;

      for ( int x = -1; x <= 1; x++ )
      {
        for ( int y = -1; y <= 1; y++ )
        {
          if ( ( x == 0 && y == 0 ) || ! HasColor( position.X + x, position.Y + y ) )
          {
            continue;
          }

          var d = new Vector2( x, y ).Length();

          if ( d >= distance )
          {
            continue;
          }

          distance = d;
          closestPixel = _buffer.ComputeIndexFromPosition( position.X + x, position.Y + y );

          // var pixelColor = _buffer.GetAt( position.X + x, position.Y + y );
          // rawColor += new Vector3( pixelColor.R, pixelColor.G, pixelColor.B);
          // numColors ++;
        }
      }

      // rawColor /= numColors;

      var rawColor = _buffer.GetIndexed( closestPixel );

      

      return new Color( rawColor.R, rawColor.G, rawColor.B, pixel.A );
    }
  }

}