/**
	Build settings definitions.

	Copyright: © 2013-2014 rejectedsoftware e.K.
	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
	Authors: Sönke Ludwig
*/
module dub.compilers.buildsettings;

import dub.internal.vibecompat.inet.path;

import std.array : array;
import std.algorithm : filter;
import std.path : globMatch;
static if (__VERSION__ >= 2067)
	import std.typecons : BitFlags;


/// BuildPlatform specific settings, like needed libraries or additional
/// include paths.
struct BuildSettings {
	import dub.internal.vibecompat.data.serialization;

	TargetType targetType;
	string targetPath;
	string targetName;
	string workingDirectory;
	string mainSourceFile;
	string[] dflags;
	string[] lflags;
	string[] libs;
	string[] linkerFiles;
	string[] sourceFiles;
	string[] copyFiles;
	string[] versions;
	string[] debugVersions;
	string[] importPaths;
	string[] stringImportPaths;
	string[] importFiles;
	string[] stringImportFiles;
	string[] preGenerateCommands;
	string[] postGenerateCommands;
	string[] preBuildCommands;
	string[] postBuildCommands;
	@byName BuildRequirements requirements;
	@byName BuildOptions options;

	BuildSettings dup()
	const {
		BuildSettings ret;
		foreach (m; __traits(allMembers, BuildSettings)) {
			static if (is(typeof(__traits(getMember, ret, m) = __traits(getMember, this, m).dup)))
				__traits(getMember, ret, m) = __traits(getMember, this, m).dup;
			else static if (is(typeof(__traits(getMember, ret, m) = __traits(getMember, this, m))))
				__traits(getMember, ret, m) = __traits(getMember, this, m);
		}
		assert(ret.targetType == targetType);
		assert(ret.targetName == targetName);
		assert(ret.importPaths == importPaths);
		return ret;
	}

	void add(in BuildSettings bs)
	{
		addDFlags(bs.dflags);
		addLFlags(bs.lflags);
		addLibs(bs.libs);
		addLinkerFiles(bs.linkerFiles);
		addSourceFiles(bs.sourceFiles);
		addCopyFiles(bs.copyFiles);
		addVersions(bs.versions);
		addDebugVersions(bs.debugVersions);
		addImportPaths(bs.importPaths);
		addStringImportPaths(bs.stringImportPaths);
		addImportFiles(bs.importFiles);
		addStringImportFiles(bs.stringImportFiles);
		addPreGenerateCommands(bs.preGenerateCommands);
		addPostGenerateCommands(bs.postGenerateCommands);
		addPreBuildCommands(bs.preBuildCommands);
		addPostBuildCommands(bs.postBuildCommands);
	}

	void addDFlags(in string[] value...) { dflags ~= value; }
	void removeDFlags(in string[] value...) { remove(dflags, value); }
	void addLFlags(in string[] value...) { lflags ~= value; }
	void addLibs(in string[] value...) { add(libs, value); }
	void addLinkerFiles(in string[] value...) { add(linkerFiles, value); }
	void addSourceFiles(in string[] value...) { add(sourceFiles, value); }
	void prependSourceFiles(in string[] value...) { prepend(sourceFiles, value); }
	void removeSourceFiles(in string[] value...) { removePaths(sourceFiles, value); }
	void addCopyFiles(in string[] value...) { add(copyFiles, value); }
	void addVersions(in string[] value...) { add(versions, value); }
	void addDebugVersions(in string[] value...) { add(debugVersions, value); }
	void addImportPaths(in string[] value...) { add(importPaths, value); }
	void addStringImportPaths(in string[] value...) { add(stringImportPaths, value); }
	void prependStringImportPaths(in string[] value...) { prepend(stringImportPaths, value); }
	void addImportFiles(in string[] value...) { add(importFiles, value); }
	void removeImportFiles(in string[] value...) { removePaths(importFiles, value); }
	void addStringImportFiles(in string[] value...) { addSI(stringImportFiles, value); }
	void addPreGenerateCommands(in string[] value...) { add(preGenerateCommands, value, false); }
	void addPostGenerateCommands(in string[] value...) { add(postGenerateCommands, value, false); }
	void addPreBuildCommands(in string[] value...) { add(preBuildCommands, value, false); }
	void addPostBuildCommands(in string[] value...) { add(postBuildCommands, value, false); }
	void addRequirements(in BuildRequirement[] value...) { foreach (v; value) this.requirements |= v; }
	void addRequirements(in BuildRequirements value) { this.requirements |= value; }
	void addOptions(in BuildOption[] value...) { foreach (v; value) this.options |= v; }
	void addOptions(in BuildOptions value) { this.options |= value; }
	void removeOptions(in BuildOption[] value...) { foreach (v; value) this.options &= ~v; }
	void removeOptions(in BuildOptions value) { this.options &= ~value; }

	// Adds vals to arr without adding duplicates.
	private void add(ref string[] arr, in string[] vals, bool no_duplicates = true)
	{
		if (!no_duplicates) {
			arr ~= vals;
			return;
		}

		foreach (v; vals) {
			bool found = false;
			foreach (i; 0 .. arr.length)
				if (arr[i] == v) {
					found = true;
					break;
				}
			if (!found) arr ~= v;
		}
	}

	private void prepend(ref string[] arr, in string[] vals, bool no_duplicates = true)
	{
		if (!no_duplicates) {
			arr = vals ~ arr;
			return;
		}

		foreach_reverse (v; vals) {
			bool found = false;
			foreach (i; 0 .. arr.length)
				if (arr[i] == v) {
					found = true;
					break;
				}
			if (!found) arr = v ~ arr;
		}
	}

	// add string import files (avoids file name duplicates in addition to path duplicates)
	private void addSI(ref string[] arr, in string[] vals)
	{
		outer:
		foreach (v; vals) {
			auto vh = Path(v).head;
			foreach (ve; arr) {
				if (Path(ve).head == vh)
					continue outer;
			}
			arr ~= v;
		}
	}

	private void removePaths(ref string[] arr, in string[] vals)
	{
		bool matches(string s)
		{
			foreach (p; vals)
				if (Path(s) == Path(p) || globMatch(s, p))
					return true;
			return false;
		}
		arr = arr.filter!(s => !matches(s))().array();
	}

	private void remove(ref string[] arr, in string[] vals)
	{
		bool matches(string s)
		{
			foreach (p; vals)
				if (s == p)
					return true;
			return false;
		}
		arr = arr.filter!(s => !matches(s))().array();
	}
}

enum BuildSetting {
	dflags            = 1<<0,
	lflags            = 1<<1,
	libs              = 1<<2,
	sourceFiles       = 1<<3,
	copyFiles         = 1<<4,
	versions          = 1<<5,
	debugVersions     = 1<<6,
	importPaths       = 1<<7,
	stringImportPaths = 1<<8,
	options           = 1<<9,
	none = 0,
	commandLine = dflags|copyFiles,
	commandLineSeparate = commandLine|lflags,
	all = dflags|lflags|libs|sourceFiles|copyFiles|versions|debugVersions|importPaths|stringImportPaths|options,
	noOptions = all & ~options
}

enum TargetType {
	autodetect,
	none,
	executable,
	library,
	sourceLibrary,
	dynamicLibrary,
	staticLibrary,
	object
}

enum BuildRequirement {
	none = 0,                     /// No special requirements
	allowWarnings        = 1<<0,  /// Warnings do not abort compilation
	silenceWarnings      = 1<<1,  /// Don't show warnings
	disallowDeprecations = 1<<2,  /// Using deprecated features aborts compilation
	silenceDeprecations  = 1<<3,  /// Don't show deprecation warnings
	disallowInlining     = 1<<4,  /// Avoid function inlining, even in release builds
	disallowOptimization = 1<<5,  /// Avoid optimizations, even in release builds
	requireBoundsCheck   = 1<<6,  /// Always perform bounds checks
	requireContracts     = 1<<7,  /// Leave assertions and contracts enabled in release builds
	relaxProperties      = 1<<8,  /// DEPRECATED: Do not enforce strict property handling (-property)
	noDefaultFlags       = 1<<9,  /// Do not issue any of the default build flags (e.g. -debug, -w, -property etc.) - use only for development purposes
}

	struct BuildRequirements {
		import dub.internal.vibecompat.data.serialization : ignore;

		static if (__VERSION__ >= 2067) {
			@ignore BitFlags!BuildRequirement values;
			this(BuildRequirement req) { values = req; }
			deprecated("Use BuildRequirement.* instead.") {
				enum none = BuildRequirement.none;
				enum allowWarnings = BuildRequirement.allowWarnings;
				enum silenceWarnings = BuildRequirement.silenceWarnings;
				enum disallowDeprecations = BuildRequirement.disallowDeprecations;
				enum silenceDeprecations = BuildRequirement.silenceDeprecations;
				enum disallowInlining = BuildRequirement.disallowInlining;
				enum disallowOptimization = BuildRequirement.disallowOptimization;
				enum requireBoundsCheck = BuildRequirement.requireBoundsCheck;
				enum requireContracts = BuildRequirement.requireContracts;
				enum relaxProperties = BuildRequirement.relaxProperties;
				enum noDefaultFlags = BuildRequirement.noDefaultFlags;
			}
		} else {
			@ignore BuildRequirement values;
			this(BuildRequirement req) { values = req; }
			BuildRequirement[] toRepresentation()
			const {
				BuildRequirement[] ret;
				for (int f = 1; f <= BuildRequirement.max; f *= 2)
					if (values & f) ret ~= cast(BuildRequirement)f;
				return ret;
			}
			static BuildRequirements fromRepresentation(BuildRequirement[] v)
			{
				BuildRequirements ret;
				foreach (f; v) ret.values |= f;
				return ret;
			}
		}
		alias values this;
	}

enum BuildOption {
	none = 0,                     /// Use compiler defaults
	debugMode = 1<<0,             /// Compile in debug mode (enables contracts, -debug)
	releaseMode = 1<<1,           /// Compile in release mode (disables assertions and bounds checks, -release)
	coverage = 1<<2,              /// Enable code coverage analysis (-cov)
	debugInfo = 1<<3,             /// Enable symbolic debug information (-g)
	debugInfoC = 1<<4,            /// Enable symbolic debug information in C compatible form (-gc)
	alwaysStackFrame = 1<<5,      /// Always generate a stack frame (-gs)
	stackStomping = 1<<6,         /// Perform stack stomping (-gx)
	inline = 1<<7,                /// Perform function inlining (-inline)
	noBoundsCheck = 1<<8,         /// Disable all bounds checking (-noboundscheck)
	optimize = 1<<9,              /// Enable optimizations (-O)
	profile = 1<<10,              /// Emit profiling code (-profile)
	unittests = 1<<11,            /// Compile unit tests (-unittest)
	verbose = 1<<12,              /// Verbose compiler output (-v)
	ignoreUnknownPragmas = 1<<13, /// Ignores unknown pragmas during compilation (-ignore)
	syntaxOnly = 1<<14,           /// Don't generate object files (-o-)
	warnings = 1<<15,             /// Enable warnings (-wi)
	warningsAsErrors = 1<<16,     /// Treat warnings as errors (-w)
	ignoreDeprecations = 1<<17,   /// Do not warn about using deprecated features (-d)
	deprecationWarnings = 1<<18,  /// Warn about using deprecated features (-dw)
	deprecationErrors = 1<<19,    /// Stop compilation upon usage of deprecated features (-de)
	property = 1<<20,             /// DEPRECATED: Enforce property syntax (-property)
	profileGC = 1<<21,            /// Profile runtime allocations
}

	struct BuildOptions {
		import dub.internal.vibecompat.data.serialization : ignore;

		static if (__VERSION__ >= 2067) {
			@ignore BitFlags!BuildOption values;
			this(BuildOption opt) { values = opt; }
			deprecated("Use BuildOption.* instead.") {
				enum none = BuildOption.none;
				enum debugMode = BuildOption.debugMode;
				enum releaseMode = BuildOption.releaseMode;
				enum coverage = BuildOption.coverage;
				enum debugInfo = BuildOption.debugInfo;
				enum debugInfoC = BuildOption.debugInfoC;
				enum alwaysStackFrame = BuildOption.alwaysStackFrame;
				enum stackStomping = BuildOption.stackStomping;
				enum inline = BuildOption.inline;
				enum noBoundsCheck = BuildOption.noBoundsCheck;
				enum optimize = BuildOption.optimize;
				enum profile = BuildOption.profile;
				enum unittests = BuildOption.unittests;
				enum verbose = BuildOption.verbose;
				enum ignoreUnknownPragmas = BuildOption.ignoreUnknownPragmas;
				enum syntaxOnly = BuildOption.syntaxOnly;
				enum warnings = BuildOption.warnings;
				enum warningsAsErrors = BuildOption.warningsAsErrors;
				enum ignoreDeprecations = BuildOption.ignoreDeprecations;
				enum deprecationWarnings = BuildOption.deprecationWarnings;
				enum deprecationErrors = BuildOption.deprecationErrors;
				enum property = BuildOption.property;
			}
		} else {
			@ignore BuildOption values;
			this(BuildOption opt) { values = opt; }
			BuildOption[] toRepresentation()
			const {
				BuildOption[] ret;
				for (int f = 1; f <= BuildOption.max; f *= 2)
					if (values & f) ret ~= cast(BuildOption)f;
				return ret;
			}
			static BuildOptions fromRepresentation(BuildOption[] v)
			{
				BuildOptions ret;
				foreach (f; v) ret.values |= f;
				return ret;
			}
		}

		alias values this;
	}
