/*
===========================================================================
Copyright (C) 2022-2024 Gian 'myT' Schellenbaum

This file is part of Challenge Quake 3 (CNQ3).

Challenge Quake 3 is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Challenge Quake 3 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
===========================================================================
*/
// compiles core shaders as byte code to be embedded into the CNQ3 client


#include <Windows.h>
#include <stdarg.h>
#include <stdio.h>
#include <assert.h>
#include <shlwapi.h>
#include <sal.h>

#include "../renderer/grp_uber_shaders.h"


char repoPath[MAX_PATH];
char outputPath[MAX_PATH];
char bin2headerPath[MAX_PATH];
char dxcPath[MAX_PATH];

const char* targetVS = "vs_6_0";
const char* targetPS = "ps_6_0";
const char* targetCS = "cs_6_0";


#define PS(Data) #Data,
const char* uberShaderPixelStates[] =
{
	UBER_SHADER_PS_LIST(PS)
};
#undef PS


// -Zi embeds debug info
// -Qembed_debug embeds debug info in shader container
// -Vn header variable name
// -WX warnings as errors
// -O3 or -O0 optimization level
// -Wno-warning disables the warning


const char* va(_Printf_format_string_ const char* format, ...)
{
	static char string[64][32000];
	static int index = 0;
	char* buf = string[index++ & 63];
	va_list argptr;

	va_start(argptr, format);
	vsprintf(buf, format, argptr);
	va_end(argptr);

	return buf;
}


struct ShaderArgs
{
	const char* headerPath;
	const char* shaderPath;
	const char* entryPoint;
	const char* targetProfile;
};

const char* OutputPath(const char* path)
{
	return va("%s\\%s", outputPath, path);
}

const char* HeaderVariable(const char* name)
{
	// the output variable isn't declared static, polluting the global namespace...
	return va("\"static %s\"", name);
}

void CompileShader(const ShaderArgs& args, int extraCount = 0, const char** extras = NULL)
{
	static char temp[4096];

	const char* headerPath = va("%s\\%s", outputPath, args.headerPath);

	// -Ges: Enable strict mode
	// -Gis: Force IEEE strictness
	// -Zi: Embed debug info
	// -Qembed_debug: Embed debug info in shader container
	strcpy(temp, va("%s -HV 2021 -Fh %s -E %s -T %s -WX -Ges -Gis -Zi -Qembed_debug",
		dxcPath, headerPath, args.entryPoint, args.targetProfile));

	for(int i = 0; i < extraCount; ++i)
	{
		strcat(temp, " ");
		strcat(temp, extras[i]);
	}

	strcat(temp, " ");
	strcat(temp, args.shaderPath);

	printf("%s\n", temp);
	system(temp);
}

struct SMAAArgs
{
	const char* headerPath;
	const char* shaderPath;
	const char* presetMacro;
	const char* variableName;
	bool vertexShader;
};

void CompileSMAAShader(const SMAAArgs& smaaArgs)
{
	const char* extras[] =
	{
		"-Vn", HeaderVariable(smaaArgs.variableName),
		smaaArgs.presetMacro,
		smaaArgs.vertexShader ? "-D SMAA_INCLUDE_VS=1" : "-D SMAA_INCLUDE_PS=1",
		"-D SMAA_HLSL_5_1=1",
		"-D SMAA_RT_METRICS=rtMetrics"
	};

	ShaderArgs args;
	args.entryPoint = smaaArgs.vertexShader ? "vs" : "ps";
	args.headerPath = smaaArgs.headerPath;
	args.shaderPath = smaaArgs.shaderPath;
	args.targetProfile = smaaArgs.vertexShader ? targetVS : targetPS;
	CompileShader(args, _countof(extras), extras);
}

void ProcessSMAAShadersForPreset(const char* presetName, const char* presetMacro)
{
	SMAAArgs args;
	args.presetMacro = presetMacro;
	for(int pass = 0; pass < 3; ++pass)
	{
		for(int ps = 0; ps < 2; ++ps)
		{
			args.headerPath = va("smaa_%s_%d_%s.h", presetName, pass + 1, ps ? "ps" : "vs");
			args.shaderPath = va("smaa_%d.hlsl", pass + 1);
			args.variableName = va("%s_%d_%s", presetName, pass + 1, ps ? "ps" : "vs");
			args.vertexShader = ps == 0;
			CompileSMAAShader(args);
		}
	}
}

void CompileSMAAShaders()
{
	ProcessSMAAShadersForPreset("low", "-D SMAA_PRESET_LOW=1");
	ProcessSMAAShadersForPreset("medium", "-D SMAA_PRESET_MEDIUM=1");
	ProcessSMAAShadersForPreset("high", "-D SMAA_PRESET_HIGH=1");
	ProcessSMAAShadersForPreset("ultra", "-D SMAA_PRESET_ULTRA=1");
}

void CompileGraphics(const char* headerPath, const char* shaderPath, const char* varName,
	int vsOptionCount = 0, int psOptionCount = 0, ...)
{
	const char* vsHeaderRelPath = va("%s.vs.h", shaderPath);
	const char* psHeaderRelPath = va("%s.ps.h", shaderPath);
	const char* vsHeaderPath = OutputPath(vsHeaderRelPath);
	const char* psHeaderPath = OutputPath(psHeaderRelPath);

	int vsExtraCount = 4;
	int psExtraCount = 4;
	const char* vsExtras[64] =
	{
		"-D", "VERTEX_SHADER=1",
		"-Vn", HeaderVariable(va("g_%s_vs", varName))
	};
	const char* psExtras[64] =
	{
		"-D", "PIXEL_SHADER=1",
		"-Vn", HeaderVariable(va("g_%s_ps", varName))
	};

	assert(vsExtraCount + vsOptionCount <= _countof(vsExtras));
	assert(psExtraCount + psOptionCount <= _countof(psExtras));

	va_list argPtr;
	va_start(argPtr, psOptionCount);
	for(int i = 0; i < vsOptionCount; i++)
	{
		vsExtras[vsExtraCount++] = va_arg(argPtr, const char*);
	}
	for(int i = 0; i < psOptionCount; i++)
	{
		psExtras[psExtraCount++] = va_arg(argPtr, const char*);
	}
	va_end(argPtr);

	ShaderArgs args;
	args.entryPoint = "vs";
	args.headerPath = vsHeaderRelPath;
	args.shaderPath = shaderPath;
	args.targetProfile = targetVS;
	CompileShader(args, vsExtraCount, vsExtras);

	args.entryPoint = "ps";
	args.headerPath = psHeaderRelPath;
	args.shaderPath = shaderPath;
	args.targetProfile = targetPS;
	CompileShader(args, psExtraCount, psExtras);
	
	const char* outHeaderPath = OutputPath(headerPath);
	system(va("type %s %s > %s", vsHeaderPath, psHeaderPath, outHeaderPath));
	system(va("del %s", vsHeaderPath));
	system(va("del %s", psHeaderPath));
}

void CompileVertexShader(const char* headerPath, const char* shaderPath, const char* varName)
{
	const char* extras[] =
	{
		"-D", "VERTEX_SHADER=1",
		"-Vn", HeaderVariable(va("g_%s_vs", varName))
	};

	ShaderArgs args;
	args.entryPoint = "vs";
	args.headerPath = headerPath;
	args.shaderPath = shaderPath;
	args.targetProfile = targetVS;
	CompileShader(args, _countof(extras), extras);
}

void CompilePixelShader(const char* headerPath, const char* shaderPath, const char* varName)
{
	const char* extras[] =
	{
		"-D", "PIXEL_SHADER=1",
		"-Vn", HeaderVariable(va("g_%s_ps", varName))
	};

	ShaderArgs args;
	args.entryPoint = "ps";
	args.headerPath = headerPath;
	args.shaderPath = shaderPath;
	args.targetProfile = targetPS;
	CompileShader(args, _countof(extras), extras);
}

void CompileCompute(const char* headerPath, const char* shaderPath, const char* varName)
{
	const char* extras[] =
	{
		"-D", "COMPUTE_SHADER=1",
		"-Vn", HeaderVariable(va("g_%s_cs", varName))
	};

	ShaderArgs args;
	args.entryPoint = "cs";
	args.headerPath = headerPath;
	args.shaderPath = shaderPath;
	args.targetProfile = targetCS;
	CompileShader(args, _countof(extras), extras);
}

void CompileUberVS(const char* headerPath, const char* shaderPath, int stageCount)
{
	const char* extras[] =
	{
		"-D", "VERTEX_SHADER=1",
		"-D", va("STAGE_COUNT=%d", stageCount),
		"-Vn", HeaderVariable(va("g_vs_%d", stageCount))
	};

	ShaderArgs args;
	args.entryPoint = "vs";
	args.headerPath = headerPath;
	args.shaderPath = shaderPath;
	args.targetProfile = targetVS;
	CompileShader(args, _countof(extras), extras);
}

void CompileUberPS(const char* stateString)
{
	UberPixelShaderState state;
	if(!ParseUberPixelShaderState(state, stateString))
	{
		fprintf(stderr, "ParseUberPixelShaderState failed!\n");
		exit(666);
	}

	const char* extras[16];
	int extraCount = 0;
	extras[extraCount++] = va("-Vn %s", HeaderVariable(va("g_ps_%s", stateString)));
	extras[extraCount++] = "-D USE_INCLUDES=1";
	extras[extraCount++] = "-D PIXEL_SHADER=1";
	if(state.globalState & UBERPS_DITHER_BIT)
	{
		extras[extraCount++] = "-D DITHER=1";
	}
	if(state.globalState & UBERPS_DEPTHFADE_BIT)
	{
		extras[extraCount++] = "-D DEPTH_FADE=1";
	}
	extras[extraCount++] = va("-D STAGE_COUNT=%d", state.stageCount);
	for(int s = 0; s < state.stageCount; ++s)
	{
		extras[extraCount++] = va("-D STAGE%d_BITS=0x%X", s, state.stageStates[s]);
	}

	ShaderArgs args;
	args.entryPoint = "ps";
	args.headerPath = va("uber_shader_ps_%s.h", stateString);
	args.shaderPath = "uber_shader.hlsl";
	args.targetProfile = targetPS;
	CompileShader(args, extraCount, extras);
}

const char* Canonicalize(const char* path)
{
	static char canonPath[MAX_PATH];

	PathCanonicalizeA(canonPath, path);

	return canonPath;
}

void InitDirectory(const char* dirName)
{
	const char* rendererPath = va("%s\\code\\renderer", repoPath);
	const char* cd = Canonicalize(va("%s\\shaders\\%s", rendererPath, dirName));
	SetCurrentDirectoryA(cd);
	const char* out = Canonicalize(va("%s\\compshaders\\%s", rendererPath, dirName));
	strcpy(outputPath, out);
	CreateDirectoryA(outputPath, NULL);

	system(va("del %s\\*.h", outputPath));
	system(va("del %s\\*.temp", outputPath));
}

void ProcessGRP()
{
	InitDirectory("grp");
	targetVS = "vs_6_0";
	targetPS = "ps_6_0";
	targetCS = "cs_6_0";

	CompileGraphics("post_gamma.h", "post_gamma.hlsl", "post");
	CompileGraphics("post_inverse_gamma.h", "post_inverse_gamma.hlsl", "post_inverse");
	CompileGraphics("imgui.h", "imgui.hlsl", "imgui");
	CompileGraphics("nuklear.h", "nuklear.hlsl", "nuklear");
	CompileGraphics("ui.h", "ui.hlsl", "ui");
	CompileGraphics("depth_pre_pass.h", "depth_pre_pass.hlsl", "zpp");
	CompileGraphics("dynamic_light.h", "dynamic_light.hlsl", "dl");
	CompileVertexShader("fog.h", "fog_inside.hlsl", "fog");
	CompilePixelShader("fog_inside.h", "fog_inside.hlsl", "fog_inside");
	CompilePixelShader("fog_outside.h", "fog_outside.hlsl", "fog_outside");
	CompileCompute("mip_1.h", "mip_1.hlsl", "mip_1");
	CompileCompute("mip_2.h", "mip_2.hlsl", "mip_2");
	CompileCompute("mip_3.h", "mip_3.hlsl", "mip_3");

	CompileSMAAShaders();
	system(va("type %s\\smaa*.h > %s\\complete_smaa.h", outputPath, outputPath));

	// type combines all files into one
	system(va("type ..\\common\\state_bits.h.hlsli ..\\common\\blend.hlsli shared.hlsli uber_shader.hlsl > %s\\uber_shader.temp", outputPath));
	system(va("%s --output %s\\uber_shader.h --hname uber_shader_string %s\\uber_shader.temp", bin2headerPath, outputPath, outputPath));
	system(va("del %s\\uber_shader.temp", outputPath));

	for(int i = 0; i < 8; ++i)
	{
		CompileUberVS(va("uber_shader_vs_%i.h", i + 1), "uber_shader.hlsl", i + 1);
	}
	system(va("type %s\\uber_shader_vs_*.h > %s\\complete_uber_vs.h", outputPath, outputPath));
	system(va("del %s\\uber_shader_vs_*.h", outputPath));

	for(int i = 0; i < _countof(uberShaderPixelStates); ++i)
	{
		CompileUberPS(uberShaderPixelStates[i]);
	}
	system(va("type %s\\uber_shader_ps_*.h > %s\\complete_uber_ps.h", outputPath, outputPath));
	system(va("del %s\\uber_shader_ps_*.h", outputPath));
}

void ProcessCRP()
{
	InitDirectory("crp");
	targetVS = "vs_6_6";
	targetPS = "ps_6_6";
	targetCS = "cs_6_6";

	CompileVertexShader("fullscreen.h", "fullscreen.hlsl", "fullscreen");
	CompilePixelShader("blit.h", "blit.hlsl", "blit");
	CompileGraphics("ui.h", "ui.hlsl", "ui");
	CompileGraphics("imgui.h", "imgui.hlsl", "imgui");
	CompileGraphics("nuklear.h", "nuklear.hlsl", "nuklear");
	CompileCompute("mip_1.h", "mip_1.hlsl", "mip_1");
	CompileCompute("mip_2.h", "mip_2.hlsl", "mip_2");
	CompileCompute("mip_3.h", "mip_3.hlsl", "mip_3");
	CompileGraphics("prepass.h", "prepass.hlsl", "prepass");
	CompileGraphics("prepass_bary.h", "prepass.hlsl", "prepass_bary", 1, 1, "-D BARYCENTRICS=1", "-D BARYCENTRICS=1");
	CompileGraphics("opaque.h", "opaque.hlsl", "opaque");
	CompileGraphics("transp_draw.h", "transp_draw.hlsl", "transp_draw");
	CompilePixelShader("transp_resolve.h", "transp_resolve.hlsl", "transp_resolve");
	CompilePixelShader("tone_map.h", "tone_map.hlsl", "tone_map");
	CompilePixelShader("tone_map_inverse.h", "tone_map_inverse.hlsl", "tone_map_inverse");
	CompilePixelShader("accumdof_accum.h", "accumdof_accum.hlsl", "accum");
	CompilePixelShader("accumdof_norm.h", "accumdof_norm.hlsl", "norm");
	CompilePixelShader("accumdof_debug.h", "accumdof_debug.hlsl", "debug");
	CompileCompute("gatherdof_split.h", "gatherdof_split.hlsl", "split");
	CompileCompute("gatherdof_coc_tile_gen.h", "gatherdof_coc_tile_gen.hlsl", "coc_tile_gen");
	CompileCompute("gatherdof_coc_tile_max.h", "gatherdof_coc_tile_max.hlsl", "coc_tile_max");
	CompileCompute("gatherdof_blur.h", "gatherdof_blur.hlsl", "blur");
	CompileCompute("gatherdof_fill.h", "gatherdof_fill.hlsl", "fill");
	CompilePixelShader("gatherdof_combine.h", "gatherdof_combine.hlsl", "combine");
	CompilePixelShader("gatherdof_debug.h", "gatherdof_debug.hlsl", "debug");
	CompileGraphics("fog_inside.h", "fog_inside.hlsl", "inside");
	CompileGraphics("fog_outside.h", "fog_outside.hlsl", "outside");
	CompilePixelShader("magnifier.h", "magnifier.hlsl", "magnifier");
	CompilePixelShader("dl_draw.h", "dl_draw.hlsl", "dl_draw");
	CompilePixelShader("dl_denoising.h", "dl_denoising.hlsl", "dl_denoising");
	CompileGraphics("add_light.h", "add_light.hlsl", "add_light");
	CompilePixelShader("gbufferviz_depth.h", "gbufferviz_depth.hlsl", "gbufferviz_depth");
	CompilePixelShader("gbufferviz_normal.h", "gbufferviz_normal.hlsl", "gbufferviz_normal");
	CompilePixelShader("gbufferviz_position.h", "gbufferviz_position.hlsl", "gbufferviz_position");
	CompilePixelShader("gbufferviz_motion.h", "gbufferviz_motion.hlsl", "gbufferviz_motion");
	CompileGraphics("wireframe_normals.h", "wireframe_normals.hlsl", "wireframe_normals");
	CompilePixelShader("skybox_motion.h", "skybox_motion.hlsl", "skybox_motion");
	CompileCompute("mblur_tile_gen.h", "mblur_tile_gen.hlsl", "tile_gen");
	CompileCompute("mblur_tile_max.h", "mblur_tile_max.hlsl", "tile_max");
	CompilePixelShader("mblur_blur.h", "mblur_blur.hlsl", "blur");
	CompilePixelShader("mblur_pack.h", "mblur_pack.hlsl", "pack");
}

int main(int /*argc*/, const char** argv)
{
	char dirPath[MAX_PATH];
	strcpy(dirPath, argv[0]);
	int l = strlen(dirPath);
	while(l-- > 0)
	{
		if(dirPath[l] == '/' || dirPath[l] == '\\')
		{
			dirPath[l] = '\0';
			break;
		}
	}
	strcpy(repoPath, Canonicalize(va("%s\\..\\..", dirPath)));
	strcpy(bin2headerPath, Canonicalize(va("%s\\tools\\bin2header.exe", repoPath)));

	char* path = getenv("DXCPATH");
	if(path != NULL)
	{
		strcpy(dxcPath, path);
	}
	else
	{
		strcpy(dxcPath, "dxc.exe");
	}
	system(va("%s --version", dxcPath));

	ProcessGRP();
	ProcessCRP();

	return 0;
}
