#pragma once
///////////////////////////////////////////////////////////////////////////////////
//                  _            __  __                                 
//   __      ____ _| |_ ___ _ __|  \/  | ___ _ __ ___   ___  _ __ _   _ 
//   \ \ /\ / / _` | __/ _ \ '__| |\/| |/ _ \ '_ ` _ \ / _ \| '__| | | |
//    \ V  V / (_| | ||  __/ |  | |  | |  __/ | | | | | (_) | |  | |_| |
//     \_/\_/ \__,_|\__\___|_|  |_|  |_|\___|_| |_| |_|\___/|_|   \__, |
//                                                                |___/ 
//                                                     
//	 \brief Water memory stylization pipeline
//	 Contains the water memory stylization pipeline with all necessary targets and operations
//
//   Developed by: Oliver Vainum�e
//
///////////////////////////////////////////////////////////////////////////////////
#include "mnpr_renderer.h"


namespace wm {
    void addTargets(MRenderTargetList &targetList) {
        // add style specific targets

        unsigned int tWidth = targetList[0]->width();
        unsigned int tHeight = targetList[0]->height();
        int MSAA = targetList[0]->multiSampleCount();

        unsigned arraySliceCount = 0;
        bool isCubeMap = false;

        MHWRender::MRasterFormat rgba8 = MHWRender::kR8G8B8A8_SNORM;
        MHWRender::MRasterFormat rgb8 = MHWRender::kR8G8B8X8;
        MHWRender::MRasterFormat rgba16f = MHWRender::kR16G16B16A16_FLOAT;
        MHWRender::MRasterFormat rgba32f = MHWRender::kR32G32B32A32_FLOAT;
        MHWRender::MRasterFormat defaultUserDepth = MHWRender::kR16G16B16A16_SNORM;

        MHWRender::MRasterFormat diffuseDepth = targetList.getDescription("diffuseTarget")->rasterFormat();
        MHWRender::MRasterFormat colorDepth = targetList.getDescription("colorTarget")->rasterFormat();

        auto appendTarget = [&](const MString &name, ::MHWRender::MRasterFormat rasterFormat) -> void {
            targetList.append(MHWRender::MRenderTargetDescription(
                name, tWidth, tHeight, 1, rasterFormat, arraySliceCount, isCubeMap));
        };

        /*
        targetList.append(MHWRender::MRenderTargetDescription(
            "bleedingTarget", tWidth, tHeight, 1, rgba8, arraySliceCount, isCubeMap));
        */

        appendTarget("edgeThresholdedTarget", colorDepth);
        appendTarget("edgeTargetWM", colorDepth);
        appendTarget("blurredEdgeTarget", colorDepth);
        appendTarget("edgeGradientTarget", colorDepth);
        appendTarget("edgeUvTarget", colorDepth);
        appendTarget("edgeUvDilatedTarget", colorDepth);
        appendTarget("edgeWidthTarget", rgba16f);
        appendTarget("edgeUvDebugTarget", colorDepth);
        appendTarget("edgeResultTarget", colorDepth);
        appendTarget("edgeResultAverageTarget", colorDepth);

        appendTarget("discreteLightTarget", diffuseDepth);
        appendTarget("discreteShadingTarget", diffuseDepth);

        appendTarget("hatchingTarget", rgba8);

        appendTarget("colorSpreadTarget", colorDepth);

        appendTarget("widerDiffuseTarget", diffuseDepth);
        appendTarget("uvTarget", diffuseDepth);
        appendTarget("debugTarget", colorDepth);
    }


    void addOperations(MHWRender::MRenderOperationList &mOperations,
                       MRenderTargetList &mRenderTargets,
                       EngineSettings &mEngSettings,
                       FXParameters &mFxParams) {
        MString opName = "";
        MOperationShader* opShader;
        QuadRender* quadOp;


        // pretty sure this can be further improved so that
        // it would not require opName and opShader as arguments.
        // Therefore, there's probably a similar solution to opShader parameter adding too.
        auto appendOp = [&](std::vector<MString> t_targetDescNames) -> void {
            mOperations.append(new QuadRender(
                opName,
                MHWRender::MClearOperation::kClearNone,
                mRenderTargets,
                *opShader));

            mRenderTargets.setOperationOutputs(opName, t_targetDescNames);
        };


        opName = "[quad] widen diffuse target";
        opShader = new MOperationShader("quadLighting", "includeNegatives");
        opShader->addTargetParameter("gDiffuseTex", mRenderTargets.getTarget("diffuseTarget"));
        opShader->addParameter("positiveScale", mFxParams.testingValue);
        appendOp({ "widerDiffuseTarget" });


        opName = "[quad edge thresholding";
        opShader = new MOperationShader("quadEdgeManipulation", "thresholdEdges");
        opShader->addTargetParameter("gEdgeTex", mRenderTargets.getTarget("edgeTarget"));
        opShader->addParameter("gEdgeThreshold", mFxParams.edgeThresholdWM);
        appendOp({ "edgeThresholdedTarget" });


        opName = "[quad edge intensity control";
        opShader = new MOperationShader("wm", "quadEdgeIntensity", "edgeIntensity");
        opShader->addSamplerState("gSampler", MHWRender::MSamplerState::kTexClamp, MHWRender::MSamplerState::kMinMagMipPoint);
        opShader->addTargetParameter("gEdgeTex", mRenderTargets.getTarget("edgeThresholdedTarget"));
        opShader->addTargetParameter("gDepthTex", mRenderTargets.getTarget("linearDepth"));
        opShader->addTargetParameter("gEdgeCtrlTex", mRenderTargets.getTarget("edgeCtrlTarget"));
        appendOp({ "edgeTargetWM" });


        opName = "[quad] separable H";
        opShader = new MOperationShader("wm", "quadSeparable", "blurH");
        opShader->addSamplerState("gSampler", MHWRender::MSamplerState::kTexClamp, MHWRender::MSamplerState::kMinMagMipPoint);
        opShader->addTargetParameter("gEdgeTex", mRenderTargets.getTarget("edgeTargetWM"));
        opShader->addTargetParameter("gDepthTex", mRenderTargets.getTarget("linearDepth"));
        opShader->addTargetParameter("gEdgeCtrlTex", mRenderTargets.getTarget("edgeCtrlTarget"));
        appendOp({ "blurredEdgeTarget", "edgeCtrlTarget" });


        opName = "[quad] separable V";
        opShader = new MOperationShader("wm", "quadSeparable", "blurV");
        opShader->addSamplerState("gSampler", MHWRender::MSamplerState::kTexClamp, MHWRender::MSamplerState::kMinMagMipPoint);
        opShader->addTargetParameter("gEdgeTex", mRenderTargets.getTarget("blurredEdgeTarget"));
        opShader->addTargetParameter("gDepthTex", mRenderTargets.getTarget("linearDepth"));
        opShader->addTargetParameter("gEdgeCtrlTex", mRenderTargets.getTarget("edgeCtrlTarget"));
        appendOp({ "blurredEdgeTarget", "edgeCtrlTarget" });


        opName = "[quad] edge gradient";
        opShader = new MOperationShader("quadGradientFinding", "gradientTowardsEdge");
        opShader->addSamplerState("gSampler", MHWRender::MSamplerState::kTexClamp, MHWRender::MSamplerState::kMinMagMipPoint);
        opShader->addTargetParameter("gValueTex", mRenderTargets.getTarget("blurredEdgeTarget"));
        /*opShader->addParameter("gChannel", std::vector<float>{ 0.01f });*/
        appendOp({ "edgeGradientTarget" });


        opName = "[quad] closest edge uv location";
        opShader = new MOperationShader("wm", "quadEdgeManipulation", "edgeUvLocations");
        opShader->addSamplerState("gSampler", MHWRender::MSamplerState::kTexClamp, MHWRender::MSamplerState::kMinMagMipPoint);
        opShader->addTargetParameter("gEdgeTex", mRenderTargets.getTarget("edgeTargetWM"));
        opShader->addTargetParameter("gBlurredEdgeTex", mRenderTargets.getTarget("blurredEdgeTarget"));
        opShader->addTargetParameter("gGradientTex", mRenderTargets.getTarget("edgeGradientTarget"));
        appendOp({ "edgeUvTarget" });


        opName = "[quad] dilate closest edge uv location";
        opShader = new MOperationShader("wm", "quadEdgeManipulation", "dilateEdgeLocations");
        opShader->addTargetParameter("gEdgeLocationTex", mRenderTargets.getTarget("edgeUvTarget"));
        appendOp({ "edgeUvDilatedTarget" });


        opName = "[quad] edge width";
        opShader = new MOperationShader("wm", "quadEdgeManipulation", "removeFarPixels");
        opShader->addTargetParameter("gEdgeLocationTex", mRenderTargets.getTarget("edgeUvDilatedTarget"));
        opShader->addTargetParameter("gControlTex", mRenderTargets.getTarget("edgeCtrlTarget"));
        opShader->addParameter("gEdgeWidth", mFxParams.edgeWidthWM);
        appendOp({ "edgeWidthTarget" });


        opName = "[quad] edge uv location debug";
        opShader = new MOperationShader("wm", "quadEdgeManipulation", "edgeLocationDebug");
        opShader->addTargetParameter("gEdgeLocationTex", mRenderTargets.getTarget("edgeWidthTarget"));
        appendOp({ "edgeUvDebugTarget" });


        opName = "[quad] edge intensity";
        opShader = new MOperationShader("wm", "quadEdgeManipulation", "edgeIntensity");
        opShader->addTargetParameter("gEdgeLocationTex", mRenderTargets.getTarget("edgeWidthTarget"));
        opShader->addTargetParameter("gControlTex", mRenderTargets.getTarget("edgeCtrlTarget"));
        opShader->addParameter("gEdgeIntensity", mFxParams.edgeIntensityWM);
        appendOp({ "edgeResultTarget" });


        opName = "[quad] edge average";
        opShader = new MOperationShader("wm", "quadEdgeManipulation", "averageEdgeIntensity");
        opShader->addTargetParameter("gEdgeTex", mRenderTargets.getTarget("edgeResultTarget"));
        appendOp({ "edgeResultAverageTarget" });


        opName = "[quad] discrete light";
        opShader = new MOperationShader("quadCelShader", "discreteLight");
        opShader->addTargetParameter("gSpecularTex", mRenderTargets.getTarget("specularTarget"));
        opShader->addTargetParameter("gDiffuseTex", mRenderTargets.getTarget("widerDiffuseTarget"));
        opShader->addParameter("gSurfaceThresholdHigh", mFxParams.surfaceThresholdHigh);
        opShader->addParameter("gSurfaceThresholdMid", mFxParams.surfaceThresholdMid);
        opShader->addParameter("gTransitionHighMid", mFxParams.transitionHighMid);
        opShader->addParameter("gTransitionMidLow", mFxParams.transitionMidLow);
        opShader->addParameter("gSurfaceHighIntensity", mFxParams.surfaceHighIntensity);
        opShader->addParameter("gSurfaceMidIntensity", mFxParams.surfaceMidIntensity);
        opShader->addParameter("gSurfaceLowIntensity", mFxParams.surfaceLowIntensity);
        opShader->addParameter("gDiffuseCoefficient", mFxParams.diffuseCoefficient);
        opShader->addParameter("gSpecularCoefficient", mFxParams.specularCoefficient);
        opShader->addParameter("gSpecularPower", mFxParams.specularPower);
        appendOp({ "discreteLightTarget" });


        opName = "[quad] discrete surfaces";
        opShader = new MOperationShader("quadCelShader", "shadeSurfaces");
        opShader->addTargetParameter("gColorTex", mRenderTargets.getTarget("stylizationTarget"));
        opShader->addTargetParameter("gLightTex", mRenderTargets.getTarget("discreteLightTarget"));
        opShader->addParameter("gSurfaceThresholdHigh", mFxParams.surfaceThresholdHigh);
        opShader->addParameter("gSurfaceThresholdMid", mFxParams.surfaceThresholdMid);
        opShader->addParameter("gTransitionHighMid", mFxParams.transitionHighMid);
        opShader->addParameter("gTransitionMidLow", mFxParams.transitionMidLow);
        opShader->addParameter("gSurfaceHighIntensity", mFxParams.surfaceHighIntensity);
        opShader->addParameter("gSurfaceMidIntensity", mFxParams.surfaceMidIntensity);
        opShader->addParameter("gSurfaceLowIntensity", mFxParams.surfaceLowIntensity);
        opShader->addParameter("gDiffuseCoefficient", mFxParams.diffuseCoefficient);
        opShader->addParameter("gSpecularCoefficient", mFxParams.specularCoefficient);
        opShader->addParameter("gSpecularPower", mFxParams.specularPower);
        appendOp({ "discreteShadingTarget" });


        opName = "[quad] cel shading surfaces";
        opShader = new MOperationShader("quadCelShader", "celSurfaces1");
        opShader->addTargetParameter("gColorTex", mRenderTargets.getTarget("stylizationTarget"));
        //opShader->addTargetParameter("gColorTex", mRenderTargets.target(0));
        opShader->addTargetParameter("gDepthTex", mRenderTargets.getTarget("linearDepth"));
        // opShader->addTargetParameter("gNormalTex", mRenderTargets.getTarget("normalsTarget"));
        opShader->addTargetParameter("gSpecularTex", mRenderTargets.getTarget("specularTarget"));
        opShader->addTargetParameter("gDiffuseTex", mRenderTargets.getTarget("widerDiffuseTarget"));
        opShader->addParameter("gSurfaceThresholdHigh", mFxParams.surfaceThresholdHigh);
        opShader->addParameter("gSurfaceThresholdMid", mFxParams.surfaceThresholdMid);
        opShader->addParameter("gTransitionHighMid", mFxParams.transitionHighMid);
        opShader->addParameter("gTransitionMidLow", mFxParams.transitionMidLow);
        opShader->addParameter("gSurfaceHighIntensity", mFxParams.surfaceHighIntensity);
        opShader->addParameter("gSurfaceMidIntensity", mFxParams.surfaceMidIntensity);
        opShader->addParameter("gSurfaceLowIntensity", mFxParams.surfaceLowIntensity);
        opShader->addParameter("gDiffuseCoefficient", mFxParams.diffuseCoefficient);
        opShader->addParameter("gSpecularCoefficient", mFxParams.specularCoefficient);
        opShader->addParameter("gSpecularPower", mFxParams.specularPower);
        appendOp({ "stylizationTarget" });


        opName = "[quad] color overlaps";
        opShader = new MOperationShader("quadGapsOverlaps", "overlapsWM");
        opShader->addTargetParameter("gColorTex", mRenderTargets.getTarget("stylizationTarget"));
        /*opShader->addTargetParameter("gColorTex", mRenderTargets.getTarget("colorTarget"));*/
        opShader->addTargetParameter("gEdgeTex", mRenderTargets.getTarget("blurredEdgeTarget"));
        opShader->addTargetParameter("gControlTex", mRenderTargets.getTarget("edgeCtrlTarget"));
        opShader->addTargetParameter("gNormalsTex", mRenderTargets.getTarget("normalsTarget"));
        opShader->addTargetParameter("gDepthTex", mRenderTargets.getTarget("linearDepth"));
        appendOp({ "stylizationTarget" });
        /*appendOp({ "colorSpreadTarget" });*/


        opName = "[quad] substrate-based hatching";
        opShader = new MOperationShader("quadHatching", "hatchTest");
        opShader->addTargetParameter("gStylizationTex", mRenderTargets.getTarget("stylizationTarget"));
        opShader->addTargetParameter("gHatchCtrl", mRenderTargets.getTarget("pigmentCtrlTarget"));
        opShader->addTargetParameter("gColorTex", mRenderTargets.getTarget("colorTarget"));
        opShader->addTargetParameter("gSubstrateTex", mRenderTargets.getTarget("substrateTarget"));
        opShader->addTargetParameter("gDiffuseTex", mRenderTargets.getTarget("widerDiffuseTarget"));
        opShader->addTargetParameter("gSpecularTex", mRenderTargets.getTarget("specularTarget"));
        //opShader->addParameter("gTestingValue", mFxParams.testingValue);
        appendOp({ "stylizationTarget" });


        opName = "[quad] uv hatching";
        opShader = new MOperationShader("quadHatching", "hatchUVsTest");
        opShader->addTargetParameter("gStylizationTex", mRenderTargets.getTarget("stylizationTarget"));
        opShader->addTargetParameter("gColorTex", mRenderTargets.getTarget("colorTarget"));
        opShader->addTargetParameter("gUVsTex", mRenderTargets.getTarget("normalsTarget"));
        opShader->addTargetParameter("gDiffuseTex", mRenderTargets.getTarget("widerDiffuseTarget"));
        opShader->addTargetParameter("gSpecularTex", mRenderTargets.getTarget("specularTarget"));
        //opShader->addParameter("gTestingValue", mFxParams.testingValue);
        appendOp({ "hatchingTarget" });
        /*appendOp({ "stylizationTarget" });*/


        /*opName = "[quad] edge darkening";
        opShader = new MOperationShader("quadEdgeManipulation", "applyEdgesWM");
        opShader->addTargetParameter("gColorTex", mRenderTargets.getTarget("stylizationTarget"));
        opShader->addTargetParameter("gEdgeTex", mRenderTargets.getTarget("blurredEdgeTarget"));
        opShader->addTargetParameter("gControlTex", mRenderTargets.getTarget("edgeCtrlTarget"));
        opShader->addParameter("gSubstrateColor", mEngSettings.substrateColor);
        opShader->addParameter("gEdgeIntensity", mFxParams.edgeIntensityWM);
        opShader->addParameter("gEdgeThreshold", mFxParams.edgeWidthWM);
        appendOp({ "stylizationTarget" });*/


        opName = "[quad] edge darkening";
        opShader = new MOperationShader("wm", "quadEdgeManipulation", "applyEdges");
        opShader->addTargetParameter("gRenderTex", mRenderTargets.getTarget("stylizationTarget"));
        opShader->addTargetParameter("gEdgeTex", mRenderTargets.getTarget("edgeResultAverageTarget"));
        appendOp({ "stylizationTarget" });
    }
};
