using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.Text.RegularExpressions;
using System.Linq;
using UnityEngine;

namespace CommonUtil
{
    public class SvnUtil
    {
        public static string svn_tmp_dir = Path.Combine(Directory.GetCurrentDirectory(), "Temp", "_svn_tmp_");
        public static bool haveAddEnv = false;
        public static string auth = "";
        public static void addEnv()
        {
            if (haveAddEnv)
            {
                return;
            }
            if (Application.platform == RuntimePlatform.WindowsEditor)
            {
                var envPath = Path.Combine(Directory.GetCurrentDirectory(), "ToolsExternal", "svn_cmdline");
                var path = System.Environment.GetEnvironmentVariable("PATH");
                System.Environment.SetEnvironmentVariable("PATH", envPath + Path.PathSeparator + path);
            }
            haveAddEnv = true;
        }
        public static CmdResult exec(string args, string workPath = "")
        {
            addEnv();
            args += auth;
            return CmdUtil.Cmd(getSvn(), args, workPath);
        }
        public static void commit(string path, string msg = "")
        {
            if (msg == "")
            {
                msg = "commit:" + path;
            }
            var args = string.Format("commit {0} -m \"{1}\"", path, msg);
            Debug.Log(exec(args));
        }
        public static void commitList(List<string> paths, string msg = "")
        {
            var str = string.Join(" ", paths.ToArray());
            if (msg == "")
            {
                msg = "commit:" + str;
            }
            var args = string.Format("commit {0} -m \"{1}\"", str, msg);
            Debug.Log(exec(args));
        }
        public static void revert(string path)
        {
            var args = string.Format(" revert -R {0}", path);
            Debug.Log(exec(args));
        }
        public static void revertList(List<string> paths)
        {
            var str = string.Join(" ", paths.ToArray());
            var args = string.Format(" revert -R {0}", str);
            Debug.Log(exec(args));
        }
        public static void add(string path)
        {
            var args = string.Format(" add {0}", path);
            Debug.Log(exec(args));
        }
        public static void addList(List<string> paths)
        {
            var str = string.Join(" ", paths.ToArray());
            var args = string.Format(" add {0}", str);
            Debug.Log(exec(args));
        }
        public static void delete(string path)
        {
            var args = string.Format(" delete {0}", path);
            Debug.Log(exec(args));
        }
        public static void deleteList(List<string> paths)
        {
            var str = string.Join(" ", paths.ToArray());
            var args = string.Format(" delete {0}", str);
            Debug.Log(exec(args));
        }
        public static void update(string path)
        {
            var args = string.Format(" update {0}", path);
            Debug.Log(exec(args));
        }
        public static string status(string path)
        {
            var args = string.Format(" status {0}", path);
            var cmdResult = exec(args);
            return cmdResult.stdOut.ToString();
        }
        public static void cleanup(string path)
        {
            var args = string.Format(" cleanup {0}", path);
            Debug.Log(exec(args));
        }
        public static void commitFolder(string path, string msg)
        {
            var statusStr = status(path);
            var list = statusStr.Split('\n');
            var toAddList = new List<string>();
            var toDeleteList = new List<string>();
            var toCommitList = new List<string>();
            foreach (var line in list)
            {
                if (line.StartsWith("?"))
                {
                    var file = line.Substring(1).Trim();
                    if (file != "")
                    {
                        toAddList.Add(file);
                        toCommitList.Add(file);
                    }
                }
                else if (line.StartsWith("!"))
                {
                    var file = line.Substring(1).Trim();
                    if (file != "")
                    {
                        toDeleteList.Add(file);
                        toCommitList.Add(file);
                    }
                }
                else if (line.StartsWith("M"))
                {
                    var file = line.Substring(1).Trim();
                    if (file != "")
                    {
                        toCommitList.Add(file);
                    }
                }
            }
            if (toAddList.Count > 0)
            {
                addList(toAddList);
            }
            if (toDeleteList.Count > 0)
            {
                deleteList(toDeleteList);
            }
            if (toCommitList.Count > 0)
            {
                commitList(toCommitList, msg);
            }
        }
        public static bool isFileExit(string urlOfDir, string filename)
        {
            addEnv();
            string cmd = string.Format("list --xml {0}", urlOfDir);
            cmd += auth;
            string ret = CmdUtil.Cmd(getSvn(), cmd).stdOut.ToString();
            Debug.Log($"ret is:{ret}");
            XmlDocument doc = new XmlDocument();
            try
            {
                doc.LoadXml(ret);
            }
            catch (System.Exception e)
            {
                Debug.Log(e.Message);
                return false;
            }
            XmlNodeList childs = doc.SelectSingleNode("/lists/list").ChildNodes;
            foreach (XmlNode child in childs)
            {
                XmlNode nameNode = child.SelectSingleNode("name");
                string name = nameNode.FirstChild.Value;
                if (name == filename)
                {
                    return true;
                }
            }
            return false;
        }
        public static void addFile(string file)
        {
            addEnv();
            string cmd_file = string.Format("add {0}", file);
            cmd_file += auth;
            Debug.Log(CmdUtil.Cmd(getSvn(), cmd_file));
        }
        public static bool isExit(string urlOfFile)
        {
            addEnv();
            List<string> splits = new List<string>(urlOfFile.Split('/'));
            string filename = splits[splits.Count - 1];
            splits.RemoveAt(splits.Count - 1);
            string parentUrl = string.Join("/", splits);
            return isFileExit(parentUrl, filename);
        }
        public static string getFileContent(string urlOfDir, string filename)
        {
            addEnv();
            string dstFile = getFile(urlOfDir, filename);
            if (dstFile != null && File.Exists(dstFile))
            {
                return File.ReadAllText(dstFile);
            }
            else
            {
                return null;
            }
        }
        public static string getFile(string urlOfDir, string filename, bool bCreateIfNotExist = false)
        {
            addEnv();
            if (!Directory.Exists(svn_tmp_dir))
            {
                Directory.CreateDirectory(svn_tmp_dir);
            }
            string workPath = Path.Combine(Directory.GetCurrentDirectory(), svn_tmp_dir);
            string urlOfFile = urlOfDir + "/" + filename;
            string md5 = Md5Util.GetMd5OfString(urlOfFile);
            {
                string cmd_dir = string.Format("checkout --depth empty {0} {1}", urlOfDir, md5);
                cmd_dir += auth;
                Debug.Log(CmdUtil.Cmd(getSvn(), cmd_dir, workPath));
            }
            string tmpDirPath = Path.Combine(workPath, md5);
            if(!Directory.Exists(tmpDirPath))
            {
                return null;
            }
            {
                string cmd_file = string.Format("update {0}", filename);
                cmd_file += auth;
                Debug.Log(CmdUtil.Cmd(getSvn(), cmd_file, tmpDirPath));
            }
            if (bCreateIfNotExist)
            {
                string fullFile = Path.Combine(tmpDirPath, filename);
                if (!File.Exists(fullFile))
                {
                    File.WriteAllText(fullFile, "");
                    addFile(fullFile);
                }
            }
            {
                string cmd_revert = string.Format("revert {0}", filename);
                cmd_revert += auth;
                Debug.Log(CmdUtil.Cmd(getSvn(), cmd_revert, tmpDirPath));
            }
            string dstFile = Path.Combine(svn_tmp_dir, md5, filename);
            if (File.Exists(dstFile))
            {
                return dstFile;
            }
            else
            {
                return null;
            }
        }
        public static string getFileContentReadOnly(string urlOfFile)
        {
            addEnv();
            string cmd = $"cat {urlOfFile}";
            cmd += auth;
            return CmdUtil.Cmd(getSvn(), cmd).stdOut.ToString();
        }
        //empty infinity
        public static string getDir(string urlOfDir, string depth = "empty")
        {
            addEnv();
            if (!Directory.Exists(svn_tmp_dir))
            {
                Directory.CreateDirectory(svn_tmp_dir);
            }
            string workPath = Path.Combine(Directory.GetCurrentDirectory(), svn_tmp_dir);
            string md5 = Md5Util.GetMd5OfString(urlOfDir);
            {
                string cmd_dir = string.Format("checkout --depth {0} {1} {2}", depth, urlOfDir, md5);
                cmd_dir += auth;
                Debug.Log(CmdUtil.Cmd(getSvn(), cmd_dir, workPath));
            }
            string dstDir = Path.Combine(svn_tmp_dir, md5);
            if (Directory.Exists(dstDir))
            {
                return dstDir;
            }
            else
            {
                return null;
            }
        }
        public static string getFile(string urlOfFile)
        {
            addEnv();
            List<string> splits = new List<string>(urlOfFile.Split('/'));
            string filename = splits[splits.Count - 1];
            splits.RemoveAt(splits.Count - 1);
            string parentUrl = string.Join("/", splits);
            return getFile(parentUrl, filename);
        }

        public static string[] listDir(string urlOfDir, string pattern)
        {
            addEnv();
            string cmd = string.Format("list --xml {0}", urlOfDir);
            cmd += auth;
            string ret = CmdUtil.Cmd(getSvn(), cmd).stdOut.ToString();
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(ret);
            List<string> result = new List<string>();
            XmlNodeList childs = doc.SelectSingleNode("/lists/list").ChildNodes;
            foreach (XmlNode child in childs)
            {
                XmlNode nameNode = child.SelectSingleNode("name");
                string name = nameNode.FirstChild.Value;
                if (Regex.IsMatch(name, pattern))
                {
                    result.Add(name);
                }
            }
            return result.ToArray();
        }
        public static string getFileByPattern(string urlOfDir, string pattern)
        {
            addEnv();
            string cmd = string.Format("list --xml {0}", urlOfDir);
            cmd += auth;
            string ret = CmdUtil.Cmd(getSvn(), cmd).stdOut.ToString();
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(ret);
            XmlNodeList childs = doc.SelectSingleNode("/lists/list").ChildNodes;
            foreach (XmlNode child in childs)
            {
                XmlNode nameNode = child.SelectSingleNode("name");
                string name = nameNode.FirstChild.Value;
                if (Regex.IsMatch(name, pattern))
                {
                    return getFile(urlOfDir, name);
                }
            }
            return null;
        }
        public static bool makedir(string urlOfDir)
        {
            addEnv();
            if (isExit(urlOfDir))
            {
                return false;
            }

            List<string> splits = new List<string>(urlOfDir.Split('/'));
            string dirname = splits[splits.Count - 1];
            splits.RemoveAt(splits.Count - 1);
            string parentUrl = string.Join("/", splits);
            if (!isExit(parentUrl))
            {
                return false;
            }
            string parentPath = getDir(parentUrl);
            string filePath = Path.Combine(parentPath, dirname);
            Directory.CreateDirectory(filePath);
            add(filePath);
            commit(filePath);
            return true;
        }
        public static bool makedirs(string urlOfDir)
        {
            addEnv();
            List<string> splits = new List<string>(urlOfDir.Split('/'));
            List<string> makeUrls = new List<string>();
            while (true)
            {
                if (splits.Count <= 3)
                {
                    break;
                }
                else
                {
                    string url = string.Join("/", splits.ToArray());
                    if (SvnUtil.isExit(url))
                    {
                        for (int i = makeUrls.Count - 1; i >= 0; --i)
                        {
                            string urlToMake = makeUrls[i];
                            //Console.WriteLine(urlToMake);
                            SvnUtil.makedir(urlToMake);
                        }
                        return true;
                    }
                    else
                    {
                        makeUrls.Add(url);
                    }
                    splits.RemoveAt(splits.Count - 1);
                }
            }
            return false;
        }
        public static string getRevision(string full)
        {
            addEnv();
            if (full.Last() == Path.DirectorySeparatorChar)
            {
                full = full.Substring(0, full.Length - 1);
            }
            string dir = Path.GetDirectoryName(full);
            string sub = Path.GetFileName(full);
            string cmd = string.Format("info --show-item last-changed-revision \"{0}\"", sub);
            cmd += auth;
            Debug.Log($"getRevision cmd:{cmd}");
            return CmdUtil.Cmd(getSvn(), cmd, dir).stdOut.ToString().TrimEnd(new char[] { '\n', '\r' });
        }
        public static Dictionary<string, int> getRevisionsOfFiles(List<string> files, string baseDir)
        {
            Dictionary<string, int> map = new Dictionary<string, int>();
            if (Application.platform == RuntimePlatform.WindowsEditor)
            {

                List<System.Action> funcs = new List<System.Action>();
                foreach (string file in files)
                {
                    funcs.Add(() =>
                    {
                        string cmd = $"info --show-item last-changed-revision \"{file}\"";
                        cmd += auth;
                        string ret = CmdUtil.Cmd(getSvn(), cmd, baseDir).stdOut.ToString();
                        string revisionStr = ret.TrimEnd(new char[] { '\n', '\r' });
                        int iSvn = -1;
                        if (int.TryParse(revisionStr, out iSvn))
                        {
                            lock (map)
                            {
                                map[file] = iSvn;
                            }
                        }
                    });
                }
                //CmdUtil.RunFuncsAsync(funcs, 1, null);
                foreach (var f in funcs) {
                    f();
                }
                return map;
            }
            else
            {
                files = files.ConvertAll(file => string.Format("\"{0}\"", file));
                string filesStr = string.Join(" ", files);
                string cmd = $"info --show-item last-changed-revision {filesStr}";
                cmd += auth;
                string ret = CmdUtil.Cmd(getSvn(), cmd, baseDir).stdOut.ToString();
                if (files.Count == 1)
                {
                    string revisionStr = ret.TrimEnd(new char[] { '\n', '\r' });
                    int iSvn = -1;
                    if (int.TryParse(revisionStr, out iSvn))
                    {
                        string file = files[0].TrimStart('"').TrimEnd('"');
                        map[file] = iSvn;
                    }
                }
                else
                {
                    Regex regex = new Regex("(^\\d+)\\s+(.*)$", RegexOptions.Singleline);
                    string[] lines = ret.Split('\n');
                    foreach (string line in lines)
                    {
                        Match match = regex.Match(line);
                        //for (int i = 0; i < match.Groups.Count; ++i) {
                        //    Group g = match.Groups[i];
                        //    Debug.Log(g);
                        //}
                        if (match.Groups.Count == 3)
                        {
                            string revisionStr = match.Groups[1].Value;
                            string fileStr = match.Groups[2].Value.TrimEnd('\r').Replace(Path.DirectorySeparatorChar, '/');
                            int iSvn = -1;
                            if (int.TryParse(revisionStr, out iSvn))
                            {
                                map[fileStr] = iSvn;
                            }
                        }
                    }
                }
                return map;
            }
        }
        static void _splitPathToDirAndFile(string path, out string dir, out string fileName)
        {
            if (path.Last() == Path.DirectorySeparatorChar)
            {
                path = path.Substring(0, path.Length - 1);
            }
            path = path.Replace($"{Path.DirectorySeparatorChar}CompiledLua{Path.DirectorySeparatorChar}", $"{Path.DirectorySeparatorChar}");
            dir = Path.GetDirectoryName(path);
            fileName = Path.GetFileName(path);
        }
        public static string getLastChangedDate(string path)
        {
            addEnv();
            string dir, file;
            _splitPathToDirAndFile(path, out dir, out file);
            string cmd = string.Format("info --show-item last-changed-date \"{0}\"", file);
            cmd += auth;
            return CmdUtil.Cmd(getSvn(), cmd, dir).stdOut.ToString().TrimEnd(new char[] { '\n', '\r' });
        }
        public static string getLastChangedAuthor(string path)
        {
            addEnv();
            string dir, file;
            _splitPathToDirAndFile(path, out dir, out file);
            string cmd = string.Format("info --show-item last-changed-author \"{0}\"", file);
            cmd += auth;
            return CmdUtil.Cmd(getSvn(), cmd, dir).stdOut.ToString().TrimEnd(new char[] { '\n', '\r' });
        }
        static string getSvn()
        {
            if (Application.platform == RuntimePlatform.OSXEditor)
            {
                if (File.Exists("/usr/local/bin/svn"))
                    return "/usr/local/bin/svn";
                if (File.Exists("/opt/homebrew/bin/svn"))
                    return "/opt/homebrew/bin/svn";
                return "unknow";
            }
            else
            {
                return "svn";
            }
        }
        public static string getUrl(string path)
        {
            addEnv();
            string cmd = string.Format("info --show-item url \"{0}\"", path);
            cmd += auth;
            string url = CmdUtil.Cmd(getSvn(), cmd).stdOut.ToString().TrimEnd(new char[] { '\n', '\r' });
            return url;
        }
        public static string getRootUrl(string path)
        {
            addEnv();
            string cmd = string.Format("info --show-item repos-root-url \"{0}\"", path);
            cmd += auth;
            string rootUrl = CmdUtil.Cmd(getSvn(), cmd).stdOut.ToString().TrimEnd(new char[] { '\n', '\r' });
            return rootUrl;
        }
        public static string getBranchName(string path)
        {
            addEnv();
            string url = getUrl(path);
            string rootUrl = getRootUrl(path);
            Debug.Log($"SvnUtil.getBranchName url is {url},rootUrl is {rootUrl}");
            string suffix = url.Substring(rootUrl.Length + 1);
            string branchName = suffix.Split('/')[1].Replace("/", "_");
            return branchName;
        }
        public static string getDateOfRevision(string path, string revision)
        {
            string cmd = string.Format("info -r {0} --show-item last-changed-date \"{1}\"", revision, path);
            cmd += auth;
            return CmdUtil.Cmd(getSvn(), cmd).stdOut.ToString().TrimEnd(new char[] { '\n', '\r' });
        }

        public static string getMergedRevision(string srcUrlOrDir, string dstUrlOrDir)
        {
            string cmd = string.Format("mergeinfo --show-revs merged {0} {1}", srcUrlOrDir, dstUrlOrDir);
            cmd += auth;
            string result = CmdUtil.Cmd(getSvn(), cmd).stdOut.ToString().TrimEnd(new char[] { '\n', '\r' });
            string[] lines = result.Split(new char[] { '\n' });
            string lastest = lines[lines.Length - 1];
            return lastest.TrimEnd(new char[] { '\r' }).TrimStart(new char[] { 'r' });
        }
    }
}
