using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using UnityEngine;
namespace XCharts.Runtime
{
    /// 
    /// Log system. Used to output logs with date and log type, support output to file, support custom output log type.
    /// ||日志系统。用于输出带日期和日志类型的日志,支持输出到文件,支持自定义输出的日志类型。
    /// 
    public class XLog : MonoBehaviour
    {
        public const int ALL = 0;
        public const int WARNING = 1;
        public const int DEBUG = 2;
        public const int INFO = 3;
        public const int PROTO = 4;
        public const int VITAL = 5;
        public const int ERROR = 6;
        public const int EXCEPTION = 7;
        private const int MAX_ERROR_LOG = 20;
        public static bool isReportBug = false;
        public static bool isOutputLog = false;
        public static bool isUploadLog = false;
        public static bool isCloseOutLog = false;
        public static int errorCount = 0;
        public static int exceptCount = 0;
        public static int uploadTick = 20;
        public static int reportTick = 10;
        private static bool initFileSuccess = false;
        private static bool[] levelList = new bool[] { true, true, true, true, true, true, true, true };
        private static List writeList = new List();
        private static float uploadTime = 0;
        private static float reportTime = 0;
        private string outpath;
        private StreamWriter writer;
        private string[] temp;
        public int logCount = 0;
        public static List errorList = new List();
        private static object m_Lock = new object();
        private static XLog m_Instance;
        public static XLog Instance
        {
            get
            {
                // if (m_Instance == null)
                // {
                //     GameObject go = new GameObject("XLog");
                //     m_Instance = go.AddComponent();
                //     DontDestroyOnLoad(go);
                // }
                return m_Instance;
            }
        }
        void Awake()
        {
            if (m_Instance != null)
            {
                Destroy(gameObject);
                return;
            }
            m_Instance = this;
            InitLogFile();
            // Application.logMessageReceived += HandleLog;
            Application.logMessageReceivedThreaded += HandleLog;
        }
        void OnDestroy()
        {
            if (writer != null)
            {
                writer.Close();
                writer.Dispose();
            }
            // Application.logMessageReceived -= HandleLog;
            Application.logMessageReceivedThreaded -= HandleLog;
        }
        void Update()
        {
            uploadTime += Time.deltaTime;
            reportTime += Time.deltaTime;
            lock (m_Lock)
            {
                if (writeList.Count > 0)
                {
                    logCount = writeList.Count;
                    if (!initFileSuccess)
                    {
                        writeList.Clear();
                        return;
                    }
                    try
                    {
                        temp = writeList.ToArray();
                        int count = 0;
                        foreach (var str in temp)
                        {
                            count++;
                            writer.WriteLine(str);
                            writeList.Remove(str);
                            if (count > 10) break;
                        }
                        writer.Flush();
                    }
                    catch (Exception e)
                    {
                        initFileSuccess = false;
                        //Application.logMessageReceived -= HandleLog;
                        Application.logMessageReceivedThreaded -= HandleLog;
                        UnityEngine.Debug.LogError("write outlog.txt error:" + e.Message);
                    }
                }
            }
        }
        private void InitLogFile()
        {
            ClearAllLog();
            XLog.EnableLog(ALL);
            if (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer)
            {
                XLog.ClearAllLog();
                XLog.EnableLog(VITAL);
                XLog.EnableLog(ERROR);
                XLog.isReportBug = true;
                XLog.isUploadLog = true;
            }
            else
            {
                XLog.isUploadLog = false;
                XLog.isReportBug = false;
            }
            outpath = GetLogOutputPath();
            try
            {
                if (File.Exists(outpath))
                {
                    File.Delete(outpath);
                }
                writer = new StreamWriter(outpath, false, Encoding.UTF8);
                writer.WriteLine(GetNowTime() + "init file success!!");
                UnityEngine.Debug.Log(GetNowTime() + "init file success:" + outpath);
                writer.Flush();
                initFileSuccess = true;
            }
            catch (Exception e)
            {
                initFileSuccess = false;
                Application.logMessageReceived -= HandleLog;
                UnityEngine.Debug.LogError("write outlog.txt error:" + e.Message);
            }
        }
        private static string GetLogOutputPath()
        {
#if UNITY_EDITOR
            string path = Application.dataPath + "/../outlog.txt";
#else
            string path = Application.persistentDataPath + "/outlog.txt";
            if (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer)
            {
                path = Application.persistentDataPath + "/outlog.txt";
            }
            else
            {
                path = Application.dataPath + "/../outlog.txt";
            }
#endif
            return path;
        }
        private void HandleLog(string logString, string stackTrace, LogType type)
        {
            lock (m_Lock)
            {
                if (!initFileSuccess) return;
                int index = logString.IndexOf("stack traceback");
                if (index > 0)
                {
                    string log = logString.Substring(0, index);
                    string trace = logString.Substring(index, logString.Length - index);
                    logString = log;
                    stackTrace = trace;
                }
                if (type == LogType.Log)
                {
                }
                else if (type == LogType.Error)
                {
                    if (logString.IndexOf("LUA ERROR") > 0 || logString.IndexOf("stack traceback") > 0) exceptCount++;
                    else errorCount++;
                    writeList.Add(logString);
                    //writeList.Add(stackTrace + "\n");
                    if (errorList.Count >= MAX_ERROR_LOG)
                    {
                        errorList.RemoveAt(1);
                    }
                    if (errorList.Count < MAX_ERROR_LOG)
                    {
                        errorList.Add(logString);
                       // errorList.Add(stackTrace + "\n");
                    }
                }
                else if (type == LogType.Exception)
                {
                    exceptCount++;
                    writeList.Add(logString);
                    writeList.Add(stackTrace + "\n");
                    if (errorList.Count >= MAX_ERROR_LOG)
                    {
                        errorList.RemoveAt(1);
                    }
                    if (errorList.Count < MAX_ERROR_LOG)
                    {
                        errorList.Add(logString);
                        errorList.Add(stackTrace + "\n");
                    }
                }
            }
        }
        public static void FlushLog()
        {
            var instance = XLog.Instance;
            if (instance != null && instance.writer != null)
            {
                for (int i = 0; i < writeList.Count; i++)
                {
                    instance.writer.WriteLine(writeList[i]);
                }
                instance.writer.Flush();
                writeList.Clear();
            }
        }
        public static void EnableLog(int logType)
        {
            if (logType < 0 || logType >= levelList.Length) return;
            levelList[logType] = true;
        }
        public static void ClearAllLog()
        {
            for (int i = 0; i < levelList.Length; i++)
            {
                levelList[i] = false;
            }
        }
        public static bool CanLog(int level)
        {
            if (level < 0 || level >= levelList.Length) return false;
            return levelList[level] || levelList[0];
        }
        public static void Log(string log)
        {
            Debug(log);
        }
        public static void LogError(string log)
        {
            Error(log);
        }
        public static void LogWarning(string log)
        {
            Warning(log);
        }
        public static void Debug(string log)
        {
            if (!CanLog(DEBUG)) return;
            UnityEngine.Debug.Log(GetNowTime() + "[DEBUG]\t" + log);
        }
        public static void Vital(string log)
        {
            if (!CanLog(INFO)) return;
            UnityEngine.Debug.Log(GetNowTime() + "[VITAL]\t" + log);
        }
        public static void Info(string log)
        {
            if (!CanLog(INFO)) return;
            UnityEngine.Debug.Log(GetNowTime() + "[INFO]\t" + log);
        }
        public static void Proto(string log)
        {
            if (!CanLog(PROTO)) return;
            UnityEngine.Debug.Log(GetNowTime() + "[PROTO]\t" + log);
        }
        public static void Warning(string log)
        {
            if (!CanLog(WARNING)) return;
            UnityEngine.Debug.LogWarning(GetNowTime() + "[WARN]\t" + log);
        }
        public static void Error(string log)
        {
            if (!CanLog(ERROR)) return;
            UnityEngine.Debug.LogError(GetNowTime() + "[ERROR]\t" + log);
        }
        public static string GetNowTime(string formatter = null)
        {
            DateTime now = DateTime.Now;
            if (formatter == null)
                return now.ToString("[HH:mm:ss fff]", DateTimeFormatInfo.InvariantInfo);
            else
                return now.ToString(formatter, DateTimeFormatInfo.InvariantInfo);
        }
        public static ulong GetTimestamp()
        {
            return (ulong)(DateTime.Now - new DateTime(190, 1, 1, 0, 0, 0, 0)).TotalSeconds;
        }
    }
}