/**
 * Construct the final microcode from the compiled and verified shader source.
 * @param ShaderOutput - Where to store the microcode and parameter map.
 * @param InShaderSource - GLSL source with input/output signature.
 * @param SourceLen - The length of the GLSL source code.
 */
static void BuildShaderOutput(
	FShaderCompilerOutput& ShaderOutput,
	const FShaderCompilerInput& ShaderInput, 
	const ANSICHAR* InShaderSource,
	int32 SourceLen,
	GLSLVersion Version
	)
{
	const ANSICHAR* USFSource = InShaderSource;
	CrossCompiler::FHlslccHeader CCHeader;
	if (!CCHeader.Read(USFSource, SourceLen))
	{
		UE_LOG(LogOpenGLShaderCompiler, Error, TEXT("Bad hlslcc header found"));
	}
	
	if (*USFSource != '#')
	{
		UE_LOG(LogOpenGLShaderCompiler, Error, TEXT("Bad hlslcc header found! Missing '#'!"));
	}

	FOpenGLCodeHeader Header = {0};
	FShaderParameterMap& ParameterMap = ShaderOutput.ParameterMap;
	EShaderFrequency Frequency = (EShaderFrequency)ShaderOutput.Target.Frequency;

	TBitArray<> UsedUniformBufferSlots;
	UsedUniformBufferSlots.Init(false, 32);

	// Write out the magic markers.
	Header.GlslMarker = 0x474c534c;
	switch (Frequency)
	{
	case SF_Vertex:
		Header.FrequencyMarker = 0x5653;
		break;
	case SF_Pixel:
		Header.FrequencyMarker = 0x5053;
		break;
	case SF_Geometry:
		Header.FrequencyMarker = 0x4753;
		break;
	case SF_Hull:
		Header.FrequencyMarker = 0x4853;
		break;
	case SF_Domain:
		Header.FrequencyMarker = 0x4453;
		break;
	case SF_Compute:
		Header.FrequencyMarker = 0x4353;
		break;
	default:
		UE_LOG(LogOpenGLShaderCompiler, Fatal, TEXT("Invalid shader frequency: %d"), (int32)Frequency);
	}

	static const FString AttributePrefix = TEXT("in_ATTRIBUTE");
	static const FString GL_Prefix = TEXT("gl_");
	for (auto& Input : CCHeader.Inputs)
	{
		// Only process attributes for vertex shaders.
		if (Frequency == SF_Vertex && Input.Name.StartsWith(AttributePrefix))
		{
			int32 AttributeIndex = ParseNumber(*Input.Name + AttributePrefix.Len());
			Header.Bindings.InOutMask |= (1 << AttributeIndex);
		}
		// Record user-defined input varyings
		else if (!Input.Name.StartsWith(GL_Prefix))
		{
			FOpenGLShaderVarying Var;
			Var.Location = Input.Index;
			Var.Varying = ParseIdentifierANSI(Input.Name);
			Header.Bindings.InputVaryings.Add(Var);
		}
	}

	// Generate vertex attribute remapping table.
	// This is used on devices where GL_MAX_VERTEX_ATTRIBS < 16
	if (Frequency == SF_Vertex)
	{
		uint32 AttributeMask = Header.Bindings.InOutMask;
		int32 NextAttributeSlot = 0;
		Header.Bindings.VertexRemappedMask = 0;
		for (int32 AttributeIndex = 0; AttributeIndex < 16; AttributeIndex++, AttributeMask >>= 1)
		{
			if (AttributeMask & 0x1)
			{
				Header.Bindings.VertexRemappedMask |= (1 << NextAttributeSlot);
				Header.Bindings.VertexAttributeRemap[AttributeIndex] = NextAttributeSlot++;
			}
			else
			{
				Header.Bindings.VertexAttributeRemap[AttributeIndex] = -1;
			}
		}
	}
	static int32 Run(const FRunInfo& RunInfo)
	{
		ILanguageSpec* Language = nullptr;
		FCodeBackend* Backend = nullptr;

		uint32 Flags = HLSLCC_PackUniforms;
		Flags |= RunInfo.bValidate ? 0 : HLSLCC_NoValidation;
		Flags |= RunInfo.bRunCPP ? 0 : HLSLCC_NoPreprocess;
		Flags |= RunInfo.bPackIntoUBs ? HLSLCC_PackUniformsIntoUniformBuffers : 0;
		Flags |= RunInfo.bUseDX11Clip ? HLSLCC_DX11ClipSpace : 0;
		Flags |= RunInfo.bFlattenUBs ? HLSLCC_FlattenUniformBuffers : 0;
		Flags |= RunInfo.bFlattenUBStructs ? HLSLCC_FlattenUniformBufferStructures : 0;
		Flags |= RunInfo.bGroupFlattenUBs ? HLSLCC_GroupFlattenedUniformBuffers : 0;
		Flags |= RunInfo.bCSE ? HLSLCC_ApplyCommonSubexpressionElimination : 0;
		Flags |= RunInfo.bExpandExpressions ? HLSLCC_ExpandSubexpressions : 0;
		Flags |= RunInfo.bSeparateShaders ? HLSLCC_SeparateShaderObjects : 0;

		FGlslLanguageSpec GlslLanguage(RunInfo.Target == HCT_FeatureLevelES2);
		FGlslCodeBackend GlslBackend(Flags, RunInfo.Target);
		FMetalLanguageSpec MetalLanguage;
		FMetalCodeBackend MetalBackend(Flags, RunInfo.Target);

		switch (RunInfo.BackEnd)
		{
		case CCT::FRunInfo::BE_Metal:
			Language = &MetalLanguage;
			Backend = &MetalBackend;
			break;

		case CCT::FRunInfo::BE_OpenGL:
			Language = &GlslLanguage;
			Backend = &GlslBackend;
			Flags |= HLSLCC_DX11ClipSpace;
			break;

		default:
			return 1;
		}

		FString HLSLShaderSource;
		if (RunInfo.bUseNew)
		{
			if (RunInfo.bList)
			{
				if (!FFileHelper::LoadFileToString(HLSLShaderSource, *RunInfo.InputFile))
				{
					UE_LOG(LogCrossCompilerTool, Error, TEXT("Couldn't load Input file '%s'!"), *RunInfo.InputFile);
					return 1;
				}

				TArray<FString> List;

				if (!FFileHelper::LoadANSITextFileToStrings(*RunInfo.InputFile, &IFileManager::Get(), List))
				{
					return 1;
				}

				int32 Count = 0;
				for (auto& File : List)
				{
					FString HLSLShader;
					if (!FFileHelper::LoadFileToString(HLSLShader, *File))
					{
						UE_LOG(LogCrossCompilerTool, Error, TEXT("Couldn't load Input file '%s'!"), *File);
						continue;
					}
					UE_LOG(LogCrossCompilerTool, Log, TEXT("%d: %s"), Count++, *File);

					if (!CrossCompiler::Parser::Parse(HLSLShader, File, false))
					{
						UE_LOG(LogCrossCompilerTool, Log, TEXT("Error compiling '%s'!"), *File);
						return 1;
					}
				}
			}
			else
			{
				if (RunInfo.bRunCPP)
				{
					//GMalloc->DumpAllocatorStats(*FGenericPlatformOutputDevices::GetLog());
					if (!Preprocess(RunInfo.InputFile, HLSLShaderSource))
					{
						UE_LOG(LogCrossCompilerTool, Log, TEXT("Error during preprocessor on '%s'!"), *RunInfo.InputFile);
						return 1;
					}
					//GMalloc->DumpAllocatorStats(*FGenericPlatformOutputDevices::GetLog());
				}
				else
				{
					if (!FFileHelper::LoadFileToString(HLSLShaderSource, *RunInfo.InputFile))
					{
						UE_LOG(LogCrossCompilerTool, Error, TEXT("Couldn't load Input file '%s'!"), *RunInfo.InputFile);
						return 1;
					}
				}

				if (RunInfo.bPreprocessOnly)
				{
					return 0;
				}

				if (!CrossCompiler::Parser::Parse(HLSLShaderSource, *RunInfo.InputFile, true))
				{
					UE_LOG(LogCrossCompilerTool, Log, TEXT("Error compiling '%s'!"), *RunInfo.InputFile);
					return 1;
				}
			}
			//Scanner.Dump();
			return 0;
		}
		else
		{
			if (!FFileHelper::LoadFileToString(HLSLShaderSource, *RunInfo.InputFile))
			{
				UE_LOG(LogCrossCompilerTool, Error, TEXT("Couldn't load Input file '%s'!"), *RunInfo.InputFile);
				return 1;
			}
		}

		ANSICHAR* ShaderSource = 0;
		ANSICHAR* ErrorLog = 0;

		FHlslCrossCompilerContext Context(Flags, RunInfo.Frequency, RunInfo.Target);
		if (Context.Init(TCHAR_TO_ANSI(*RunInfo.InputFile), Language))
		{
			Context.Run(
				TCHAR_TO_ANSI(*HLSLShaderSource),
				TCHAR_TO_ANSI(*RunInfo.Entry),
				Backend,
				&ShaderSource,
				&ErrorLog);
		}

		if (ErrorLog)
		{
			FString OutError(ANSI_TO_TCHAR(ErrorLog));
			UE_LOG(LogCrossCompilerTool, Warning, TEXT("%s"), *OutError);
		}

		if (ShaderSource)
		{
			FString OutSource(ANSI_TO_TCHAR(ShaderSource));
			UE_LOG(LogCrossCompilerTool, Display, TEXT("%s"), *OutSource);
			if (RunInfo.OutputFile.Len() > 0)
			{
				FFileHelper::SaveStringToFile(OutSource, *RunInfo.OutputFile);
			}
		}

		if (ShaderSource)
		{
			const ANSICHAR* USFSource = ShaderSource;
			CrossCompiler::FHlslccHeader CCHeader;
			int32 Len = FCStringAnsi::Strlen(USFSource);
			if (!CCHeader.Read(USFSource, Len))
			{
				UE_LOG(LogCrossCompilerTool, Error, TEXT("Bad hlslcc header found"));
			}

			if (Language == &GlslLanguage && *USFSource != '#')
			{
				UE_LOG(LogCrossCompilerTool, Error, TEXT("Bad hlslcc header found! Missing '#'!"));
			}
		}

		free(ShaderSource);
		free(ErrorLog);

		return 0;
	}