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

namespace FullScreenShaders.Components.Effects
{
    [AutoLoad]
    class TargetScaler : Component
    {
        const CONST_TV_RENDERSURFACEFORMAT SurfaceFormat = CONST_TV_RENDERSURFACEFORMAT.TV_TEXTUREFORMAT_X8R8G8B8;

        TVShader tentShader, kaiserShader, bicubicDownsamplingShader, bicubicInterpolationShader;
        TVRenderSurface downsamplingSurface, interpolationSurface, tempSurface;

        DownsamplingMode oldDownMode;
        InterpolationMode oldInterMode = InterpolationMode.Bilinear;
        int oldKaiserTaps = 8, oldDownFactor = 1;

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

        public override void Initialize()
        {
            Screen2DImmediate.Settings_SetClamping(true);

            tentShader = Scene.CreateShader();
            tentShader.CreateFromEffectFile("Tent 4x.fx");

            ReInitKaiser();

            Rendering.ViewportSizeChanged += () => 
            {
                tentShader.SetEffectParamVector2("texelSize", Rendering.TexelSize);
                tentShader.SetEffectParamFloat("downscaleFactor", SampleState.DownscaleFactor);

                kaiserShader.SetEffectParamVector2("texelSize", Rendering.TexelSize);
                kaiserShader.SetEffectParamFloat("downscaleFactor", SampleState.DownscaleFactor);

                if (bicubicDownsamplingShader != null)
                {
                    bicubicDownsamplingShader.SetEffectParamVector2("texelSize", Rendering.TexelSize);
                    bicubicDownsamplingShader.SetEffectParamFloat("downscaleFactor", SampleState.DownscaleFactor);
                }

                if (bicubicInterpolationShader != null)
                {
                    bicubicInterpolationShader.SetEffectParamVector2("texelSize", Rendering.TexelSize);
                    bicubicInterpolationShader.SetEffectParamFloat("downscaleFactor", SampleState.DownscaleFactor);
                }
            };

            base.Initialize();
        }

        void ReInitKaiser()
        {
            if (kaiserShader != null)
                kaiserShader.Destroy();

            kaiserShader = Scene.CreateShader();
            kaiserShader.AddDefine("TAPS", Convert.ToString(SampleState.KaiserTaps));
            if (SampleState.KaiserTaps < 16)
                kaiserShader.CreateFromEffectFile("Downsampling LQ.fx");
            else
                kaiserShader.CreateFromEffectFile("Downsampling HQ.fx");

            float[] weights = KaiserKernel.GetWeights(SampleState.KaiserTaps);

            switch (SampleState.KaiserTaps)
            {
                case 4:
                    kaiserShader.SetEffectParamVector4("tapWeights", new TV_4DVECTOR(weights[0], weights[0], weights[1], weights[1]));
                    break;
                case 8:
                    kaiserShader.SetEffectParamVector4("tapWeights", new TV_4DVECTOR(weights[0], weights[1], weights[2], weights[3]));
                    break;

                default:
                    var weightGroups = new TV_4DVECTOR[weights.Length / 4];
                    for (int i = 0; i < weightGroups.Length; i++)
                        for (int j = 0; j < 4; j++)
                            weightGroups[i] = new TV_4DVECTOR(weights[i * 4], weights[i * 4 + 1], weights[i * 4 + 2], weights[i * 4 + 3]);

                    kaiserShader.SetEffectParamVectorArray4("tapWeights", weightGroups, weightGroups.Length);
                    break;
            }
        }

        public override void Update(TimeSpan elapsedTime)
        {
            if (oldKaiserTaps != SampleState.KaiserTaps)
            {
                ReInitKaiser();
                oldKaiserTaps = SampleState.KaiserTaps;
            }

            if (oldDownMode != SampleState.DownsamplingMode)
            {
                if (SampleState.DownsamplingMode == DownsamplingMode.Bicubic)
                    ReInitBicubicDownsampling();

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

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

                if (SampleState.DownsamplingMode.DoublePass())
                    tempSurface = Scene.CreateRenderSurfaceEx(-1, -1, SurfaceFormat, false, true);

                switch(SampleState.DownsamplingMode)
                {
                    case DownsamplingMode.DoubleBlit:
                        downsamplingSurface = Scene.CreateRenderSurfaceEx(-1, -1, SurfaceFormat, false, true, 0.5f);
                        break;

                    case DownsamplingMode.Tent:
                        downsamplingSurface = Scene.CreateRenderSurfaceEx(-1, -1, SurfaceFormat, false, true);
                        break;

                    case DownsamplingMode.Kaiser:
                    case DownsamplingMode.Bicubic:
                        downsamplingSurface = Scene.CreateRenderSurfaceEx(-1, -1, SurfaceFormat, false, true);
                        break;
                }

                oldDownMode = SampleState.DownsamplingMode;
            }

            if (SampleState.DownscaleFactor != oldDownFactor)
            {
                if (SampleState.DownsamplingMode == DownsamplingMode.Bicubic && SampleState.DownscaleFactor > 1)
                    ReInitBicubicDownsampling();

                if (SampleState.InterpolationMode == InterpolationMode.Bicubic && SampleState.DownscaleFactor > 1)
                    ReInitBicubicInterpolation();

                oldDownFactor = SampleState.DownscaleFactor;
            }

            if (oldInterMode != SampleState.InterpolationMode)
            {
                switch (SampleState.InterpolationMode)
                {
                    case InterpolationMode.Nearest:
                    case InterpolationMode.Bilinear:
                        if (interpolationSurface != null)
                            interpolationSurface.Destroy();
                        interpolationSurface = null;
                        break;

                    case InterpolationMode.Bicubic:
                        ReInitBicubicInterpolation();
                        interpolationSurface = Scene.CreateRenderSurfaceEx(-1, -1, SurfaceFormat, false, true);
                        break;
                }

                oldInterMode = SampleState.InterpolationMode;
            }
        }

        void ReInitBicubicDownsampling()
        {
            if (bicubicDownsamplingShader != null)
                bicubicDownsamplingShader.Destroy();

            bicubicDownsamplingShader = Scene.CreateShader();
            bicubicDownsamplingShader.AddDefine("TAPS", Convert.ToString(SampleState.DownscaleFactor * 2));
            bicubicDownsamplingShader.CreateFromEffectFile("Downsampling LQ.fx");

            bicubicDownsamplingShader.SetEffectParamVector2("texelSize", Rendering.TexelSize);
            bicubicDownsamplingShader.SetEffectParamFloat("downscaleFactor", SampleState.DownscaleFactor);

            float[] weights = BicubicKernel.GetWeights(SampleState.DownscaleFactor);

            switch (SampleState.DownscaleFactor)
            {
                case 2:
                    bicubicDownsamplingShader.SetEffectParamVector4("tapWeights", new TV_4DVECTOR(weights[0], weights[0], weights[1], weights[1]));
                    break;
                case 4:
                    bicubicDownsamplingShader.SetEffectParamVector4("tapWeights", new TV_4DVECTOR(weights[0], weights[1], weights[2], weights[3]));
                    break;
            }
        }

        void ReInitBicubicInterpolation()
        {
            if (bicubicInterpolationShader != null)
                bicubicInterpolationShader.Destroy();

            bicubicInterpolationShader = Scene.CreateShader();
            bicubicInterpolationShader.AddDefine("TAPS", Convert.ToString(SampleState.DownscaleFactor * 2));
            bicubicInterpolationShader.CreateFromEffectFile("Interpolation.fx");

            bicubicInterpolationShader.SetEffectParamVector2("texelSize", Rendering.TexelSize);
            bicubicInterpolationShader.SetEffectParamFloat("downscaleFactor", SampleState.DownscaleFactor);

            float[] weights = BicubicKernel.GetWeights(SampleState.DownscaleFactor);

            switch (SampleState.DownscaleFactor)
            {
                case 2:
                    bicubicInterpolationShader.SetEffectParamVector4("tapWeights", new TV_4DVECTOR(weights[0], weights[0], weights[1], weights[1]));
                    break;
                case 4:
                    bicubicInterpolationShader.SetEffectParamVector4("tapWeights", new TV_4DVECTOR(weights[0], weights[1], weights[2], weights[3]));
                    break;
            }
        }

        public override void PreDraw()
        {
            TVShader downsamplingShader = null;
            switch (SampleState.DownsamplingMode)
            {
                case DownsamplingMode.Bicubic: downsamplingShader = bicubicDownsamplingShader; break;
                case DownsamplingMode.Kaiser: downsamplingShader = kaiserShader; break;
            }

            switch (SampleState.DownsamplingMode)
            {
                case DownsamplingMode.Blit:
                    Rendering.PostSurface.BltFromRenderSurface(Rendering.TempSurface);
                    break;

                case DownsamplingMode.DoubleBlit:
                    downsamplingSurface.BltFromRenderSurface(Rendering.TempSurface);
                    Rendering.PostSurface.BltFromRenderSurface(downsamplingSurface);
                    break;

                case DownsamplingMode.Tent:
                    Screen2DImmediate.Settings_SetTextureFilter(CONST_TV_TEXTUREFILTER.TV_FILTER_BILINEAR);

                    Rendering.PostSurface.StartRender();
                    Screen2DImmediate.Draw_FullscreenQuadWithShader(tentShader, 0, 0, 1, 1, downsamplingSurface.GetTexture());
                    Rendering.PostSurface.EndRender();
                    break;

                case DownsamplingMode.Kaiser:
                case DownsamplingMode.Bicubic:
                    Screen2DImmediate.Settings_SetTextureFilter(CONST_TV_TEXTUREFILTER.TV_FILTER_NEARESTPOINT);

                    downsamplingShader.SetPass(ConvolutionPass.Horizontal);
                    downsamplingSurface.StartRender();
                    Screen2DImmediate.Draw_FullscreenQuadWithShader(downsamplingShader, 0, 0, 1, 1, tempSurface.GetTexture());
                    downsamplingSurface.EndRender();

                    downsamplingShader.SetPass(ConvolutionPass.Vertical);
                    Rendering.PostSurface.StartRender();
                    Screen2DImmediate.Draw_FullscreenQuadWithShader(downsamplingShader, 0, 0, 1, 1, downsamplingSurface.GetTexture());
                    Rendering.PostSurface.EndRender();
                    break;
            }
        }

        public override void PostDraw()
        {
            if (SampleState.Downscaling && Rendering.TempSurface != null)
            {
                switch(SampleState.DownsamplingMode)
                {
                    case DownsamplingMode.Blit:
                    case DownsamplingMode.DoubleBlit:     
                        Rendering.TempSurface.BltFromMainBuffer(); 
                        break;

                    case DownsamplingMode.Tent:     
                        downsamplingSurface.BltFromMainBuffer(); 
                        break;

                    case DownsamplingMode.Kaiser:
                    case DownsamplingMode.Bicubic:   
                        tempSurface.BltFromMainBuffer(); 
                        break;
                }

                Screen2DImmediate.Settings_SetTextureFilter(
                    SampleState.InterpolationMode == InterpolationMode.Bilinear ?
                    CONST_TV_TEXTUREFILTER.TV_FILTER_BILINEAR :
                    CONST_TV_TEXTUREFILTER.TV_FILTER_NEARESTPOINT);

                switch(SampleState.InterpolationMode)
                {
                    case InterpolationMode.Nearest:
                    case InterpolationMode.Bilinear:
                        Screen2DImmediate.Draw_Texture(Rendering.PostSurface.GetTexture(), 0, 0,
                            Rendering.ViewportSize.x * SampleState.DownscaleFactor - 1,
                            Rendering.ViewportSize.y * SampleState.DownscaleFactor - 1);
                        break;

                    case InterpolationMode.Bicubic:
                        //Screen2DImmediate.Settings_SetTextureFilter(CONST_TV_TEXTUREFILTER.TV_FILTER_BILINEAR);

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

                        bicubicInterpolationShader.SetPass(ConvolutionPass.Vertical);
                        Screen2DImmediate.Draw_FullscreenQuadWithShader(bicubicInterpolationShader, 0, 0, 1, 1, interpolationSurface.GetTexture());
                        break;
                }
            }
        }

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