#include "Reshade.fxh"

#define NOISE_SIZE 16
#define BUFFER_SIZE int2(BUFFER_WIDTH,BUFFER_HEIGHT)
#define getColor(c) tex2Dlod(ReShade::BackBuffer,float4(c,0.0,0.0))
#define getBlur(c) tex2Dlod(blurSampler,float4(c,0.0,0.0))
#define getDepth(c) ReShade::GetLinearizedDepth(c)*RESHADE_DEPTH_LINEARIZATION_FAR_PLANE
#define diff3t(v1,v2,t) (abs(v1.x-v2.x)>t || abs(v1.y-v2.y)>t || abs(v1.z-v2.z)>t)

namespace DHAnime {

	texture blueNoiseTex < source ="dh_bluenoise.png" ; > { Width = NOISE_SIZE; Height = NOISE_SIZE; MipLevels = 1; Format = RGBA8; };
    sampler blueNoiseSampler { Texture = blueNoiseTex;  AddressU = REPEAT;	AddressV = REPEAT;	AddressW = REPEAT;};


//// uniform

	uniform int iBlackLineThickness <
	    ui_category = "Black lines";
		ui_label = "Thickness";
		ui_type = "slider";
	    ui_min = 0;
	    ui_max = 16;
	    ui_step = 1;
	> = 3;
	uniform float fBlackLineThreshold <
	    ui_category = "Black lines";
		ui_label = "Threshold";
		ui_type = "slider";
	    ui_min = 0.0;
	    ui_max = 1.0;
	    ui_step = 0.001;
	> = 0.995;

	uniform float iSurfaceBlur <
		ui_category = "Colors";
		ui_label = "Surface blur";
		ui_type = "slider";
	    ui_min = 0;
	    ui_max = 16;
	    ui_step = 1;
	> = 3;
	
	uniform float fSaturation <
		ui_category = "Colors";
		ui_label = "Saturation multiplier";
		ui_type = "slider";
	    ui_min = 0.0;
	    ui_max = 5.0;
	    ui_step = 0.01;
	> = 2.5;

	uniform float iShadingSteps <
		ui_category = "Colors";
		ui_label = "Shading steps";
		ui_type = "slider";
	    ui_min = 1;
	    ui_max = 255;
	    ui_step = 1;
	> = 16;
	
	uniform bool bDithering <
	    ui_category = "Colors";
		ui_label = "Dithering";
	> = false;

//// textures

	texture normalTex { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; };
	sampler normalSampler { Texture = normalTex; };

	texture blurTex { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; };
	sampler blurSampler { Texture = blurTex; };

//// Functions

	// Normals

	float3 normal(float2 texcoord)
	{
		float3 offset = float3(ReShade::PixelSize.xy, 0.0);
		float2 posCenter = texcoord.xy;
		float2 posNorth  = posCenter - offset.zy;
		float2 posEast   = posCenter + offset.xz;
	
		float3 vertCenter = float3(posCenter - 0.5, 1) * getDepth(posCenter);
		float3 vertNorth  = float3(posNorth - 0.5,  1) * getDepth(posNorth);
		float3 vertEast   = float3(posEast - 0.5,   1) * getDepth(posEast);
	
		return normalize(cross(vertCenter - vertNorth, vertCenter - vertEast));
	}

	
	void saveNormal(in float3 normal, out float4 outNormal) 
	{
		outNormal = float4(normal*0.5+0.5,1.0);
	}
	
	float3 loadNormal(in float2 coords) {
		return (tex2Dlod(normalSampler,float4(coords,0,0)).xyz-0.5)*2.0;
	}

	// Color space

	float RGBCVtoHUE(in float3 RGB, in float C, in float V) {
	      float3 Delta = (V - RGB) / C;
	      Delta.rgb -= Delta.brg;
	      Delta.rgb += float3(2,4,6);
	      Delta.brg = step(V, RGB) * Delta.brg;
	      float H;
	      H = max(Delta.r, max(Delta.g, Delta.b));
	      return frac(H / 6);
	}

	float3 RGBtoHSL(in float3 RGB) {
	    float3 HSL = 0;
	    float U, V;
	    U = -min(RGB.r, min(RGB.g, RGB.b));
	    V = max(RGB.r, max(RGB.g, RGB.b));
	    HSL.z = ((V - U) * 0.5);
	    float C = V + U;
	    if (C != 0)
	    {
	    	HSL.x = RGBCVtoHUE(RGB, C, V);
	    	HSL.y = C / (1 - abs(2 * HSL.z - 1));
	    }
	    return HSL;
	}
	  
	float3 HUEtoRGB(in float H) 
	{
	    float R = abs(H * 6 - 3) - 1;
	    float G = 2 - abs(H * 6 - 2);
	    float B = 2 - abs(H * 6 - 4);
	    return saturate(float3(R,G,B));
	}
	  
	float3 HSLtoRGB(in float3 HSL)
	{
	    float3 RGB = HUEtoRGB(HSL.x);
	    float C = (1 - abs(2 * HSL.z - 1)) * HSL.y;
	    return (RGB - 0.5) * C + HSL.z;
	}


//// PS

	void PS_Input(float4 vpos : SV_Position, in float2 coords : TEXCOORD0, out float4 outNormal : SV_Target, out float4 outBlur : SV_Target1)
	{
		float3 normal = normal(coords);
		saveNormal(normal,outNormal);
		
		if(iSurfaceBlur>0) {
			float4 sum;
			int count;
			
			int maxDistance = iSurfaceBlur*iSurfaceBlur;
			float depth = getDepth(coords);
			
			int2 delta;
			for(delta.x=-iSurfaceBlur;delta.x<=iSurfaceBlur;delta.x++) {
				for(delta.y=-iSurfaceBlur;delta.y<=iSurfaceBlur;delta.y++) {
					int d = dot(delta,delta);
					if(d<=maxDistance) {
						float2 searchCoords = coords+ReShade::PixelSize*delta;
						float searchDepth = getDepth(searchCoords);
						float dRatio = depth/searchDepth;
						
						if(dRatio>=0.95 && dRatio<=1.05) {
							sum += getColor(searchCoords);
							count++;
						}
					}
				}
			}
			outBlur = sum/count;
		} else {
			outBlur = getColor(coords);
		}
	}

	void PS_Manga(float4 vpos : SV_Position, in float2 coords : TEXCOORD0, out float4 outPixel : SV_Target)
	{
		// black line
		if(iBlackLineThickness>0) {
			int maxDistance = iBlackLineThickness*iBlackLineThickness;
			float depth = getDepth(coords);
			float3 normal = loadNormal(coords);
			
			int2 delta;
			for(delta.x=-iBlackLineThickness;delta.x<=iBlackLineThickness;delta.x++) {
				for(delta.y=-iBlackLineThickness;delta.y<=iBlackLineThickness;delta.y++) {
					int d = dot(delta,delta);
					if(d<=maxDistance) {
						float2 searchCoords = coords+ReShade::PixelSize*delta;
						float searchDepth = getDepth(searchCoords);
						float3 searchNormal = loadNormal(searchCoords);

						if(depth/searchDepth<=fBlackLineThreshold && diff3t(normal,searchNormal,0.1)) {
							outPixel = float4(0.0,0.0,0.0,1.0);
							return;
						}
					}
				}
			}
		}

		float3 color = getBlur(coords).rgb;
		float3 hsl = RGBtoHSL(color);
		
		// shading steps
		float stepSize = 1.0/iShadingSteps;
		if(bDithering) {
			int2 coordsNoise = int2(coords*BUFFER_SIZE)%NOISE_SIZE;
			float noise = tex2Dfetch(blueNoiseSampler,coordsNoise).r;
			
			float exact = (hsl.z/stepSize)/iShadingSteps;
			float ceiled = ceil(hsl.z/stepSize)/iShadingSteps;
			float rounded = round(hsl.z/stepSize)/iShadingSteps;
			float floored = floor(hsl.z/stepSize)/iShadingSteps;
			
			float ditherRatio = min(ceiled-exact,exact-floored)/stepSize;
			float dithered = ceiled-exact<exact-floored 
				? ceiled
				: floored;
				
			if(noise>0.7) {
				hsl.z = dithered;
			} else {
				hsl.z = exact;
			}
		} else {
			hsl.z = round(hsl.z/stepSize)/iShadingSteps;
		}

		// saturation
		hsl.y = clamp(hsl.y*fSaturation,0,1);

		color = HSLtoRGB(hsl);

		outPixel = float4(color,1.0);	
	}

//// Techniques

	technique DH_Anime <
	>
	{
		pass
		{
			VertexShader = PostProcessVS;
			PixelShader = PS_Input;
			RenderTarget = normalTex;
			RenderTarget1 = blurTex;
		}
		pass
		{
			VertexShader = PostProcessVS;
			PixelShader = PS_Manga;
		}
	}

}