using System;
using ComponentFramework;
using ComponentFramework.Modules;
using FullScreenShaders.Services;
using MTV3D65;
using FullScreenShaders.Structure;

namespace FullScreenShaders.Components.Effects
{
    [AutoLoad]
    class BlurEffect : Component
    {
        TVShader blurShader;
        bool oldFast;
        int oldTaps = 7;
        double oldStDev = GaussianKernel.IdealStandardDeviation(7);

        public BlurEffect(ICore core) : base(core) { }

        public override void Initialize()
        {
            ReInitShader();

            Rendering.ViewportSizeChanged += () => blurShader.SetEffectParamVector2("texelSize", Rendering.TexelSize);

            base.Initialize();
        }

        void ReInitShader()
        {
            // Only 5, 9, 13 and 17 taps are supported in the fast shader
            int sampledTaps = SampleState.FastGaussian ?
                (int)Math.Ceiling((SampleState.GaussianTaps - 1) / 4.0) * 4 + 1 : 
                SampleState.GaussianTaps;

            if (blurShader != null)
                blurShader.Destroy();

            blurShader = Scene.CreateShader();

            blurShader.AddDefine("TAPS", Convert.ToString(sampledTaps));
            blurShader.CreateFromEffectFile(SampleState.FastGaussian ? "Fast Gaussian.fx" : "Gaussian Blur.fx");

            CalculateWeights();

            blurShader.SetEffectParamVector2("texelSize", Rendering.TexelSize);
        }

        void CalculateWeights()
        {
            float[] weights;

            if (SampleState.FastGaussian)
            {
                // Only 5, 9, 13 and 17 taps are supported in the fast shader
                int sampledTaps = SampleState.FastGaussian ?
                    (int)Math.Ceiling((SampleState.GaussianTaps - 1) / 4.0) * 4 + 1 :
                    SampleState.GaussianTaps;

                float[] offsets;
                GaussianKernel.GetFastWeights(sampledTaps, SampleState.GaussianStandardDeviation,
                    out weights, out offsets);

                if (sampledTaps != 5)
                    blurShader.SetEffectParamFloat("centerTapWeight", weights[0]);

                switch (sampledTaps)
                {
                    case 5:
                        blurShader.SetEffectParamVector3("tapWeights", new TV_3DVECTOR(weights[1], weights[0], weights[1]));
                        blurShader.SetEffectParamVector3("tapOffsets", new TV_3DVECTOR(-offsets[1], offsets[0], offsets[1]));
                        break;
                    case 9:
                        blurShader.SetEffectParamVector4("tapWeights", new TV_4DVECTOR(weights[2], weights[1], weights[1], weights[2]));
                        blurShader.SetEffectParamVector4("tapOffsets", new TV_4DVECTOR(-offsets[2], -offsets[1], offsets[1], offsets[2]));
                        break;
                    case 13:
                        blurShader.SetEffectParamVector3("tapWeights", new TV_3DVECTOR(weights[1], weights[2], weights[3]));
                        blurShader.SetEffectParamVector3("tapOffsets", new TV_3DVECTOR(offsets[1], offsets[2], offsets[3]));
                        break;
                    case 17:
                        blurShader.SetEffectParamVector4("tapWeights", new TV_4DVECTOR(weights[1], weights[2], weights[3], weights[4]));
                        blurShader.SetEffectParamVector4("tapOffsets", new TV_4DVECTOR(offsets[1], offsets[2], offsets[3], offsets[4]));
                        break;
                }
            }
            else
            {
                weights = GaussianKernel.GetWeights(SampleState.GaussianTaps, SampleState.GaussianStandardDeviation);
                blurShader.SetEffectParamFloat("centerTapWeight", weights[0]);
                switch (SampleState.GaussianTaps)
                {
                    case 5:
                        blurShader.SetEffectParamVector4("tapWeights", new TV_4DVECTOR(weights[2], weights[1], weights[1], weights[2]));
                        break;
                    case 7:
                        blurShader.SetEffectParamVector3("tapWeights", new TV_3DVECTOR(weights[1], weights[2], weights[3]));
                        break;
                    case 9:
                        blurShader.SetEffectParamVector4("tapWeights", new TV_4DVECTOR(weights[1], weights[2], weights[3], weights[4]));
                        break;
                }
            }
        }

        public override void Update(TimeSpan elapsedTime)
        {
            if (oldTaps != SampleState.GaussianTaps || oldFast != SampleState.FastGaussian)
            {
                ReInitShader();
                oldFast = SampleState.FastGaussian;
                oldTaps = SampleState.GaussianTaps;
            }
            else if (oldStDev != SampleState.GaussianStandardDeviation)
            {
                CalculateWeights();
                oldStDev = SampleState.GaussianStandardDeviation;
            }
        }

        public override void PreDraw()
        {
            if (SampleState.GaussianBlur && SampleState.Downscaling && Rendering.TempSurface != null)
            {
                Screen2DImmediate.Settings_SetTextureFilter(SampleState.FastGaussian ?
                    CONST_TV_TEXTUREFILTER.TV_FILTER_BILINEAR : CONST_TV_TEXTUREFILTER.TV_FILTER_NEARESTPOINT);

                blurShader.SetPass(ConvolutionPass.Horizontal);
                Rendering.TempSurface.BltFromRenderSurface(Rendering.PostSurface);
                Rendering.PostSurface.StartRender();
                Screen2DImmediate.Draw_FullscreenQuadWithShader(blurShader, 0, 0, 1, 1, Rendering.TempSurface.GetTexture());
                Rendering.PostSurface.EndRender();

                blurShader.SetPass(ConvolutionPass.Vertical);
                Rendering.TempSurface.BltFromRenderSurface(Rendering.PostSurface);
                Rendering.PostSurface.StartRender();
                Screen2DImmediate.Draw_FullscreenQuadWithShader(blurShader, 0, 0, 1, 1, Rendering.TempSurface.GetTexture());
                Rendering.PostSurface.EndRender();
            }
        }

        public override void PostDraw()
        {
            if (SampleState.GaussianBlur && !SampleState.Downscaling)
            {
                Screen2DImmediate.Settings_SetTextureFilter(SampleState.FastGaussian ?
                    CONST_TV_TEXTUREFILTER.TV_FILTER_BILINEAR : CONST_TV_TEXTUREFILTER.TV_FILTER_NEARESTPOINT);

                Rendering.PostSurface.BltFromMainBuffer();
                blurShader.SetPass(ConvolutionPass.Horizontal);
                Screen2DImmediate.Draw_FullscreenQuadWithShader(blurShader, 0, 0, 1, 1, Rendering.PostSurface.GetTexture());

                Rendering.PostSurface.BltFromMainBuffer();
                blurShader.SetPass(ConvolutionPass.Vertical);
                Screen2DImmediate.Draw_FullscreenQuadWithShader(blurShader, 0, 0, 1, 1, Rendering.PostSurface.GetTexture());
            }
        }

        public override int Order
        {
            get { return 30; }
        }

        [ServiceDependency]
        public IRendering Rendering { private get; set; }
        [ServiceDependency]
        public ISampleState SampleState { private get; set; }
    }
}
