//============= Copyright (c) Ludic GmbH, All rights reserved. ==============
//
// Purpose: Part of the My Behaviour Tree Code
//
//=============================================================================

using System;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MyBT
{
    [System.Serializable]
    public class MethodScanner {

        public static bool debugActive = false;

        public static readonly BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Static | BindingFlags.InvokeMethod | BindingFlags.GetField;

        private static bool IsGenerated (DictionaryOfStringAndDictionaryOfStringAndMethodImplementation methods) {
            if (methods != null) {
                int count = 0;
                foreach (KeyValuePair<string, DictionaryOfStringAndMethodImplementation> fileKvp in methods) {
                    if (fileKvp.Value != null) {
                        foreach (KeyValuePair<string, MethodImplementation> functionKvp in fileKvp.Value) {
                            count += 1;
                        }
                    }
                }
                return (count > 0);
            }
            return false;
        }

        public static void Reset(ref DictionaryOfStringAndDictionaryOfStringAndMethodImplementation methods) {
            if (methods != null) {
                foreach (KeyValuePair<string, DictionaryOfStringAndMethodImplementation> fileKvp in methods) {
                    if (fileKvp.Value != null) {
                        foreach (KeyValuePair<string, MethodImplementation> functionKvp in fileKvp.Value) {
                            #if UNITY_EDITOR
                            UnityEngine.Object.DestroyImmediate(functionKvp.Value);
                            #else
                            UnityEngine.Object.Destroy(functionKvp.Value);
                            #endif
                        }
                    }
                }
                
                methods = null;
            }
        }

        public static bool CheckComponents(GameObject checkObject, bool readAllComponents, ref DictionaryOfStringAndDictionaryOfStringAndMethodImplementation methods) {
            bool success = true;

            methods = new DictionaryOfStringAndDictionaryOfStringAndMethodImplementation();

            foreach (Component component in checkObject.GetComponents<Component>()) {
                if (debugActive) Debug.Log("MethodScanner.CheckComponents: Checking Component "+component.GetType().Name+" on "+component.gameObject.name);
                if (component != null) { 
                    MemberInfo[] members = component.GetType().GetMembers(bindingFlags);
                    foreach (MemberInfo memberInfo in members) {
                        if (memberInfo.IsDefined(typeof(Task), true)) {
                            MethodImplementation methodImplementation = ScriptableObject.CreateInstance<MethodImplementation>();
                            methodImplementation.Init(memberInfo, component);
                            if (debugActive) Debug.Log("MethodScanner.CheckComponents: Method found with name "+methodImplementation.fullName + " " + methodImplementation.parameters);
                            if (methodImplementation.HasImplementation()) {
                                success &= AddTaskImplementation(methodImplementation, ref methods);
                            }
                        }

                        else {
                            if (readAllComponents) {
                                MethodImplementation methodImplementation = ScriptableObject.CreateInstance<MethodImplementation>();
                                methodImplementation.Init(memberInfo, component);
                                if (debugActive) Debug.Log("MethodScanner.CheckComponents: Method found with name "+methodImplementation.fullName + " " + methodImplementation.parameters);
                                if (methodImplementation.HasImplementation()) {
                                    success &= AddTaskImplementation(methodImplementation, ref methods);
                                }
                            }
                        }
                    }
                }
            }
            return IsGenerated(methods) && success;
        }

        public static bool AddTaskImplementation(MethodImplementation methodImplementation, ref DictionaryOfStringAndDictionaryOfStringAndMethodImplementation methods) {
            string taskName = methodImplementation.fullName;
            string paramtersAsString = methodImplementation.parameters.ToString();

            // generate unique key dictionarys
            if (!methods.ContainsKey(taskName)) {
                methods[taskName] = new DictionaryOfStringAndMethodImplementation(); //Dictionary<string, MethodImplementation>();
            }

            // add the list
            if (!methods[taskName].ContainsKey(paramtersAsString)) {
                methods[taskName][paramtersAsString] = methodImplementation;
            }

            // we have this exact implementation already in the dict, should normally not happen, maybe if you have 2 scripts of the same type on the object?
            else {
                Debug.LogWarning("Duplicate function: " + taskName + " with parms: " + paramtersAsString);
                return false;
            }

            return true;
        }
    }
}