using Godot;
using System.Collections.Generic;

namespace Rokojori
{  
  public static class ColorX 
  {
    public static float GetColorScale( this Color c )
    {
      return Mathf.Max( 1f, Mathf.Max( c.R, Mathf.Max( c.G, c.B ) ) );
    }

    public static Color ScaleColor( this Color c, float scale )
    {
      return new Color( c.R * scale, c.G * scale, c.B * scale, c.A );
    }

    public static HSLColor ToHSL( this Color c, float scale = 1f )
    {
      return HSLColor.FromRGBA( c.ScaleColor( 1f / scale ) );
    }

  
    public static float r( this Vector4 rgba )
    {
      return rgba.X;
    }

    public static Vector3 rgb( this Vector4 rgba )
    {
      return new Vector3( rgba.X, rgba.Y, rgba.Z );
    }

    public static float GetChannel( this Color color, ColorChannelType colorChannelType )
    {
      if ( ColorChannelType.Red == colorChannelType )
      {
        return color.R;
      }

      if ( ColorChannelType.Green == colorChannelType )
      {
        return color.G;
      }

      if ( ColorChannelType.Blue == colorChannelType )
      {
        return color.B;
      }

      if ( ColorChannelType.Alpha == colorChannelType )
      {
        return color.A;
      }

      var hsl = HSLColor.FromRGBA( color );

      
      if ( ColorChannelType.Hue == colorChannelType )
      {
        return hsl.h;
      }

      if ( ColorChannelType.Saturation == colorChannelType )
      {
        return hsl.s;
      }

      if ( ColorChannelType.Luminance == colorChannelType )
      {
        return hsl.l;
      }

      return 0;

    }

    public static Color SetChannel( this Color color, ColorChannelType colorChannelType, float value )
    {
      if ( ColorChannelType.Red == colorChannelType )
      {
        color.R = value;
      }

      if ( ColorChannelType.Green == colorChannelType )
      {
        color.G = value;
      }

      if ( ColorChannelType.Blue == colorChannelType )
      {
        color.B = value;
      }

      if ( ColorChannelType.Alpha == colorChannelType )
      {
        color.A = value;
      }

      var hsl = HSLColor.FromRGBA( color );

      
      if ( ColorChannelType.Hue == colorChannelType )
      {
        hsl.h = value;
        color = hsl;
      }

      if ( ColorChannelType.Saturation == colorChannelType )
      {
        hsl.s = value;
        color = hsl;
      }

      if ( ColorChannelType.Luminance == colorChannelType )
      {
        hsl.l = value;
        color = hsl;
      }

      return color;
    }


    public static float SRGBtoLinear( float value )
    {
      return value < 0.04045f ? value * ( 1.0f / 12.92f ) : 
                                Mathf.Pow( ( value + 0.055f) * ( 1f / ( 1f + 0.055f ) ), 2.4f );
    }

    public static Color SRGBtoLinear( this Color color )
    {
      return new Color(
				SRGBtoLinear( color.R ),
				SRGBtoLinear( color.G ),
				SRGBtoLinear( color.B ),
				color.A
      );
    }

    public static float LinearToSRGB( float value )
    {
      return value < 0.0031308f ? 12.92f * value : 
                                  ( 1.0f + 0.055f ) * Mathf.Pow( value, 1.0f / 2.4f ) - 0.055f;
    }

    public static Color LinearToSRGB( this Color color )
    {
      return new Color(
				LinearToSRGB( color.R ),
				LinearToSRGB( color.G ),
				LinearToSRGB( color.B ),
				color.A
      );
    }


    public static Color From( List<float> floats, float basis = 1.0f )
    {
      if ( floats.Count == 1 )
      {
        var grey = floats[ 0 ] / basis;
        return new Color( grey, grey, grey, 1 );
      }

      if ( floats.Count == 2 )
      {
        var grey = floats[ 0 ] / basis;
        var alpha = floats[ 1 ] / basis;
        return new Color( grey, grey, grey, alpha );
      }

      if ( floats.Count == 3 )
      {
        var r = floats[ 0 ] / basis;
        var g = floats[ 1 ] / basis;
        var b = floats[ 2 ] / basis;

        return new Color( r, g, b, 1 );
      }

      if ( floats.Count == 4 )
      {
        var r = floats[ 0 ] / basis;
        var g = floats[ 1 ] / basis;
        var b = floats[ 2 ] / basis;
        var alpha = floats[ 3 ] / basis;

        return new Color( r, g, b, alpha );
      }

      return new Color( 0, 0, 0, 0 );
    }

    public static Color UnblendBlack( this Color c, float treshold = 0 )
    {
      if ( c.A <= treshold )
      {
        return c;
      }

      return new Color( c.R / c.A, c.G / c.A, c.B / c.A, c.A );
    }

    public static float AlphaMultipliedR( this Color c )
    {
      return c.R * c.A;
    }

    public static float AlphaMultipliedG( this Color c )
    {
      return c.G * c.A;
    }

    public static float AlphaMultipliedB( this Color c )
    {
      return c.B * c.A;
    } 

    public static float pmR( this Color c ) => c.AlphaMultipliedR();
    public static float pmG( this Color c ) => c.AlphaMultipliedG();
    public static float pmB( this Color c ) => c.AlphaMultipliedB();
    public static Color PreMultiply( this Color c )
    {
      return new Color( c.pmR(), c.pmG(), c.pmB(), c.A );
    }
    
    static float Clamp( float f )
    {
      return MathX.Clamp01( f ); 
    }

    public static Color Gamma( this Color color, float gamma )
    {
      return new Color(
        Mathf.Pow( color.R, gamma ),
        Mathf.Pow( color.G, gamma ),
        Mathf.Pow( color.B, gamma ),
        color.A 
      );
    }

    public static Vector3 ToVector3( this Color color )
    {
      return new Vector3( color.R, color.G, color.B );
    }

    public static Vector4 ToVector4( this Color color )
    {
      return new Vector4( color.R, color.G, color.B, color.A );
    }

    public static Color ToColor( this Vector4 color )
    {
      return new Color( color.X, color.Y, color.Z, color.W );
    }

    public static Color ToColor( this Vector3 color )
    {
      return new Color( color.X, color.Y, color.Z, 1f );
    }

    public static Color ToColor( this Vector3 vec, float alpha = 1 )
    {
      return new Color( vec.X, vec.Y, vec.Z, alpha);
    }

    public static Color ToColor( this Vector2 vec, float b = 0, float alpha = 1 )
    {
      return new Color( vec.X, vec.Y, b, alpha);
    }
   

    public static Color Blend( Color bottom, Color top )
    {
      var fade = ( 1f - top.A );
      var a = top.A + bottom.A * fade;

      var r = top.pmR() + bottom.pmR() * fade;
      var g = top.pmG() + bottom.pmG() * fade;
      var b = top.pmB() + bottom.pmB() * fade;
      
      return new Color( r, g, b, 1 ) / a;
    }

    public static Color PreMultipliedSingleComponentBlendMode( Color bottom, Color top, System.Func<float,float,float, float> blending )
    {
      var fade = ( 1f - top.A );
      var a = top.A + bottom.A * fade;

      var r = blending( bottom.pmR(), top.pmR(), fade );
      var g = blending( bottom.pmG(), top.pmG(), fade );
      var b = blending( bottom.pmB(), top.pmB(), fade );
      
      return new Color( r, g, b, 1 ) / a;
    }

    public static Color PreMultipliedRGBBlendMode( Color bottom, Color top, System.Func<Color,Color,float, Color> blending )
    {
      var fade = ( 1f - top.A );
      var a = top.A + bottom.A * fade;

      var rgb = blending( bottom.PreMultiply(), top.PreMultiply(), fade );
      
      return new Color( rgb.R, rgb.G, rgb.B, 1 ) / a;
    }

    

    public static Color BlendMultiply( Color bottom, Color top )
    {
      return PreMultipliedSingleComponentBlendMode( bottom, top, 
      ( a, b, f)=>
      {
        return a * b;
      }
      );
    }

    public static Color BlendScreen( Color bottom, Color top )
    {
      return PreMultipliedSingleComponentBlendMode( bottom, top, 
      ( a, b, f)=>
      {
        return 1f - ( 1f - a ) * ( 1f - b);
      }
      );
    }

    public static Color BlendOverlay( Color bottom, Color top )
    {
      return PreMultipliedSingleComponentBlendMode( bottom, top, 
      ( a, b, f)=>
      {
        return a < 0.5f ? (2.0f * a * b) : (1.0f - 2.0f * (1.0f - a) * (1.0f - b)); 
      }
      );
    }

    public static Color BlendHardLight( Color bottom, Color top )
    {
      return PreMultipliedSingleComponentBlendMode( bottom, top, 
      ( a, b, f)=>
      {
        return b < 0.5f ? (2.0f * a * b) : (1.0f - 2.0f * (1.0f - a) * (1.0f - b)) ; 
      }
      );
    }

    public static Color BlendSoftLight( Color bottom, Color top )
    {
      return PreMultipliedSingleComponentBlendMode( bottom, top, 
      ( a, b, f)=>
      {
        return b < 0.5f ? (2.0f * a * b) : (1.0f - 2.0f * (1.0f - a) * (1.0f - b)) ; 
      }
      );
    }

    public static Color BlendColor( Color bottom, Color top )
    {
      var hslBottom = HSLColor.FromRGBA( bottom );
      var hslTop = HSLColor.FromRGBA( top );

      var combined = bottom.A == 0 ? bottom : (Color)( new HSLColor( hslTop.h, hslTop.s, hslBottom.l, top.A ) );

      return ColorX.Blend( bottom, combined );
    }

    public static Color BlendHue( Color bottom, Color top )
    {
      var hslA = HSLColor.FromRGBA( bottom );
      var hslB = HSLColor.FromRGBA( top );

      var combined = new HSLColor( hslB.h, hslA.s, hslA.l, bottom.A );

      return ColorX.Lerp( bottom, combined, top.A );
    }

    public static Color BlendSaturation( Color bottom, Color top )
    {
      var hslA = HSLColor.FromRGBA( bottom );
      var hslB = HSLColor.FromRGBA( top );

      var combined = new HSLColor( hslB.h, hslA.s, hslA.l, bottom.A );

      return ColorX.Lerp( bottom, combined, top.A );
    }

    public static Color BlendAdd( Color bottom, Color top )
    {
      return PreMultipliedSingleComponentBlendMode( bottom, top, 
      ( b, t, f)=>
      {
        return b + t;
      }
      );
    }

    

    public static Color Lerp( Color a, Color b, float amount )
    {
      return a.Lerp( b, amount ); 
    }

    public static Vector3 RGB( Color c )
    {
      return new Vector3( c.R, c.G, c.B );
    }

    public static Vector2 RA( Color c )
    {
      return new Vector2( c.R, c.A);
    }

    public static Color Create( Vector3 rgb, float a )
    {
      return new Color( rgb.X, rgb.Y, rgb.Z, a );
    }

    public static Color FromVector4( Vector4 c )
    {
      return new Color( c.X, c.Y, c.Z, c.W );
    }

    public static Color Fade( Color color, float fade )
    {
      color.A = color.A * fade;
      return color;
    }

    public static Color FadeAlpha( this Color color, float fade )
    {
      color.A = color.A * fade;
      return color;
    }

    public static Color SetAlpha( Color color, float alpha )
    {
      color.A = alpha;
      return color;
    }
  
  }
}