using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor.Networking.PlayerConnection;
using UnityEngine;
using UnityEngine.TestTools;

namespace UnityEditor.TestTools.CodeCoverage.Utils
{
    internal static class CoverageUtils
    {
        public static bool IsConnectedToPlayer
        {
            get
            {
                return EditorConnection.instance.ConnectedPlayers.Count > 0;
            }
        }

        public static string NormaliseFolderSeparators(string folderPath, bool stripTrailingSlash = false)
        {
            if (folderPath != null)
            {
                folderPath = folderPath.Replace('\\', '/');
                if (stripTrailingSlash)
                {
                    folderPath = folderPath.TrimEnd('/');
                }
            }

            return folderPath;
        }

        public static bool EnsureFolderExists(string folderPath)
        {
            if (string.IsNullOrEmpty(folderPath))
                return false;

            if (!Directory.Exists(folderPath))
            {
                try
                {
                    Directory.CreateDirectory(folderPath);
                }
                catch (Exception)
                {
                    return false;
                }
            }

            return true;
        }

        public static string GetProjectFolderName()
        {
            string[] projectPathArray = GetProjectPath().Split('/');

            Debug.Assert(projectPathArray.Length > 0);

            string folderName = projectPathArray[projectPathArray.Length - 1];

            char[] invalidChars = Path.GetInvalidPathChars();
            StringBuilder folderNameStringBuilder = new StringBuilder();
            foreach (char c in folderName)
            {
                if (invalidChars.Contains(c))
                {
                    folderNameStringBuilder.Append('_');
                }
                else
                {
                    folderNameStringBuilder.Append(c);
                }
            }

            return folderNameStringBuilder.ToString();
        }

        public static string StripAssetsFolderIfExists(string folderPath)
        {
            if (folderPath != null)
            {
                string toTrim = "Assets";
                folderPath = folderPath.TrimEnd(toTrim.ToCharArray());
            }

            return folderPath;
        }

        public static string GetProjectPath()
        {
            return NormaliseFolderSeparators(StripAssetsFolderIfExists(Application.dataPath), true);
        }

        public static string GetRootFolderPath(CoverageSettings coverageSettings)
        {
            string rootFolderPath = string.Empty;
            string coverageFolderPath = string.Empty;

            if (CommandLineManager.instance.batchmode && !CommandLineManager.instance.useProjectSettings)
            {
                if (coverageSettings.resultsPathFromCommandLine.Length > 0)
                {
                    coverageFolderPath = coverageSettings.resultsPathFromCommandLine;
                    EnsureFolderExists(coverageFolderPath);
                }
            }
            else
            {
                if (CommandLineManager.instance.runFromCommandLine && coverageSettings.resultsPathFromCommandLine.Length > 0)
                {
                    coverageFolderPath = coverageSettings.resultsPathFromCommandLine;
                    EnsureFolderExists(coverageFolderPath);
                }
                else
                {
                    coverageFolderPath = CoveragePreferences.instance.GetStringForPaths("Path", string.Empty);
                }
            }

            string projectPath = GetProjectPath();

            if (EnsureFolderExists(coverageFolderPath))
            {
                coverageFolderPath = NormaliseFolderSeparators(coverageFolderPath, true);

                // Add 'CodeCoverage' directory if coverageFolderPath is projectPath
                if (string.Equals(coverageFolderPath, projectPath, StringComparison.InvariantCultureIgnoreCase))
                    rootFolderPath = JoinPaths(coverageFolderPath, coverageSettings.rootFolderName);
                // else user coverageFolderPath as the root folder
                else
                    rootFolderPath = coverageFolderPath;
            }
            else
            {
                // Add 'CodeCoverage' directory to projectPath if coverageFolderPath is not valid
                rootFolderPath = JoinPaths(projectPath, coverageSettings.rootFolderName);
            }
            return rootFolderPath;
        }

        public static string GetHistoryFolderPath(CoverageSettings coverageSettings)
        {
            string historyFolderPath = string.Empty;
            string rootFolderPath = coverageSettings.rootFolderPath;

            if (CommandLineManager.instance.batchmode && !CommandLineManager.instance.useProjectSettings)
            {
                if (coverageSettings.historyPathFromCommandLine.Length > 0)
                {
                    historyFolderPath = coverageSettings.historyPathFromCommandLine;
                    EnsureFolderExists(historyFolderPath);
                }
            }
            else
            {
                if (CommandLineManager.instance.runFromCommandLine && coverageSettings.historyPathFromCommandLine.Length > 0)
                {
                    historyFolderPath = coverageSettings.historyPathFromCommandLine;
                    EnsureFolderExists(historyFolderPath);
                }
                else
                {
                    historyFolderPath = CoveragePreferences.instance.GetStringForPaths("HistoryPath", string.Empty);
                }
            }

            bool addHistorySubDir = false;
            string projectPath = GetProjectPath();

            if (EnsureFolderExists(historyFolderPath))
            {
                historyFolderPath = NormaliseFolderSeparators(historyFolderPath, true);
                
                // If historyFolderPath == rootFolderPath, add 'Report-history' sub directory in rootFolderPath 
                if (string.Equals(historyFolderPath, rootFolderPath, StringComparison.InvariantCultureIgnoreCase))
                {
                    addHistorySubDir = true;
                }
                // If historyFolderPath == projectPath, add 'CodeCoverage' directory to projectPath
                // and add 'Report-history' sub directory in rootFolderPath 
                else if (string.Equals(historyFolderPath, projectPath, StringComparison.InvariantCultureIgnoreCase))
                {
                    rootFolderPath = JoinPaths(projectPath, coverageSettings.rootFolderName);
                    addHistorySubDir = true;
                }
                // otherwise keep the original historyFolderPath
            }
            else
            {
                // If historyFolderPath is not valid, add 'CodeCoverage' directory to projectPath
                // and add 'Report-history' sub directory in rootFolderPath
                rootFolderPath = JoinPaths(projectPath, coverageSettings.rootFolderName);
                addHistorySubDir = true;
            }

            if (addHistorySubDir)
            {
                historyFolderPath = JoinPaths(rootFolderPath, CoverageSettings.ReportHistoryFolderName);
            }

            return historyFolderPath;
        }

        public static string JoinPaths(string pathLeft, string pathRight)
        {
            string[] pathsToJoin = new string[] { pathLeft, pathRight };
            return string.Join("/", pathsToJoin);
        }

        public static int GetNumberOfFilesInFolder(string folderPath, string filePattern, SearchOption searchOption)
        {
            if (folderPath != null && Directory.Exists(folderPath))
            {
                string[] files = Directory.GetFiles(folderPath, filePattern, searchOption);
                return files.Length;
            }

            return 0;
        }

        public static void ClearFolderIfExists(string folderPath, string filePattern)
        {
            if (folderPath != null && Directory.Exists(folderPath))
            {
                DirectoryInfo dirInfo = new DirectoryInfo(folderPath);

                foreach (FileInfo file in dirInfo.GetFiles(filePattern))
                {
                    try
                    {
                        file.Delete();
                    }
                    catch (Exception)
                    {
                        ResultsLogger.Log(ResultID.Warning_FailedToDeleteFile, file.FullName);
                    }
                }

                foreach (DirectoryInfo dir in dirInfo.GetDirectories())
                {
                    try
                    {
                        dir.Delete(true);
                    }
                    catch (Exception)
                    {
                        ResultsLogger.Log(ResultID.Warning_FailedToDeleteDir, dir.FullName);
                    }
                }
            }
        }

        public static bool IsValidFolder(string folderPath)
        {
            return !string.IsNullOrEmpty(folderPath) && Directory.Exists(folderPath);
        }

        public static bool IsValidFile(string filePath)
        {
            return !string.IsNullOrEmpty(filePath) && File.Exists(filePath);
        }

        private static HashSet<char> regexSpecialChars = new HashSet<char>(new[] { '[', '\\', '^', '$', '.', '|', '?', '*', '+', '(', ')' });

        public static string GlobToRegex(string glob, bool startEndConstrains = true)
        {
            var regex = new StringBuilder();
            var characterClass = false;
            char prevChar = Char.MinValue;

            if (startEndConstrains)
                regex.Append("^");
            foreach (var c in glob)
            {
                if (characterClass)
                {
                    if (c == ']')
                    {
                        characterClass = false;
                    }
                    regex.Append(c);
                    continue;
                }
                switch (c)
                {
                    case '*':
                        if (prevChar == '*')
                            regex.Append(".*"); //if it's double * pattern then don't stop at folder separator
                        else
                            regex.Append("[^\\n\\r/]*"); //else match everything except folder separator (and new line)
                        break;
                    case '?':
                        regex.Append("[^\\n\\r/]");
                        break;
                    case '[':
                        characterClass = true;
                        regex.Append(c);
                        break;
                    default:
                        if (regexSpecialChars.Contains(c))
                        {
                            regex.Append('\\');
                        }
                        regex.Append(c);
                        break;
                }

                prevChar = c;
            }
            if (startEndConstrains)
                regex.Append("$");
            return regex.ToString();
        }

        public static string[] GetFilteringLogParams(AssemblyFiltering assemblyFiltering, PathFiltering pathFiltering, string[] otherParams = null)
        {
            string[] logParams = { assemblyFiltering != null && assemblyFiltering.includedAssemblies.Length > 0 ? assemblyFiltering.includedAssemblies : "<Not specified>",
                    assemblyFiltering != null && assemblyFiltering.excludedAssemblies.Length > 0 ? assemblyFiltering.excludedAssemblies : "<Not specified>",
                    pathFiltering != null && pathFiltering.includedPaths.Length > 0 ? pathFiltering.includedPaths : "<Not specified>",
                    pathFiltering != null && pathFiltering.excludedPaths.Length > 0 ? pathFiltering.excludedPaths : "<Not specified>" };

            if (otherParams != null && otherParams.Length > 0)
                logParams = otherParams.Concat(logParams).ToArray();

            return logParams;
        }

        [ExcludeFromCoverage]
        public static string BrowseForDir(string directory, string title)
        {
            if (string.IsNullOrEmpty(directory))
            {
                string variable = "ProgramFiles";
#if UNITY_EDITOR_OSX
                variable = "HOME";
#endif
                string candidateDirectory = Environment.GetEnvironmentVariable(variable);
                if (IsValidFolder(candidateDirectory))
                    directory = candidateDirectory;
            }

            directory = EditorUtility.OpenFolderPanel(title, directory, string.Empty);

            EditorWindow.FocusWindowIfItsOpen(typeof(CodeCoverageWindow));

            if (!IsValidFolder(directory))
                return string.Empty;

            return directory;
        }

        [ExcludeFromCoverage]
        public static string BrowseForFile(string directory, string title)
        {
            if (string.IsNullOrEmpty(directory))
            {
                string variable = "ProgramFiles";
#if UNITY_EDITOR_OSX
                variable = "HOME";
#endif
                string candidateDirectory = Environment.GetEnvironmentVariable(variable);
                if (IsValidFolder(candidateDirectory))
                    directory = candidateDirectory;
            }

            string file = EditorUtility.OpenFilePanel(title, directory, "cs");

            EditorWindow.FocusWindowIfItsOpen(typeof(CodeCoverageWindow));

            if (!IsValidFile(file))
                return string.Empty;

            return file;
        }
    }
}
