import gradlecpp.RegamedllPlayTestPlugin
import gradlecpp.RegamedllPlayTestTask
import gradlecpp.VelocityUtils
import org.doomedsociety.gradlecpp.GradleCppUtils
import org.doomedsociety.gradlecpp.LazyNativeDepSet
import org.doomedsociety.gradlecpp.cfg.ToolchainConfig
import org.doomedsociety.gradlecpp.cfg.ToolchainConfigUtils
import org.doomedsociety.gradlecpp.gcc.GccToolchainConfig
import org.doomedsociety.gradlecpp.msvc.EnhancedInstructionsSet
import org.doomedsociety.gradlecpp.msvc.FloatingPointModel
import org.doomedsociety.gradlecpp.msvc.MsvcToolchainConfig
import org.doomedsociety.gradlecpp.toolchain.icc.Icc
import org.doomedsociety.gradlecpp.toolchain.icc.IccCompilerPlugin
import org.gradle.language.cpp.CppSourceSet
import org.gradle.nativeplatform.NativeBinarySpec
import org.gradle.nativeplatform.NativeExecutableSpec
import org.gradle.nativeplatform.NativeLibrarySpec
import org.gradle.nativeplatform.SharedLibraryBinarySpec
import regamedll.testdemo.RegamedllDemoRunner
import versioning.RegamedllVersionInfo
import org.apache.commons.io.FilenameUtils
import org.apache.commons.compress.archivers.ArchiveInputStream

apply plugin: 'cpp'
apply plugin: IccCompilerPlugin
apply plugin: RegamedllPlayTestPlugin
apply plugin: gradlecpp.CppUnitTestPlugin

repositories {
	maven {
		url 'http://nexus.rehlds.org/nexus/content/repositories/regamedll-releases/'
	}
}

configurations {
	regamedll_tests
}

dependencies {
	regamedll_tests 'regamedll.testdemos:cstrike-basic:1.0'
}

project.ext.dep_cppunitlite = project(':dep/cppunitlite')

void createIntergrationTestTask(NativeBinarySpec b) {
	boolean regamedllFixes = b.flavor.name.contains('regamedllFixes')
	boolean mpLib = b.name.toLowerCase().contains('mp')

	if (!(b instanceof SharedLibraryBinarySpec)) return
	if (!GradleCppUtils.windows) return
	if (regamedllFixes) return
	if (!mpLib) return

	String unitTestTask = b.hasProperty('cppUnitTestTask') ? b.cppUnitTestTask : null

	def demoItgTestTask = project.tasks.create(b.namingScheme.getTaskName('demoItgTest'), RegamedllPlayTestTask)
	demoItgTestTask.with {
		regamedllImageRoot = new File(project.projectDir, '_regamedllTestImg')
		regamedllTestLogs = new File(this.project.buildDir, "_regamedllTestLogs/${b.name}")
		testDemos = project.configurations.regamedll_tests
		testFor = b

		//inputs/outputs for up-to-date check
		inputs.files testDemos.files
		outputs.dir regamedllTestLogs

		//dependencies on test executable
		if (unitTestTask) {
			dependsOn unitTestTask
		}

		postExtractAction {
			def binaryOutFile = GradleCppUtils.getBinaryOutputFile(b)
			def binaryOutDir = new File(project.projectDir, '/_regamedllTestImg/cstrike/dlls')
			GradleCppUtils.copyFile(binaryOutFile, new File(binaryOutDir, binaryOutFile.name), true)
		}
	}

	b.buildTask.dependsOn demoItgTestTask
}

void postEvaluate(NativeBinarySpec b) {

	// attach generateAppVersion task to all 'compile source' tasks
	GradleCppUtils.getCompileTasks(b).each { Task t ->
		t.dependsOn project.generateAppVersion
	}

	createIntergrationTestTask(b)
}

void setupToolchain(NativeBinarySpec b)
{
	boolean unitTestExecutable = b.component.name.endsWith('_tests')
	boolean mpLib = b.name.toLowerCase().contains('mp')
	boolean regamedllFixes = b.flavor.name.contains('regamedllFixes')

	ToolchainConfig cfg = rootProject.createToolchainConfig(b)
	cfg.projectInclude(project, '', '/engine', '/common', '/dlls', '/game_shared', '/pm_shared', '/regamedll', '/hookers', '/public', '/public/regamedll')

	if (unitTestExecutable)
	{
		cfg.projectInclude(dep_cppunitlite, '/include')
		b.lib LazyNativeDepSet.create(dep_cppunitlite, 'cppunitlite', b.buildType.name, true)
	}

	cfg.singleDefines 'USE_BREAKPAD_HANDLER', 'DEDICATED', 'REGAMEDLL_SELF', 'REGAMEDLL_API', 'CLIENT_WEAPONS'

	if (cfg instanceof MsvcToolchainConfig)
	{
		cfg.compilerOptions.pchConfig = new MsvcToolchainConfig.PrecompiledHeadersConfig(
			enabled: true,
			pchHeader: 'precompiled.h',
			pchSourceSet: 'regamedll_pch'
		);

		cfg.singleDefines('_CRT_SECURE_NO_WARNINGS')
		if (!regamedllFixes)
		{
			cfg.compilerOptions.floatingPointModel = FloatingPointModel.PRECISE
			cfg.compilerOptions.enhancedInstructionsSet = EnhancedInstructionsSet.DISABLED
		}
		else {
			cfg.compilerOptions.args '/Oi', '/GF', '/GS-', '/GR-'
		}

		if (mpLib)
		{
			cfg.linkerOptions.randomizedBaseAddress = false
			cfg.linkerOptions.baseAddress = '0x4970000'
		}
	}
	else if (cfg instanceof GccToolchainConfig)
	{
		cfg.compilerOptions.pchConfig = new GccToolchainConfig.PrecompilerHeaderOptions(
			enabled: true,
			pchSourceSet: 'regamedll_pch'
		);

		cfg.compilerOptions.languageStandard = 'c++0x'
		cfg.defines([
			'_stricmp': 'strcasecmp',
			'_strnicmp': 'strncasecmp',
			'_strdup': 'strdup',
			'_unlink': 'unlink',
			'_vsnprintf': 'vsnprintf',
			'_write' : 'write',
			'_close' : 'close',
			'_vsnwprintf' : 'vswprintf',
			'_access' : 'access'
		])

		cfg.linkerOptions.args '-no-opt-class-analysis'
		cfg.compilerOptions.args '-Qoption,cpp,--treat_func_as_string_literal_cpp', '-g0', '-fno-rtti'
		cfg.extraLibs 'dl', 'm', 'stdc++'
	}

	if (mpLib && GradleCppUtils.windows && !unitTestExecutable) {
		cfg.linkerOptions.definitionFile = "${projectDir}\\msvc\\mp.def";
	}

	if (unitTestExecutable) {
		cfg.singleDefines 'REGAMEDLL_UNIT_TESTS'
	} else if (!mpLib) {
		cfg.singleDefines 'HOOK_GAMEDLL'
	}

	if (regamedllFixes) {
		cfg.singleDefines 'REGAMEDLL_FIXES', 'REGAMEDLL_CHECKS', 'REGAMEDLL_ADD', 'NDEBUG'
	} else {
		cfg.singleDefines 'PLAY_GAMEDLL'
	}

	ToolchainConfigUtils.apply(project, cfg, b)
	GradleCppUtils.onTasksCreated(project, 'postEvaluate', {
		postEvaluate(b)
	})
}

class RegamedllSrc {
	static void regamedll_src(def h) {
		h.regamedll_src(CppSourceSet) {

			source {
				srcDirs "engine", "dlls", "game_shared", "pm_shared", "regamedll", "public", "version"

				include "**/*.cpp"
				exclude "precompiled.cpp"
				exclude "tier0/dbg.cpp", "utlsymbol.cpp", "utlbuffer.cpp"

				if (GradleCppUtils.windows)
				{
					exclude "tier0/platform_linux.cpp"
				}
				else
				{
					exclude "tier0/platform_win32.cpp"
					exclude "classes_dummy.cpp"
				}
			}
		}
	}

	static void regamedll_pch(def h) {
		h.regamedll_pch(CppSourceSet) {
			source {
				srcDirs "regamedll"
				include "precompiled.cpp"
			}
		}
	}

	static void regamedll_hooker_src(def h) {
		h.regamedll_hooker_src(CppSourceSet) {
			source {
				srcDirs "hookers"
				include "main.cpp", "6153_hooker.cpp", "hooker.cpp", "memory.cpp", "hooker_impl.cpp", "RegameDLLRuntimeConfig.cpp"
			}
		}
	}

	static void regamedll_mp_src(def h) {
		h.regamedll_mp_src(CppSourceSet) {
			source {
				srcDirs "hookers"
				include "main_mp.cpp"
			}
		}
	}

	static void regamedll_tests_src(def h) {
		h.regamedll_tests_src(CppSourceSet) {
			source {
				srcDir "unittests"
				include "**/*.cpp"
			}
		}
	}
}

model {
	buildTypes {
		debug
		release
	}

	platforms {
		x86 {
			architecture "x86"
		}
	}

	toolChains {
		visualCpp(VisualCpp) {
		}
		icc(Icc) {
		}
	}

	flavors {
		regamedllNofixes
		regamedllFixes
	}

	components {
		regamedll_hooker_gamedll(NativeLibrarySpec) {
			targetPlatform 'x86'
			baseName 'filesystem_stdio'

			sources {
				RegamedllSrc.regamedll_pch(it)
				RegamedllSrc.regamedll_src(it)
				RegamedllSrc.regamedll_hooker_src(it)
			}

			binaries.all { NativeBinarySpec b -> project.setupToolchain(b) }
		}

		regamedll_mp_gamedll(NativeLibrarySpec) {
			targetPlatform 'x86'
			baseName GradleCppUtils.windows ? 'mp' : 'cs'
			sources {
				RegamedllSrc.regamedll_pch(it)
				RegamedllSrc.regamedll_src(it)
				RegamedllSrc.regamedll_mp_src(it)
			}
			binaries.all { NativeBinarySpec b -> project.setupToolchain(b) }
		}

		regamedll_hooker_gamedll_tests(NativeExecutableSpec) {
			targetPlatform 'x86'
			sources {
				RegamedllSrc.regamedll_pch(it)
				RegamedllSrc.regamedll_src(it)
				RegamedllSrc.regamedll_tests_src(it)
			}

			binaries.all { NativeBinarySpec b -> project.setupToolchain(b) }
		}

		regamedll_mp_gamedll_tests(NativeExecutableSpec) {
			targetPlatform 'x86'
			sources {
				RegamedllSrc.regamedll_pch(it)
				RegamedllSrc.regamedll_src(it)
				RegamedllSrc.regamedll_tests_src(it)
			}

			binaries.all { NativeBinarySpec b -> project.setupToolchain(b) }
		}
	}
}

task buildRelease {
	dependsOn binaries.withType(SharedLibraryBinarySpec).matching { SharedLibraryBinarySpec blib ->
		blib.buildable && blib.buildType.name == 'release' && !blib.name.contains('Regamedll_hooker_gamedll')
	}
}

task buildFixes {
	dependsOn binaries.withType(SharedLibraryBinarySpec).matching {
		SharedLibraryBinarySpec blib -> blib.buildable && blib.buildType.name == 'release' && blib.flavor.name == 'regamedllFixes' && blib.component.name == 'regamedll_mp_gamedll'
	}
}

gradle.taskGraph.whenReady { graph ->
	if (!graph.hasTask(buildFixes)) {
		return;
	}

	// skip all tasks with the matched substrings in the name like "test"
	def tasks = graph.getAllTasks();
	tasks.findAll { it.name.toLowerCase().contains("test") }.each { task ->
		task.enabled = false;
	}
}

task prepareDevEnvTests {
	def regamedllTests = new File(project.projectDir, '_dev/testDemos')

	inputs.files configurations.regamedll_tests.files
	outputs.dir regamedllTests

	doLast {
		regamedllTests.mkdirs()
		configurations.regamedll_tests.files.each { File f ->
			def t = zipTree(f)
			copy {
				into new File(regamedllTests, FilenameUtils.getBaseName(f.absolutePath))
				from t
			}
		}
	}
}

task prepareDevEnvGamedll << {
	['_dev/regamedll', '_dev/regamedll_mp'].each { gamedllDir ->
		def regamedllImage = new File(project.projectDir, gamedllDir)
		regamedllImage.mkdirs()
		def demoRunner = new RegamedllDemoRunner(project.configurations.regamedll_playtest_image.getFiles(), regamedllImage, null)
		demoRunner.prepareEngine()
		//demoRunner.prepareDemo()
	}
}

task prepareDevEnv {
	dependsOn prepareDevEnvGamedll, prepareDevEnvTests
}

tasks.clean.doLast {
	project.file('version/appversion.h').delete()
}

task generateAppVersion {

	RegamedllVersionInfo verInfo = (RegamedllVersionInfo) rootProject.regamedllVersionInfo
	def tplFile = project.file('version/appversion.vm')
	def renderedFile = project.file('version/appversion.h')

	inputs.file tplFile
	inputs.file project.file('gradle.properties')
	outputs.file renderedFile
	inputs.property('version', verInfo.asMavenVersion())
	inputs.property('lastCommitDate', verInfo.lastCommitDate.toString())

	doLast {
		def templateCtx = [
			verInfo: verInfo
		]

		def content = VelocityUtils.renderTemplate(tplFile, templateCtx)

		renderedFile.delete()
		renderedFile.write(content, 'utf-8')

		println 'The current ReGameDLL version is ' + verInfo.asVersion().toString() + ', maven version is ' + verInfo.asMavenVersion().toString() + ', commit id: ' + verInfo.commitID.toString() + ', commit author: ' + verInfo.authorCommit.toString() + ', url: (' + verInfo.urlCommits.toString() + ')';
	}
}