using Godot;
using System.Collections.Generic;

namespace Rokojori
{  
  [Tool]
  [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/FlareVFX.svg") ]
  public partial class FlareLayer:Resource
  {    
    [Export]
    public string layerName = "";

    [Export]
    public bool solo = false;

    [Export]
    public bool hide = false;


    [Export( PropertyHint.Range, "0,1" )]
    public float opacity = 1f;

    [Export]
    public int numLayers = 1;

    [Export]
    public FlareType flareType;

    [ExportGroup("Color/Overwrite")]
    [Export]
    public Color overwriteColor = Colors.White;

    [Export( PropertyHint.Range, "0,1" )]
    public float overwriteAmount = 0f;

    [ExportGroup("Color")]

    [Export( PropertyHint.Range, "-180,180" )]
    public float minHueRandom = 0;

    [Export( PropertyHint.Range, "-180,180" )]
    public float maxHueRandom = 0;

    [Export]
    public CurveTexture opacityCurvePerLayer = null;

    [Export]
    public Color tint = Colors.White;

    [Export( PropertyHint.Range, "-180,180" )]
    public float hueShift = 0;

    [Export( PropertyHint.Range, "-180,180" )]
    public float temparatureShift = 0;

    [Export( PropertyHint.Range, "-100,100" )]
    public float saturationShift = 0;

    [Export( PropertyHint.Range, "-100,100" )]
    public float lighntessShift = 0;


    [ExportGroup("Size")]
    [Export]
    public float size = 1;

    [Export( PropertyHint.Range, "0,1" )]
    public float sizeScale = 1;

    [Export( PropertyHint.Range, "0,1" )]
    public float sizeXScale = 1f;

    [Export( PropertyHint.Range, "0,1" )]
    public float sizeYScale = 1f;

    [Export( PropertyHint.Range, "0,2" )]
    public float worldSizeScale = 1f;

    [Export( PropertyHint.Range, "0,2" )]
    public float screenSizeScale = 1f;

    [Export( PropertyHint.Range, "0,1" )]
    public float worldSize_vs_screenSize = 1f;

    [Export( PropertyHint.Range, "0,2" )]
    public float randomScaleMin = 1f;

    [Export( PropertyHint.Range, "0,2" )]
    public float randomScaleMax = 1f;

    [Export]
    public bool overwriteExtension = false;

    [Export( PropertyHint.Range, "1,10" )]
    public float extension = 1f;


    [ExportGroup("ScreenOffset")]

    [Export]
    public Vector2 screenOffset = Vector2.Zero;

    [Export( PropertyHint.Range, "0,1" )]
    public float screenOffsetStaticScale = 1f;

    [Export]
    public Vector2 screenOffsetStaticDirection = Vector2.Zero;

     [Export( PropertyHint.Range, "0,1" )]
    public float screenOffsetLayerScale = 1f;

    [Export]
    public Vector2 screenOffsetLayerDirection = Vector2.Zero;

    [ExportGroup("Rotation")]
    [Export( PropertyHint.Range, "-180,180" )]
    public float rotation = 0;

    [Export( PropertyHint.Range, "-180,180" )]
    public float rotationOverX = 0;

    [Export( PropertyHint.Range, "-180,180" )]
    public float rotationOverY = 0f;

    [Export( PropertyHint.Range, "-180,180" )]
    public float rotationPerLayer = 0f;

    [ExportGroup("Overwrites")]    
    [Export]
    public FlareFading fading = null;

    [Export]
    public FlareChromaticAberation chromaticAberation = null;

    [Export]
    public FlareOcclusion occlusion = null;

    [Export]
    public FlareBlendMode blendMode = null;

    [Export]
    public FlareStencilMode stencilMode = null;

    [Export]
    public int renderPriority = 0;


    public Data Create( FlareVFX vfx )
    {
      return new Data( vfx, this );
    }

    public class Data
    {
      public FlareLayer layer;
      public FlareVFX vfx;
      public MeshInstance3D meshInstance;
      public ShaderMaterial material;

      public FlareBlendMode.BlendModeType GetBlendMode()
      {
        return layer.blendMode != null ? layer.blendMode.blendMode : vfx.preset.blendMode;
      }

      public Data( FlareVFX vfx, FlareLayer layer )
      {
        this.vfx = vfx;
        this.layer = layer;
      }


      public void Update()
      { 
        if ( meshInstance == null || layer.flareType == null )
        {
          return;
        }

        EnsureMesh();
        EnsureMaterial();

        if ( material == null )
        {
          return;
        }

        layer.flareType.ApplyShape( this );


        ApplyColor();
        ApplySize();
        ApplyScreenOffset();
        ApplyRotation();
        ApplyFading();
        ApplyChromaticAberation();
        ApplyOcclusion();

        material.RenderPriority = layer.renderPriority;
        
      }

      void EnsureMesh()
      {       
        var arrayMesh = meshInstance.Mesh as ArrayMesh;

        if ( arrayMesh != null && arrayMesh.SurfaceGetArrayLen( 0 ) == layer.numLayers * 4 )
        {
          return;
        }

        var gen = new UnitBillboardQuadsGenerator();
        gen.num = layer.numLayers;
        meshInstance.Mesh = gen.Generate();
      }

      void EnsureMaterial()
      {
        var miMaterial = meshInstance.GetSurfaceOverrideMaterial( 0 ) as ShaderMaterial;
        
        if ( material != null && miMaterial != null && miMaterial.Shader == layer.flareType.GetShader( this ) )
        {
          return;
        }

        material = layer.flareType.CreateMaterial( this );
        meshInstance.SetSurfaceOverrideMaterial( 0, material );
      }

      void ApplyColor()
      {
        var preset = vfx.preset;
        var color = preset.mainColor;

        if ( layer.overwriteAmount > 0 )
        {
          color = color.Lerp( layer.overwriteColor, layer.overwriteAmount );
        }

        color *= layer.tint;

        var scale = color.GetColorScale();
        var hsl = color.ToHSL( scale );

        hsl.h += layer.hueShift + preset.hueShift;

        hsl.h = MathX.Repeat( hsl.h, 360f );

        hsl.h = HSLColor.ShiftTemparatureOfHue( hsl.h, preset.temparatureShift + layer.temparatureShift );
        hsl.s += ( layer.saturationShift + preset.saturationShift ) / 100f;
        hsl.l += ( layer.lighntessShift + preset.lightnessShift ) / 100f;

        hsl.ClampToLimits();

        color = hsl.ToRGBA( scale );

        // color = preset.mainColor; 
        BaseFlareShader.color.Set( material, color );
        BaseFlareShader.opacity.Set( material, preset.opacity * layer.opacity );
        BaseFlareShader.opacityCurve.Set( material, layer.opacityCurvePerLayer );
        BaseFlareShader.minHueRandom.Set( material, layer.minHueRandom / 360f );
        BaseFlareShader.maxHueRandom.Set( material, layer.maxHueRandom / 360f );      

      }

      void ApplySize()
      {
        var preset = vfx.preset;
        var size = Vector2.One * preset.scale;

        size *= layer.size * layer.sizeScale * new Vector2( layer.sizeXScale, layer.sizeYScale );
      
        BaseFlareShader.sizeX.Set( material, size.X );
        BaseFlareShader.sizeY.Set( material, size.Y );

        BaseFlareShader.worldSizeScale.Set( material, layer.worldSizeScale );
        BaseFlareShader.screenSizeScale.Set( material, layer.screenSizeScale );
        BaseFlareShader.worldSizeVsScreenSize.Set( material, layer.worldSize_vs_screenSize );

        BaseFlareShader.randomScaleMin.Set( material, layer.randomScaleMin );
        BaseFlareShader.randomScaleMax.Set( material, layer.randomScaleMax );

        
        BaseFlareShader.extension.Set( material, layer.overwriteExtension ? layer.extension : preset.extension );
      }

      void ApplyScreenOffset()
      {
        BaseFlareShader.screenOffset.Set( material, layer.screenOffset );
        BaseFlareShader.screenOffsetScale.Set( material, layer.screenOffsetStaticDirection * layer.screenOffsetStaticScale );
        BaseFlareShader.screenOffsetLayerSpread.Set( material, layer.screenOffsetLayerDirection * layer.screenOffsetLayerScale  );
      }

      void ApplyRotation()
      {
        BaseFlareShader.rotation.Set( material, layer.rotation );
        BaseFlareShader.rotationOverX.Set( material, layer.rotationOverX );
        BaseFlareShader.rotationOverY.Set( material, layer.rotationOverY );
        BaseFlareShader.rotationPerLayer.Set( material, layer.rotationPerLayer );

        // BaseFlareShader.vertexRotation.Set( material, layer.vertexRotation );
        // BaseFlareShader.vertexRotationOverX.Set( material, layer.vertexRotationOverX );
        // BaseFlareShader.vertexRotationOverY.Set( material, layer.vertexRotationOverY );
        // BaseFlareShader.vertexRotationPerLayer.Set( material, layer.vertexRotationPerLayer );
      }

      void ApplyFading()
      {
        var preset = vfx.preset;
        var fading = preset.fading;

        if ( layer.fading != null )
        {
          fading = layer.fading;
        }

        if ( fading == null )
        {
          BaseFlareShader.fadingMode.Set( material, 0 );
          return;
        }

        fading.ApplyFading( this );
      }

      void ApplyChromaticAberation()
      {
        var preset = vfx.preset;
        var chromaticAberation = preset.chromaticAberation;

        if ( layer.chromaticAberation != null )
        {
          chromaticAberation = layer.chromaticAberation;
        }

        if ( chromaticAberation == null )
        {
          BaseFlareShader.chromaticAberationMode.Set( material, 0 );
          return;
        }


        chromaticAberation.ApplyChromaticAberation( this );
      }


      void ApplyOcclusion()
      {
        var preset = vfx.preset;
        var occlusion = preset.occlusion;

        if ( layer.occlusion != null )
        {
          occlusion = layer.occlusion;
        }

        if ( occlusion == null )
        {
          BaseFlareShader.occlusionMode.Set( material, 0 );
          return;
        }

        occlusion.ApplyOcclusion( this );
      }
    }
   
  }
}