//============= 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;
using System.Globalization;

namespace MyBT {
    [System.Serializable]
    public class TaskParameter : ScriptableObject {
        
        // TypeCode.DBNull is considered invalid or undefined
        [SerializeField]
        public System.TypeCode valType = TypeCode.DBNull;

        // public object valValue = null;
        public object valValue {
            set {
                if (value == null) {
                    valSerialized = "n";
                    return;
                }
                var type = value.GetType();
                if (type == typeof(int))
                    valSerialized = "i"+value.ToString();
                else if (type == typeof(float))
                    valSerialized = "f"+value.ToString();
                else if (type == typeof(string))
                    valSerialized = "s"+value;
                // else if (type == typeof(Color)) {
                //     Color32 c = (Color)valValue;
                //     uint v = (byte)c.r + (byte)c.g<<8 + (byte)c.b<<16 + (byte)c.a<<24;
                //     valSerialized = "c" + v;
                // }
                // else if (type == typeof())
                else if (type == typeof(Vector3)) {
                    Vector3 v = (Vector3)value;
                    valSerialized = "v" + v.x + "|" +v.y + "|" + v.z;
                }
            }
            get {
                if (valSerialized.Length == 0)
                    return null;
                char type = valSerialized[0];
                if (type == 'n')
                    return null;
                else if (type == 'i')
                    return int.Parse(valSerialized.Substring(1));
                else if (type == 'f')
                    return float.Parse(valSerialized.Substring(1));
                else if (type == 's')
                    return valSerialized.Substring(1);
                // else if (type == 'c') {
                //     byte v = byte.Parse(valSerialized.Substring(1));
                //     Color c = new Color32(v & 0xFF, (v>>8) & 0xFF, (v>>16) & 0xFF,(v>>24) & 0xFF);
                //     valValue = c;
                // }
                else if (type == 'v') {
                    string[] v = valSerialized.Substring(1).Split('|');
                    return new Vector3(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]));
                }
                return null;
            }
        }

        [SerializeField, HideInInspector]
        private string valSerialized = "";

        /// <summary>
        /// init taskparameters from string
        /// return null on success, return string with error details on error
        /// </summary>
        /// <param name="stringValue"></param>
        /// <returns></returns>
        public string Init(string stringValue) {
            bool boolValue;
            int intValue;
            float floatValue;

            Type thisType = Type.GetType(stringValue);
            if (thisType != null) {
                Debug.Log("TaskScriptParser.ParseValue: thisType " + thisType.FullName);
            }

            NumberStyles style = NumberStyles.Any;
            CultureInfo culture = CultureInfo.InvariantCulture;


            // Place checks higher in if-else statement to give higher priority to type.      
            if (bool.TryParse(stringValue, out boolValue)) {
                valType = TypeCode.Boolean;
                valValue = boolValue;
                return null;
            }
            else if (int.TryParse(stringValue, style, culture, out intValue)) {
                valType = TypeCode.Int32;
                valValue = intValue;
                return null;
            }
            else if (float.TryParse(stringValue, style, culture, out floatValue)) {
                valType = TypeCode.Single;
                valValue = floatValue;
                return null;
            }
            // handle string and enum
            else {
                // is null value
                if (stringValue == "null") {
                    valType = TypeCode.Empty;
                    valValue = null;
                    return null;
                }

                // is empty value
                if (stringValue == "") {
                    // no content
                    return null;
                }

                string[] splitValue = stringValue.Split('"');

                // is string with ""
                if (splitValue.Length == 3) {
                    valType = TypeCode.String;
                    valValue = splitValue[1];
                    return null;
                }

                // is enum? -> currently dont handle enums
                else {
                    // Debug.Log($"TaskParameter: detected enum: {stringValue}");
                    string[] enumSplit = stringValue.Split('.');
                    try {
                        string enumNamespace = null;
                        System.Type enumType = null;
                        string convertValue = null;

                        // object underlyingValue = Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()));

                        if (enumSplit.Length == 3) {
                            enumNamespace = enumSplit[0];
                            enumType = GetEnumType(enumSplit[1]);
                            convertValue = enumSplit[2];
                        } else if (enumSplit.Length == 2) {
                            enumType = GetEnumType(enumSplit[0]);
                            convertValue = enumSplit[1];
                        }
                        object enumValue = Enum.Parse(enumType, convertValue);
                        valType = TypeCode.Int32;
                        valValue = (int)enumValue;
                        // Debug.Log($"TaskParameter: detected enum: {enumType} {enumValue} string: {stringValue} type: {valType} value: {valValue}");
                        return null;
                    }
                    catch (Exception e) {
                        // Debug.LogError("failed conversion of string '" + stringValue + "' error: " + e.ToString());
                        return "failed conversion of string '" + stringValue + "' error: " + e.ToString();
                    }
                }
            }
        }

        public bool IsValid() {
            return (valType != TypeCode.DBNull);
        }

        public static Assembly[] GetEnumAssembly (string enumNamespace=null) {
            if (enumNamespace != null) {
                try {
                    Assembly assembly = System.Reflection.Assembly.Load(enumNamespace);
                    if (assembly != null) {
                        return new Assembly[] {assembly};
                    }
                }
                catch { } // ignore load error 
            }
            return AppDomain.CurrentDomain.GetAssemblies();
        }

        public static Type GetEnumType(string enumName, string enumNamespace=null) {

            foreach (var assembly in GetEnumAssembly(enumNamespace)) {
                var type = assembly.GetType(enumName);
                if (type == null)
                    continue;
                if (type.IsEnum)
                    return type;
            }
            return null;
        }

        public TaskParameter Duplicate () {
            TaskParameter tp = ScriptableObject.CreateInstance<TaskParameter>();
            tp.valType = valType;
            tp.valSerialized = valSerialized;
            tp.valValue = valValue;
            return tp;
        }
    }
}