using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Security;
using System.Text;
using UnityEngine;

namespace UnityTest
{
    public class XmlResultWriter
    {
        private readonly StringBuilder m_ResultWriter = new StringBuilder();
        private int m_Indend;
        private readonly string m_SuiteName;
        private readonly ITestResult[] m_Results;
        string m_Platform;

        public XmlResultWriter(string suiteName, string platform, ITestResult[] results)
        {
            m_SuiteName = suiteName;
            m_Results = results;
            m_Platform = platform;
        }

        private const string k_NUnitVersion = "2.6.2-Unity";

        public string GetTestResult()
        {
            InitializeXmlFile(m_SuiteName, new ResultSummarizer(m_Results));
            foreach (var result in m_Results)
            {
                WriteResultElement(result);
            }
            TerminateXmlFile();
            return m_ResultWriter.ToString();
        }

        private void InitializeXmlFile(string resultsName, ResultSummarizer summaryResults)
        {
            WriteHeader();

            DateTime now = DateTime.Now;
            var attributes = new Dictionary<string, string>
            {
                {"name", "Unity Tests"},
                {"total", summaryResults.TestsRun.ToString()},
                {"errors", summaryResults.Errors.ToString()},
                {"failures", summaryResults.Failures.ToString()},
                {"not-run", summaryResults.TestsNotRun.ToString()},
                {"inconclusive", summaryResults.Inconclusive.ToString()},
                {"ignored", summaryResults.Ignored.ToString()},
                {"skipped", summaryResults.Skipped.ToString()},
                {"invalid", summaryResults.NotRunnable.ToString()},
                {"date", now.ToString("yyyy-MM-dd")},
                {"time", now.ToString("HH:mm:ss")}
            };

            WriteOpeningElement("test-results", attributes);

            WriteEnvironment(m_Platform);
            WriteCultureInfo();
            WriteTestSuite(resultsName, summaryResults);
            WriteOpeningElement("results");
        }

        private void WriteOpeningElement(string elementName)
        {
            WriteOpeningElement(elementName, new Dictionary<string, string>());
        }

        private void WriteOpeningElement(string elementName, Dictionary<string, string> attributes)
        {
            WriteOpeningElement(elementName, attributes, false);
        }


        private void WriteOpeningElement(string elementName, Dictionary<string, string> attributes, bool closeImmediatelly)
        {
            WriteIndend();
            m_Indend++;
            m_ResultWriter.Append("<");
            m_ResultWriter.Append(elementName);
            foreach (var attribute in attributes)
            {
                m_ResultWriter.AppendFormat(" {0}=\"{1}\"", attribute.Key, SecurityElement.Escape(attribute.Value));
            }
            if (closeImmediatelly)
            {
                m_ResultWriter.Append(" /");
                m_Indend--;
            }
            m_ResultWriter.AppendLine(">");
        }

        private void WriteIndend()
        {
            for (int i = 0; i < m_Indend; i++)
            {
                m_ResultWriter.Append("  ");
            }
        }

        private void WriteClosingElement(string elementName)
        {
            m_Indend--;
            WriteIndend();
            m_ResultWriter.AppendLine("</" + elementName + ">");
        }

        private void WriteHeader()
        {
            m_ResultWriter.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
            m_ResultWriter.AppendLine("<!--This file represents the results of running a test suite-->");
        }

        static string GetEnvironmentUserName()
        {
            return Environment.UserName;
        }

        static string GetEnvironmentMachineName()
        {
            return Environment.MachineName;
        }

        static string GetEnvironmentUserDomainName()
        {
            return Environment.UserDomainName;
        }

        static string GetEnvironmentVersion()
        {
            return Environment.Version.ToString();
        }

        static string GetEnvironmentOSVersion()
        {
            return Environment.OSVersion.ToString();
        }

        static string GetEnvironmentOSVersionPlatform()
        {
            return Environment.OSVersion.Platform.ToString();
        }

        static string EnvironmentGetCurrentDirectory()
        {
            return Environment.CurrentDirectory;
        }

        private void WriteEnvironment( string targetPlatform )
        {
            var attributes = new Dictionary<string, string>
            {
                {"nunit-version", k_NUnitVersion},
                {"clr-version", GetEnvironmentVersion()},
                {"os-version", GetEnvironmentOSVersion()},
                {"platform", GetEnvironmentOSVersionPlatform()},
                {"cwd", EnvironmentGetCurrentDirectory()},
                {"machine-name", GetEnvironmentMachineName()},
                {"user", GetEnvironmentUserName()},
                {"user-domain", GetEnvironmentUserDomainName()},
                {"unity-version", Application.unityVersion},
                {"unity-platform", targetPlatform}
            };
            WriteOpeningElement("environment", attributes, true);
        }

        private void WriteCultureInfo()
        {
            var attributes = new Dictionary<string, string>
            {
                {"current-culture", CultureInfo.CurrentCulture.ToString()},
                {"current-uiculture", CultureInfo.CurrentUICulture.ToString()}
            };
            WriteOpeningElement("culture-info", attributes, true);
        }

        private void WriteTestSuite(string resultsName, ResultSummarizer summaryResults)
        {
            var attributes = new Dictionary<string, string>
            {
                {"name", resultsName},
                {"type", "Assembly"},
                {"executed", "True"},
                {"result", summaryResults.Success ? "Success" : "Failure"},
                {"success", summaryResults.Success ? "True" : "False"},
                {"time", summaryResults.Duration.ToString("#####0.000", NumberFormatInfo.InvariantInfo)}
            };
            WriteOpeningElement("test-suite", attributes);
        }

        private void WriteResultElement(ITestResult result)
        {
            StartTestElement(result);

            switch (result.ResultState)
            {
                case TestResultState.Ignored:
                case TestResultState.NotRunnable:
                case TestResultState.Skipped:
                    WriteReasonElement(result);
                    break;

                case TestResultState.Failure:
                case TestResultState.Error:
                case TestResultState.Cancelled:
                    WriteFailureElement(result);
                    break;
                case TestResultState.Success:
                case TestResultState.Inconclusive:
                    if (result.Message != null)
                        WriteReasonElement(result);
                    break;
            };

            WriteClosingElement("test-case");
        }

        private void TerminateXmlFile()
        {
            WriteClosingElement("results");
            WriteClosingElement("test-suite");
            WriteClosingElement("test-results");
        }

        #region Element Creation Helpers

        private void StartTestElement(ITestResult result)
        {
            var attributes = new Dictionary<string, string>
            {
                {"name", result.FullName},
                {"executed", result.Executed.ToString()}
            };
            string resultString;
            switch (result.ResultState)
            {
                case TestResultState.Cancelled:
                    resultString = TestResultState.Failure.ToString();
                    break;
                default:
                    resultString = result.ResultState.ToString();
                    break;
            }
            attributes.Add("result", resultString);
            if (result.Executed)
            {
                attributes.Add("success", result.IsSuccess.ToString());
                attributes.Add("time", result.Duration.ToString("#####0.000", NumberFormatInfo.InvariantInfo));
            }
            WriteOpeningElement("test-case", attributes);
        }

        private void WriteReasonElement(ITestResult result)
        {
            WriteOpeningElement("reason");
            WriteOpeningElement("message");
            WriteCData(result.Message);
            WriteClosingElement("message");
            WriteClosingElement("reason");
        }

        private void WriteFailureElement(ITestResult result)
        {
            WriteOpeningElement("failure");
            WriteOpeningElement("message");
            WriteCData(result.Message);
            WriteClosingElement("message");
            WriteOpeningElement("stack-trace");
            if (result.StackTrace != null)
                WriteCData(StackTraceFilter.Filter(result.StackTrace));
            WriteClosingElement("stack-trace");
            WriteClosingElement("failure");
        }

        #endregion

        private void WriteCData(string text)
        {
            if (string.IsNullOrEmpty(text)) 
				return;
            m_ResultWriter.AppendFormat("<![CDATA[{0}]]>", text);
            m_ResultWriter.AppendLine();
        }

        public void WriteToFile(string resultDestiantion, string resultFileName)
        {
            try
            {
                var path = Path.Combine(resultDestiantion, resultFileName);
                Debug.Log("Saving results in " + path);
                File.WriteAllText(path, GetTestResult(), Encoding.UTF8);
            }
            catch (Exception e)
            {
                Debug.LogError("Error while opening file");
                Debug.LogException(e);
            }
        }
    }
}