using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace EnhancedHierarchy {
    /// 
    /// Log Entries from the console, to check if a game object has any errors or warnings.
    /// 
    public sealed class LogEntry {
        private const double UPDATE_FREQUENCY = 0.75; // Every 750ms
        private static readonly Type logEntriesType;
        private static readonly Type logEntryType;
        public int RowIndex { get; private set; }
        public string Condition { get; private set; }
        public int ErrorNum { get; private set; }
        public string File { get; private set; }
        public int Line { get; private set; }
        public int Column { get; private set; }
        public EntryMode Mode { get; private set; }
        public int InstanceID { get; private set; }
        public int Identifier { get; private set; }
        public Object ObjectReference { get; private set; }
        public MonoScript Script { get; private set; }
        public Type ClassType { get; private set; }
        public static Dictionary> gameObjectEntries = new Dictionary>(100);
        public static List compileEntries = new List(100);
        private static int lastCount;
        private static bool entriesDirty;
        private static bool lastCompileFailedState;
        private static double lastUpdatedTime;
        private static readonly Icons.Warnings warnings = new Icons.Warnings();
        static LogEntry() {
            try {
                logEntriesType = ReflectionHelper.FindType("UnityEditorInternal.LogEntries");
                logEntryType = ReflectionHelper.FindType("UnityEditorInternal.LogEntry");
                if (logEntriesType == null)
                    logEntriesType = ReflectionHelper.FindType("UnityEditor.LogEntries");
                if (logEntryType == null)
                    logEntryType = ReflectionHelper.FindType("UnityEditor.LogEntry");
                ReloadReferences();
            } catch (Exception e) {
                Debug.LogException(e);
                Preferences.ForceDisableButton(new Icons.Warnings());
            }
            Application.logMessageReceived += (logString, stackTrace, type) => MarkEntriesDirty();
            EditorApplication.update += () => {
                try {
                    #if UNITY_2017_1_OR_NEWER
                    if (!entriesDirty && EditorUtility.scriptCompilationFailed != lastCompileFailedState) {
                        lastCompileFailedState = EditorUtility.scriptCompilationFailed;
                        MarkEntriesDirty();
                    }
                    #endif
                    if (EditorApplication.timeSinceStartup - lastUpdatedTime > UPDATE_FREQUENCY) {
                        if (!entriesDirty) {
                            var currentCount = GetLogCount();
                            if (lastCount > currentCount) { // Console possibly cleared
                                if (Preferences.DebugEnabled)
                                    Debug.Log("Detected console clear");
                                MarkEntriesDirty();
                            }
                            lastCount = currentCount;
                        }
                        if (entriesDirty)
                            ReloadReferences();
                    }
                } catch (Exception e) {
                    Debug.LogException(e);
                    Preferences.ForceDisableButton(new Icons.Warnings());
                }
            };
        }
        private LogEntry(object nativeEntry, int rowIndex) {
            RowIndex = rowIndex;
            if (nativeEntry.HasField("condition"))
                Condition = nativeEntry.GetInstanceField("condition");
            else if (nativeEntry.HasField("message"))
                Condition = nativeEntry.GetInstanceField("message");
            else
                throw new MissingFieldException("LogEntry doesn't have a message field");
            if (nativeEntry.HasField("errorNum"))
                ErrorNum = nativeEntry.GetInstanceField("errorNum");
            File = nativeEntry.GetInstanceField("file");
            Line = nativeEntry.GetInstanceField("line");
            if (nativeEntry.HasField("column"))
                Column = nativeEntry.GetInstanceField("column");
            Mode = nativeEntry.GetInstanceField("mode");
            InstanceID = nativeEntry.GetInstanceField("instanceID");
            Identifier = nativeEntry.GetInstanceField("identifier");
            if (InstanceID != 0)
                ObjectReference = EditorUtility.InstanceIDToObject(InstanceID);
            if (ObjectReference)
                Script = ObjectReference as MonoScript;
            if (Script)
                ClassType = Script.GetClass();
        }
        public static void MarkEntriesDirty() {
            if (!entriesDirty && Preferences.Enabled && Preferences.IsButtonEnabled(warnings))
                entriesDirty = true;
        }
        private static void ReloadReferences() {
            if (Preferences.DebugEnabled)
                Debug.Log("Reloading Logs References");
            gameObjectEntries.Clear();
            compileEntries.Clear();
            try {
                var count = logEntriesType.InvokeStaticMethod("StartGettingEntries");
                var nativeEntry = Activator.CreateInstance(logEntryType);
                for (var i = 0; i < count; i++) {
                    logEntriesType.InvokeStaticMethod("GetEntryInternal", i, nativeEntry);
                    var proxyEntry = new LogEntry(nativeEntry, i);
                    var go = proxyEntry.ObjectReference as GameObject;
                    if (proxyEntry.ObjectReference && !go) {
                        var component = proxyEntry.ObjectReference as Component;
                        if (component)
                            go = component.gameObject;
                    }
                    // if(entry.HasMode(EntryMode.ScriptCompileError | EntryMode.ScriptCompileWarning | EntryMode.AssetImportWarning) && entry.ClassType != null)
                    // if(!referencedComponents.Any(e => e.ClassType == entry.ClassType))
                    if (proxyEntry.ClassType != null)
                        compileEntries.Add(proxyEntry);
                    if (go)
                        if (gameObjectEntries.ContainsKey(go))
                            gameObjectEntries[go].Add(proxyEntry);
                        else
                            gameObjectEntries.Add(go, new List() { proxyEntry });
                }
                EditorApplication.RepaintHierarchyWindow();
            } catch (Exception e) {
                Debug.LogException(e);
                Preferences.ForceDisableButton(new Icons.Warnings());
            } finally {
                entriesDirty = false;
                lastUpdatedTime = EditorApplication.timeSinceStartup;
                logEntriesType.InvokeStaticMethod("EndGettingEntries");
            }
        }
        public bool HasMode(EntryMode toCheck) {
            return (Mode & toCheck) != 0;
        }
        public void OpenToEdit() {
            logEntriesType.InvokeStaticMethod("RowGotDoubleClicked", RowIndex);
        }
        private static int GetLogCount() {
            return logEntriesType.InvokeStaticMethod("GetCount");
        }
        public override string ToString() {
            return Condition;
        }
    }
}