// Copyright (c) Wojciech Figat. All rights reserved.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Flax.Build;
using Flax.Build.Platforms;

namespace Flax.Deps.Dependencies
{
    /// <summary>
    /// NVIDIA NvCloth
    /// </summary>
    /// <seealso cref="Flax.Deps.Dependency" />
    class NvCloth : Dependency
    {
        private string root, nvCloth;

        /// <inheritdoc />
        public override void Build(BuildOptions options)
        {
            root = options.IntermediateFolder;
            nvCloth = Path.Combine(root, "NvCloth");

            // Get the source
            CloneGitRepoSingleBranch(root, "https://github.com/FlaxEngine/NvCloth.git", "master");

            // Patch the CMakeLists.txt to support custom compilation flags
            foreach (var os in new[] { "android", "ios", "linux", "mac", "windows", })
            {
                var filePath = Path.Combine(nvCloth, "compiler", "cmake", os, "CMakeLists.txt");
                var appendLine = "SET(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} ${NVCLOTH_CXX_FLAGS}\")";
                if (!File.ReadAllText(filePath).Contains(appendLine))
                    File.AppendAllText(filePath, Environment.NewLine + appendLine + Environment.NewLine);
            }

            foreach (var platform in options.Platforms)
            {
                foreach (var architecture in options.Architectures)
                {
                    BuildStarted(platform, architecture);
                    switch (platform)
                    {
                    case TargetPlatform.Windows:
                        Build(options, platform, architecture);
                        break;
                    case TargetPlatform.XboxOne:
                    case TargetPlatform.XboxScarlett:
                        Build(options, platform, TargetArchitecture.x64);
                        break;
                    case TargetPlatform.PS4:
                    case TargetPlatform.PS5:
                        Utilities.DirectoryCopy(Path.Combine(GetBinariesFolder(options, platform), "Data", "NvCloth"), root, true, true);
                        Build(options, platform, TargetArchitecture.x64);
                        break;
                    case TargetPlatform.Switch:
                        Utilities.DirectoryCopy(Path.Combine(GetBinariesFolder(options, platform), "Data", "NvCloth"), root, true, true);
                        Build(options, platform, TargetArchitecture.ARM64);
                        break;
                    case TargetPlatform.Android:
                        Build(options, platform, TargetArchitecture.ARM64);
                        break;
                    case TargetPlatform.Mac:
                        Build(options, platform, architecture);
                        break;
                    case TargetPlatform.iOS:
                        Build(options, platform, TargetArchitecture.ARM64);
                        break;
                    case TargetPlatform.Linux:
                        Build(options, platform, architecture);
                        break;
                    case TargetPlatform.Web:
                        Utilities.DirectoryCopy(Path.Combine(GetBinariesFolder(options, platform), "Data", "NvCloth"), root, true, true);
                        Build(options, platform, TargetArchitecture.x86);
                        break;
                    }
                }
            }

            // Copy header files
            var dstIncludePath = Path.Combine(options.ThirdPartyFolder, "NvCloth");
            Directory.GetFiles(dstIncludePath, "*.h", SearchOption.AllDirectories).ToList().ForEach(File.Delete);
            Utilities.FileCopy(Path.Combine(nvCloth, "license.txt"), Path.Combine(dstIncludePath, "License.txt"));
            Utilities.DirectoryCopy(Path.Combine(nvCloth, "include", "NvCloth"), dstIncludePath, true, true, "*.h");
            Utilities.DirectoryCopy(Path.Combine(nvCloth, "extensions", "include"), dstIncludePath, true, true, "*.h");
        }

        private void Build(BuildOptions options, TargetPlatform platform, TargetArchitecture architecture)
        {
            // Peek options
            var binariesPrefix = string.Empty;
            var binariesPostfix = string.Empty;
            var cmakeArgs = "-DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DNV_CLOTH_ENABLE_DX11=0 -DNV_CLOTH_ENABLE_CUDA=0 -DPX_GENERATE_GPU_PROJECTS=0";
            var cmakeName = string.Empty;
            var buildFolder = Path.Combine(nvCloth, "compiler", platform.ToString() + '_' + architecture.ToString());
            var envVars = new Dictionary<string, string>();
            envVars["GW_DEPS_ROOT"] = root;
            envVars["CMAKE_BUILD_PARALLEL_LEVEL"] = CmakeBuildParallel;
            switch (platform)
            {
            case TargetPlatform.Windows:
            case TargetPlatform.XboxOne:
            case TargetPlatform.XboxScarlett:
                cmakeArgs += " -DTARGET_BUILD_PLATFORM=windows -DSTATIC_WINCRT=0";
                cmakeName = "windows";
                binariesPostfix = "_x64";
                break;
            case TargetPlatform.PS4:
                cmakeArgs += " -DTARGET_BUILD_PLATFORM=ps4";
                cmakeArgs += $" -DCMAKE_TOOLCHAIN_FILE=\"{Path.Combine(nvCloth, "Externals/CMakeModules/ps4/PS4Toolchain.txt").Replace('\\', '/')}\"";
                cmakeName = "ps4";
                binariesPrefix = "lib";
                break;
            case TargetPlatform.PS5:
                cmakeArgs += " -DTARGET_BUILD_PLATFORM=ps5";
                cmakeArgs += $" -DCMAKE_TOOLCHAIN_FILE=\"{Path.Combine(nvCloth, "Externals/CMakeModules/ps5/PS5Toolchain.txt").Replace('\\', '/')}\"";
                cmakeName = "ps5";
                binariesPrefix = "lib";
                break;
            case TargetPlatform.Switch:
                cmakeArgs += " -DTARGET_BUILD_PLATFORM=NX64";
                cmakeName = "switch";
                binariesPrefix = "lib";
                envVars.Add("NintendoSdkRoot", Sdk.Get("SwitchSdk").RootPath + '\\');
                break;
            case TargetPlatform.Android:
                cmakeArgs += " -DTARGET_BUILD_PLATFORM=android";
                cmakeName = "android";
                binariesPrefix = "lib";
                if (AndroidNdk.Instance.IsValid)
                {
                    envVars.Add("ANDROID_NDK_HOME", AndroidNdk.Instance.RootPath);
                    envVars.Add("PM_ANDROIDNDK_PATH", AndroidNdk.Instance.RootPath);
                }
                break;
            case TargetPlatform.Mac:
                cmakeArgs += " -DTARGET_BUILD_PLATFORM=mac -DNVCLOTH_CXX_FLAGS=\"-Wno-error=poison-system-directories -Wno-error=missing-include-dirs\"";
                cmakeName = "mac";
                binariesPrefix = "lib";
                break;
            case TargetPlatform.iOS:
                cmakeArgs += " -DTARGET_BUILD_PLATFORM=ios";
                cmakeName = "ios";
                binariesPrefix = "lib";
                break;
            case TargetPlatform.Linux:
                cmakeArgs += " -DTARGET_BUILD_PLATFORM=linux -DNVCLOTH_CXX_FLAGS=\"-Wno-error=poison-system-directories -Wno-error=missing-include-dirs\"";
                cmakeName = "linux";
                binariesPrefix = "lib";
                envVars.Add("CC", "clang-" + LinuxConfiguration.ClangMinVer);
                envVars.Add("CXX", "clang++-" + LinuxConfiguration.ClangMinVer);
                break;
            case TargetPlatform.Web:
                cmakeArgs += " -DTARGET_BUILD_PLATFORM=web";
                cmakeArgs += $" -DCMAKE_TOOLCHAIN_FILE=\"{EmscriptenSdk.Instance.CMakeToolchainPath}\"";
                if (WebConfiguration.Threads)
                    cmakeArgs += " -DNVCLOTH_CXX_FLAGS=\"-pthread\"";
                cmakeName = "web";
                binariesPrefix = "lib";

                // Disable SIMD and fix missing symbols during linking
                Utilities.ReplaceInFile(Path.Combine(nvCloth, "src/NvSimd/NvSimdTypes.h"), "(__x86_64__) || PX_EMSCRIPTEN", "(__x86_64__) // || PX_EMSCRIPTEN");
                break;
            default: throw new InvalidPlatformException(platform);
            }
            var cmakeFolder = Path.Combine(nvCloth, "compiler", "cmake", cmakeName);

            // Setup build environment variables for the build system
            switch (BuildPlatform)
            {
            case TargetPlatform.Windows:
            {
                GetMsBuildForPlatform(platform, out var vsVersion, out var msBuild);
                if (File.Exists(msBuild))
                {
                    envVars.Add("PATH", Path.GetDirectoryName(msBuild));
                }
                break;
            }
            }

            // Print the NvCloth version
            Log.Info($"Building {File.ReadAllLines(Path.Combine(root, "README.md"))[0].Trim()} to {platform} {architecture}");

            // Generate project files
            var config = "Release";
            SetupDirectory(buildFolder, false);
            SetupDirectory(Path.Combine(root, "PxShared/src/foundation/include"), false);
            Utilities.FileDelete(Path.Combine(cmakeFolder, "CMakeCache.txt"));
            cmakeArgs += $" -DPX_STATIC_LIBRARIES=1 -DPX_OUTPUT_DLL_DIR=\"{Path.Combine(buildFolder, "bin")}\" -DPX_OUTPUT_LIB_DIR=\"{Path.Combine(buildFolder, "lib")}\" -DPX_OUTPUT_EXE_DIR=\"{Path.Combine(buildFolder, "bin")}\"";
            RunCmake(cmakeFolder, platform, architecture, $" -DCMAKE_BUILD_TYPE={config} -DBUILD_SHARED_LIBS=OFF " + cmakeArgs, envVars);

            // Run build
            BuildCmake(cmakeFolder, config, envVars);

            // Deploy binaries
            var libs = new[]
            {
                "NvCloth",
            };
            var dstBinaries = GetThirdPartyFolder(options, platform, architecture);
            var srcBinaries = Path.Combine(buildFolder, "lib");
            var platformObj = Platform.GetPlatform(platform);
            var binariesExtension = platformObj.StaticLibraryFileExtension;
            foreach (var lib in libs)
            {
                var filename = binariesPrefix + lib + binariesPostfix + binariesExtension;
                Utilities.FileCopy(Path.Combine(srcBinaries, filename), Path.Combine(dstBinaries, filename));

                var filenamePdb = Path.ChangeExtension(filename, "pdb");
                if (File.Exists(Path.Combine(srcBinaries, filenamePdb)))
                    Utilities.FileCopy(Path.Combine(srcBinaries, filenamePdb), Path.Combine(dstBinaries, filenamePdb));
            }
        }
    }
}
