using System;
using HybridCLR.Editor.Installer;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using System.Reflection;
using HybridCLR.Editor.Settings;
#if (UNITY_2020 || UNITY_2021) && (UNITY_IOS || UNITY_TVOS)
using UnityEditor.Build;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
using UnityEngine;

namespace HybridCLR.Editor.BuildProcessors
{
    public static class AddLil2cppSourceCodeToXcodeproj2020Or2021
    {

        [PostProcessBuild]
        public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
        {
            if (!HybridCLRSettings.Instance.enable)
                return;
            /*
             *  1. 生成lump，并且添加到工程
                3. 将libil2cpp目录复制到 Library/. 删除旧的. search paths里修改 libil2cpp/include为libil2cpp
                3. Libraries/bdwgc/include -> Libraries/external/bdwgc/include
                4. 将external目录复制到 Library/external。删除旧目录
                5. 将Library/external/baselib/Platforms/OSX改名为 IOS 全大写
                6. 将 external/zlib下c 文件添加到工程
                7. 移除libil2cpp.a
                8. Include path add libil2cpp/os/ClassLibraryPAL/brotli/include
                9. add external/xxHash
             */

            string pbxprojFile = BuildProcessorUtil.GetXcodeProjectFile(pathToBuiltProject);
            string srcLibil2cppDir = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp";
            string dstLibil2cppDir = $"{pathToBuiltProject}/Libraries/libil2cpp";
            string lumpDir = $"{pathToBuiltProject}/Libraries/lumps";
            string srcExternalDir = $"{SettingsUtil.LocalIl2CppDir}/external";
            string dstExternalDir = $"{pathToBuiltProject}/Libraries/external";
            //RemoveExternalLibil2cppOption(srcExternalDir, dstExternalDir);
            CopyLibil2cppToXcodeProj(srcLibil2cppDir, dstLibil2cppDir);
            CopyExternalToXcodeProj(srcExternalDir, dstExternalDir);
            var lumpFiles = CreateLumps(dstLibil2cppDir, lumpDir);
            var extraSources = GetExtraSourceFiles(dstExternalDir, dstLibil2cppDir);
            var cflags = new List<string>()
            {
                "-DIL2CPP_MONO_DEBUGGER_DISABLED",
            };
            ModifyPBXProject(pathToBuiltProject, pbxprojFile, lumpFiles, extraSources, cflags);
        }

        private static string GetRelativePathFromProj(string path)
        {
            return path.Substring(path.IndexOf("Libraries", StringComparison.Ordinal)).Replace('\\', '/');
        }

        private static void ModifyPBXProject(string pathToBuiltProject, string pbxprojFile, List<LumpFile> lumpFiles, List<string> extraFiles, List<string> cflags)
        {
            var proj = new PBXProject();
            proj.ReadFromFile(pbxprojFile);
            string targetGUID = proj.GetUnityFrameworkTargetGuid();
            // 移除旧的libil2cpp.a
            var libil2cppGUID = proj.FindFileGuidByProjectPath("Libraries/libil2cpp.a");
            if (!string.IsNullOrEmpty(libil2cppGUID))
            {
                proj.RemoveFileFromBuild(targetGUID, libil2cppGUID);
                proj.RemoveFile(libil2cppGUID);
                File.Delete(Path.Combine(pathToBuiltProject, "Libraries", "libil2cpp.a"));
            }

            //var lumpGroupGuid = proj.AddFile("Lumps", $"Classes/Lumps", PBXSourceTree.Group);

            foreach (var lumpFile in lumpFiles)
            {
                string lumpFileName = Path.GetFileName(lumpFile.lumpFile);
                string projPathOfFile = $"Classes/Lumps/{lumpFileName}";
                string relativePathOfFile = GetRelativePathFromProj(lumpFile.lumpFile);
                string lumpGuid = proj.FindFileGuidByProjectPath(projPathOfFile);
                if (!string.IsNullOrEmpty(lumpGuid))
                {
                    proj.RemoveFileFromBuild(targetGUID, lumpGuid);
                    proj.RemoveFile(lumpGuid);
                }
                lumpGuid = proj.AddFile(relativePathOfFile, projPathOfFile, PBXSourceTree.Source);
                proj.AddFileToBuild(targetGUID, lumpGuid);
            }

            foreach (var extraFile in extraFiles)
            {
                string projPathOfFile = $"Classes/Extrals/{Path.GetFileName(extraFile)}";
                string extraFileGuid = proj.FindFileGuidByProjectPath(projPathOfFile);
                if (!string.IsNullOrEmpty(extraFileGuid))
                {
                    proj.RemoveFileFromBuild(targetGUID, extraFileGuid);
                    proj.RemoveFile(extraFileGuid);
                    //Debug.LogWarning($"remove exist extra file:{projPathOfFile} guid:{extraFileGuid}");
                }
                var lumpGuid = proj.AddFile(GetRelativePathFromProj(extraFile), projPathOfFile, PBXSourceTree.Source);
                proj.AddFileToBuild(targetGUID, lumpGuid);
            }

            foreach(var configName in proj.BuildConfigNames())
            {
                //Debug.Log($"build config:{bcn}");
                string configGuid = proj.BuildConfigByName(targetGUID, configName);
                string headerSearchPaths = "HEADER_SEARCH_PATHS";
                string hspProp = proj.GetBuildPropertyForConfig(configGuid, headerSearchPaths);
                //Debug.Log($"config guid:{configGuid} prop:{hspProp}");
                string newPro = hspProp.Replace("libil2cpp/include", "libil2cpp")
                    .Replace("Libraries/bdwgc", "Libraries/external/bdwgc");

                if (!newPro.Contains("Libraries/libil2cpp/os/ClassLibraryPAL/brotli/include"))
                {
                    newPro += " $(SRCROOT)/Libraries/libil2cpp/os/ClassLibraryPAL/brotli/include";
                }
                if (!newPro.Contains("Libraries/external/xxHash"))
                {
                    newPro += " $(SRCROOT)/Libraries/external/xxHash";
                }
                newPro += " $(SRCR00T)/Libraries/external/mono";
                //Debug.Log($"config:{bcn} new prop:{newPro}");
                proj.SetBuildPropertyForConfig(configGuid, headerSearchPaths, newPro);

                string cflagKey = "OTHER_CFLAGS";
                string cfProp = proj.GetBuildPropertyForConfig(configGuid, cflagKey);
                foreach (var flag in cflags)
                {
                    if (!cfProp.Contains(flag))
                    {
                        cfProp += " " + flag;
                    }
                }
                if (configName.Contains("Debug") && !cfProp.Contains("-DIL2CPP_DEBUG="))
                {
                    cfProp += " -DIL2CPP_DEBUG=1 -DDEBUG=1";
                }
                proj.SetBuildPropertyForConfig(configGuid, cflagKey, cfProp);

            }
            proj.WriteToFile(pbxprojFile);
        }

        private static void CopyLibil2cppToXcodeProj(string srcLibil2cppDir, string dstLibil2cppDir)
        {
            BashUtil.RemoveDir(dstLibil2cppDir);
            BashUtil.CopyDir(srcLibil2cppDir, dstLibil2cppDir, true);
        }


        private static void CopyExternalToXcodeProj(string srcExternalDir, string dstExternalDir)
        {
            BashUtil.RemoveDir(dstExternalDir);
            BashUtil.CopyDir(srcExternalDir, dstExternalDir, true);

            string baselibPlatfromsDir = $"{dstExternalDir}/baselib/Platforms";
            BashUtil.RemoveDir($"{baselibPlatfromsDir}/IOS");
            BashUtil.CopyDir($"{baselibPlatfromsDir}/OSX", $"{baselibPlatfromsDir}/IOS", true);
        }

        class LumpFile
        {
            public List<string> cppFiles = new List<string>();

            public readonly string lumpFile;

            public readonly string il2cppConfigFile;

            public LumpFile(string lumpFile, string il2cppConfigFile)
            {
                this.lumpFile = lumpFile;
                this.il2cppConfigFile = il2cppConfigFile;
                this.cppFiles.Add(il2cppConfigFile);
            }

            public void SaveFile()
            {
                var lumpFileContent = new List<string>();
                foreach (var file in cppFiles)
                {
                    lumpFileContent.Add($"#include \"{GetRelativePathFromProj(file)}\"");
                }
                File.WriteAllLines(lumpFile, lumpFileContent, Encoding.UTF8);
                Debug.Log($"create lump file:{lumpFile}");
            }
        }

        private static List<LumpFile> CreateLumps(string libil2cppDir, string outputDir)
        {
            BashUtil.RecreateDir(outputDir);

            string il2cppConfigFile = $"{libil2cppDir}/il2cpp-config.h";
            var lumpFiles = new List<LumpFile>();
            int lumpFileIndex = 0;
            foreach (var cppDir in Directory.GetDirectories(libil2cppDir, "*", SearchOption.AllDirectories).Concat(new string[] {libil2cppDir}))
            {
                var lumpFile = new LumpFile($"{outputDir}/lump_{Path.GetFileName(cppDir)}_{lumpFileIndex}.cpp", il2cppConfigFile);
                foreach (var file in Directory.GetFiles(cppDir, "*.cpp", SearchOption.TopDirectoryOnly))
                {
                    lumpFile.cppFiles.Add(file);
                }
                lumpFile.SaveFile();
                lumpFiles.Add(lumpFile);
                ++lumpFileIndex;
            }

            var mmFiles = Directory.GetFiles(libil2cppDir, "*.mm", SearchOption.AllDirectories);
            if (mmFiles.Length > 0)
            {
                var lumpFile = new LumpFile($"{outputDir}/lump_mm.mm", il2cppConfigFile);
                foreach (var file in mmFiles)
                {
                    lumpFile.cppFiles.Add(file);
                }
                lumpFile.SaveFile();
                lumpFiles.Add(lumpFile);
            }
            return lumpFiles;
        }

        private static List<string> GetExtraSourceFiles(string externalDir, string libil2cppDir)
        {
            var files = new List<string>();
            foreach (string extraDir in new string[]
            {
                $"{externalDir}/zlib",
                $"{externalDir}/xxHash",
                $"{libil2cppDir}/os/ClassLibraryPAL/brotli",
            })
            {
                if (!Directory.Exists(extraDir))
                {
                    continue;
                }
                files.AddRange(Directory.GetFiles(extraDir, "*.c", SearchOption.AllDirectories));
            }
            return files;
        }
    }
}
#endif