/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "config.h"
#include "cmake.h"

#include <algorithm>
#include <cstring>
#include <fstream>
#include <iterator>

namespace CreateProjectTool {

CMakeProvider::CMakeProvider(StringList &global_warnings, std::map<std::string, StringList> &project_warnings, const int version)
	: ProjectProvider(global_warnings, project_warnings, version) {
}

const CMakeProvider::Library *CMakeProvider::getLibraryFromFeature(const char *feature, bool useSDL2) const {
	static const Library s_libraries[] = {
		{ "sdl",       kSDLVersion1,   "FindSDL",      "SDL",      "SDL_INCLUDE_DIR",       "SDL_LIBRARY",         0            },
		{ "sdl",       kSDLVersion2,   0,              "SDL2",     "SDL2_INCLUDE_DIRS",     "SDL2_LIBRARIES",      0            },
		{ "freetype",  kSDLVersionAny, "FindFreetype", "Freetype", "FREETYPE_INCLUDE_DIRS", "FREETYPE_LIBRARIES",  0            },
		{ "libz",      kSDLVersionAny, "FindZLIB",     "ZLIB",     "ZLIB_INCLUDE_DIRS",     "ZLIB_LIBRARIES",      0            },
		{ "png",       kSDLVersionAny, "FindPNG",      "PNG",      "PNG_INCLUDE_DIRS",      "PNG_LIBRARIES",       0            },
		{ "jpeg",      kSDLVersionAny, "FindJPEG",     "JPEG",     "JPEG_INCLUDE_DIRS",     "JPEG_LIBRARIES",      0            },
		{ "mpeg2",     kSDLVersionAny, "FindMPEG2",    "MPEG2",    "MPEG2_INCLUDE_DIRS",    "MPEG2_mpeg2_LIBRARY", 0            },
		{ "flac",      kSDLVersionAny, 0,              0,          0,                       0,                     "FLAC"       },
		{ "mad",       kSDLVersionAny, 0,              0,          0,                       0,                     "mad"        },
		{ "vorbis",    kSDLVersionAny, 0,              0,          0,                       0,                     "vorbisfile vorbis ogg" },
		{ "theora",    kSDLVersionAny, 0,              0,          0,                       0,                     "theoradec"  },
		{ "fluidsynth",kSDLVersionAny, 0,              0,          0,                       0,                     "fluidsynth" },
		{ "faad",      kSDLVersionAny, 0,              0,          0,                       0,                     "faad"       },
		{ "libcurl",   kSDLVersionAny, "FindCURL",     "CURL",     "CURL_INCLUDE_DIRS",     "CURL_LIBRARIES",      0            },
		{ "sdlnet",    kSDLVersion1,   "FindSDL_net",  "SDL_net",  "SDL_NET_INCLUDE_DIRS",  "SDL_NET_LIBRARIES",   0            },
		{ "sdlnet",    kSDLVersion2,   0,              0,          0,                       0,                     "SDL2_net"   }
	};

	for (unsigned int i = 0; i < sizeof(s_libraries) / sizeof(s_libraries[0]); i++) {
		bool matchingSDL = (s_libraries[i].sdlVersion == kSDLVersionAny)
		                   || (useSDL2 && s_libraries[i].sdlVersion == kSDLVersion2)
		                   || (!useSDL2 && s_libraries[i].sdlVersion == kSDLVersion1);
		if (std::strcmp(feature, s_libraries[i].feature) == 0 && matchingSDL) {
			return &s_libraries[i];
		}
	}

	return 0;
}

void CMakeProvider::createWorkspace(const BuildSetup &setup) {
	std::string filename = setup.outputDir + "/CMakeLists.txt";
	std::ofstream workspace(filename.c_str());
	if (!workspace)
		error("Could not open \"" + filename + "\" for writing");

	workspace << "cmake_minimum_required(VERSION 3.2)\n"
			"project(" << setup.projectDescription << ")\n\n";

	workspace << "# Define the engines and subengines\n";
	writeEngines(setup, workspace);
	writeSubEngines(setup, workspace);
	workspace << "# Generate options for the engines\n";
	writeEngineOptions(workspace);

	workspace << "include_directories(${" << setup.projectDescription << "_SOURCE_DIR} ${" << setup.projectDescription << "_SOURCE_DIR}/engines\n"
			"$ENV{"<<LIBS_DEFINE<<"}/include)\n\n";

	workspace << "# Libraries and features\n";
	writeFeatureLibSearch(setup, workspace, "sdl");
	for (FeatureList::const_iterator i = setup.features.begin(), end = setup.features.end(); i != end; ++i) {
		if (!i->enable || featureExcluded(i->name)) continue;

		writeFeatureLibSearch(setup, workspace, i->name);
		workspace << "add_definitions(-D" << i->define << ")\n";
	}
	workspace << "\n";

	writeWarnings(workspace);
	writeDefines(setup, workspace);
	workspace << "# Generate definitions for the engines\n";
	writeEngineDefinitions(workspace);

	workspace << "# Generate \"engines/plugins_table.h\"\n";
	writeGeneratePluginsTable(workspace);
}

void CMakeProvider::writeFeatureLibSearch(const BuildSetup &setup, std::ofstream &workspace, const char *feature) const {
	const Library *library = getLibraryFromFeature(feature, setup.useSDL2);
	if (library) {
		if (library->module) {
			workspace << "Include(" << library->module << ")\n";
		}
		if (library->package) {
			workspace << "Find_Package(" << library->package << " REQUIRED)\n";
		}
		if (library->includesVar) {
			workspace << "include_directories(${" << library->includesVar << "})\n";
		}
	}
}

void CMakeProvider::writeEngines(const BuildSetup &setup, std::ofstream &workspace) const {
	workspace << "set(ENGINES";
	for (EngineDescList::const_iterator i = setup.engines.begin(), end = setup.engines.end(); i != end; ++i) {
		// We ignore all sub engines here because they require special handling.
		if (!i->enable || isSubEngine(i->name, setup.engines)) {
			continue;
		}

		std::string engineName;
		std::transform(i->name.begin(), i->name.end(), std::back_inserter(engineName), toupper);

		workspace << " " << engineName;
	}
	workspace << ")\n";
}

void CMakeProvider::writeSubEngines(const BuildSetup &setup, std::ofstream &workspace) const {
	for (EngineDescList::const_iterator i = setup.engines.begin(), end = setup.engines.end(); i != end; ++i) {
		// We ignore all sub engines here because they are handled in the inner loop
		if (!i->enable || isSubEngine(i->name, setup.engines) || i->subEngines.empty()) {
			continue;
		}

		std::string engineName;
		std::transform(i->name.begin(), i->name.end(), std::back_inserter(engineName), toupper);

		workspace << "set(SUB_ENGINES_" << engineName;
		for (StringList::const_iterator j = i->subEngines.begin(), subEnd = i->subEngines.end(); j != subEnd; ++j) {
			const EngineDesc &subEngine = findEngineDesc(*j, setup.engines);
			if (!subEngine.enable) continue;

			std::string subEngineName;
			std::transform(j->begin(), j->end(), std::back_inserter(subEngineName), toupper);

			workspace << " " << subEngineName;
		}
		workspace << ")\n";
	}

	workspace << "\n";
}

void CMakeProvider::createProjectFile(const std::string &name, const std::string &, const BuildSetup &setup, const std::string &moduleDir,
                                           const StringList &includeList, const StringList &excludeList) {

	const std::string projectFile = setup.outputDir + "/CMakeLists.txt";
	std::ofstream project(projectFile.c_str(), std::ofstream::out | std::ofstream::app);
	if (!project)
		error("Could not open \"" + projectFile + "\" for writing");

	if (name == setup.projectName) {
		project << "add_executable(" << name << "\n";
	} else {
		std::string engineName;
		std::transform(name.begin(), name.end(), std::back_inserter(engineName), toupper);

		project << "if (ENABLE_" << engineName << ")\n";
		project << "add_library(" << name << "\n";
	}

	std::string modulePath;
	if (!moduleDir.compare(0, setup.srcDir.size(), setup.srcDir)) {
		modulePath = moduleDir.substr(setup.srcDir.size());
		if (!modulePath.empty() && modulePath.at(0) == '/')
			modulePath.erase(0, 1);
	}

	if (modulePath.size())
		addFilesToProject(moduleDir, project, includeList, excludeList, setup.filePrefix + '/' + modulePath);
	else
		addFilesToProject(moduleDir, project, includeList, excludeList, setup.filePrefix);


	project << ")\n";
	if (name != setup.projectName) {
		project << "endif()\n";
	}

	project << "# Libraries\n";
	if (name == setup.projectName) {
		const Library *sdlLibrary = getLibraryFromFeature("sdl", setup.useSDL2);
		project << "target_link_libraries(" << name << " ${" << sdlLibrary->librariesVar << "})\n";

		for (FeatureList::const_iterator i = setup.features.begin(), end = setup.features.end(); i != end; ++i) {
			if (!i->enable || featureExcluded(i->name)) continue;

			const Library *library = getLibraryFromFeature(i->name, setup.useSDL2);
			if (!library) continue;

			if (library->librariesVar) {
				project << "target_link_libraries(" << name << " ${" << library->librariesVar << "})\n";
			} else {
				project << "target_link_libraries(" << name << " " << library->libraries << ")\n";
			}
		}
		project << "if (WIN32)\n";
		project << "    target_sources(" << name << " PUBLIC dists/" << name << ".rc)\n";
		project << "    target_link_libraries(" << name << " winmm)\n";
		project << "endif()\n";
		project << "\n";

		project << "# Engines libraries handling\n";
		writeEnginesLibrariesHandling(setup, project);

		project << "set_property(TARGET " << name << " PROPERTY CXX_STANDARD 11)\n";
		project << "set_property(TARGET " << name << " PROPERTY CXX_STANDARD_REQUIRED ON)\n";
	}
}

void CMakeProvider::writeWarnings(std::ofstream &output) const {
	output << "SET (CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS}";
	for (StringList::const_iterator i = _globalWarnings.begin(); i != _globalWarnings.end(); ++i) {
		output << " " << *i;
	}
	output << "\")\n";
}

void CMakeProvider::writeDefines(const BuildSetup &setup, std::ofstream &output) const {
	output << "if (WIN32)\n";
	output << "    add_definitions(-DWIN32)\n";
	output << "else()\n";
	output << "    add_definitions(-DPOSIX)\n";
	output << "endif()\n";

	output << "if (CMAKE_SIZEOF_VOID_P MATCHES 8)\n";
	output << "    add_definitions(-DSCUMM_64BITS)\n";
	output << "endif()\n";

	output << "add_definitions(-DSDL_BACKEND)\n\n";
}

void CMakeProvider::writeFileListToProject(const FileNode &dir, std::ofstream &projectFile, const int indentation,
                                                const StringList &duplicate, const std::string &objPrefix, const std::string &filePrefix) {

	for (FileNode::NodeList::const_iterator i = dir.children.begin(); i != dir.children.end(); ++i) {
		const FileNode *node = *i;

		if (!node->children.empty()) {
			writeFileListToProject(*node, projectFile, indentation + 1, duplicate, objPrefix + node->name + '_', filePrefix + node->name + '/');
		} else {
			std::string name, ext;
			splitFilename(node->name, name, ext);
			projectFile << "\t" << filePrefix + node->name << "\n";
		}
	}
}

const char *CMakeProvider::getProjectExtension() {
	return ".txt";
}

void CMakeProvider::writeEngineOptions(std::ofstream &workspace) const {
	workspace << "foreach(ENGINE IN LISTS ENGINES)\n";
	workspace << "    OPTION(ENABLE_${ENGINE} \"Enable ${ENGINE}\" ON)\n";
	workspace << "endforeach(ENGINE)\n\n";
}

void CMakeProvider::writeGeneratePluginsTable(std::ofstream &workspace) const {
	workspace << "file(REMOVE \"engines/plugins_table.h\")\n";
	workspace << "file(APPEND \"engines/plugins_table.h\" \"/* This file is automatically generated by CMake */\\n\")\n";
	workspace << "foreach(ENGINE IN LISTS ENGINES)\n";
	workspace << "    if (ENABLE_${ENGINE})\n";
	workspace << "        file(APPEND \"engines/plugins_table.h\" \"#if PLUGIN_ENABLED_STATIC(${ENGINE})\\n\")\n";
	workspace << "        file(APPEND \"engines/plugins_table.h\" \"LINK_PLUGIN(${ENGINE})\\n\")\n";
	workspace << "        file(APPEND \"engines/plugins_table.h\" \"#endif\\n\")\n";
	workspace << "    endif()\n";
	workspace << "endforeach()\n\n";
}

void CMakeProvider::writeEnginesLibrariesHandling(const BuildSetup &setup, std::ofstream &workspace) const {
	workspace << "foreach(ENGINE IN LISTS ENGINES)\n";
	workspace << "    if (ENABLE_${ENGINE})\n";
	workspace << "        string(TOLOWER ${ENGINE} ENGINE_LIB)\n\n";
	workspace << "        # Enable C++11\n";
	workspace << "        set_property(TARGET ${ENGINE_LIB} PROPERTY CXX_STANDARD 11)\n";
	workspace << "        set_property(TARGET ${ENGINE_LIB} PROPERTY CXX_STANDARD_REQUIRED ON)\n\n";
	workspace << "        # Link against the engine\n";
	workspace << "        target_link_libraries("<< setup.projectName <<" ${ENGINE_LIB})\n";
	workspace << "    endif()\n";
	workspace << "endforeach()\n\n";
}

void CMakeProvider::writeEngineDefinitions(std::ofstream &workspace) const {
	workspace << "foreach(ENGINE IN LISTS ENGINES)\n";
	workspace << "    if (ENABLE_${ENGINE})\n";
	workspace << "        add_definitions(-DENABLE_${ENGINE})\n";
	workspace << "        foreach(SUB_ENGINE IN LISTS SUB_ENGINES_${ENGINE})\n";
	workspace << "            add_definitions(-DENABLE_${SUB_ENGINE})\n";;
	workspace << "        endforeach(SUB_ENGINE)\n";
	workspace << "    endif()\n";
	workspace << "endforeach()\n\n";
}

bool CMakeProvider::featureExcluded(const char *name) const {
	return std::strcmp(name, "nasm") == 0 ||
			std::strcmp(name, "updates") == 0 ; // NASM is not supported for now
}

const EngineDesc &CMakeProvider::findEngineDesc(const std::string &name, const EngineDescList &engines) const {
	for (EngineDescList::const_iterator i = engines.begin(), end = engines.end(); i != end; ++i) {
		if (i->name == name) {
			return *i;
		}

	}

	error("Unable to find requested engine");
}

} // End of CreateProjectTool namespace
