using System;
using System.Collections.Generic;
using System.Linq;
using BepInEx.Configuration;
using HarmonyLib;
namespace BanquetForCyka {
    public interface IFieldModifier {
        void CaptureOriginal();
        void Apply();
        void Restore();
        void LogValue(string prefix);
    }
    /// 
    /// Represents a field that can be multiplied by a configurable value
    ///  
    /// The type of the field value 
    /// The type of the multiplier 
    public class FieldConfiguration {
        private string _fieldName;
        private ConfigEntry _defaultMultiplier;
        private Func> _conditionalMultiplier;
        private Func _applyCondition;
        public string FieldName {
            get { return _fieldName; }
            set { _fieldName = value; }
        }
        public ConfigEntry DefaultMultiplier {
            get { return _defaultMultiplier; }
            set { _defaultMultiplier = value; }
        }
        public Func> ConditionalMultiplier {
            get { return _conditionalMultiplier; }
            set { _conditionalMultiplier = value; }
        }
        public Func ApplyCondition {
            get { return _applyCondition; }
            set { _applyCondition = value; }
        }
        public FieldConfiguration(string fieldName, ConfigEntry defaultMultiplier) {
            _fieldName = fieldName;
            _defaultMultiplier = defaultMultiplier;
        }
        public FieldConfiguration(string fieldName, ConfigEntry defaultMultiplier,
                                  Func> conditionalMultiplier) {
            _fieldName = fieldName;
            _defaultMultiplier = defaultMultiplier;
            _conditionalMultiplier = conditionalMultiplier;
        }
        public FieldConfiguration(string fieldName, ConfigEntry defaultMultiplier,
                                  Func applyCondition) {
            _fieldName = fieldName;
            _defaultMultiplier = defaultMultiplier;
            _applyCondition = applyCondition;
        }
        public FieldConfiguration(string fieldName, ConfigEntry defaultMultiplier,
                                  Func> conditionalMultiplier,
                                  Func applyCondition) {
            _fieldName = fieldName;
            _defaultMultiplier = defaultMultiplier;
            _conditionalMultiplier = conditionalMultiplier;
            _applyCondition = applyCondition;
        }
        public ConfigEntry GetMultiplier(object __instance) {
            if (_conditionalMultiplier == null) {
                return _defaultMultiplier;
            }
            return _conditionalMultiplier(__instance);
        }
        public bool ShouldApply(object __instance) {
            if (_applyCondition == null) {
                return true;
            }
            return _applyCondition(__instance);
        }
    }
    public class MultipliedField : IFieldModifier {
        private readonly string _fieldName;
        private readonly ConfigEntry _multiplier;
        private readonly Traverse _parentTraverse;
        private readonly Func _applyCondition;
        private TField _originalValue;
        public string FieldName {
            get { return _fieldName; }
        }
        public MultipliedField(string fieldName, ConfigEntry multiplier, Traverse parentTraverse,
                               Func applyCondition = null) {
            _fieldName = fieldName;
            _multiplier = multiplier;
            _parentTraverse = parentTraverse;
            _applyCondition = applyCondition;
            if (!parentTraverse.Field(fieldName).FieldExists()) {
                throw new ArgumentException(
                    string.Format("Field {0} does not exist on {1}", fieldName, parentTraverse));
            }
            // Verify TField is a numeric type
            if (!IsNumericType(typeof(TField))) {
                throw new ArgumentException(
                    string.Format("Field type {0} must be a numeric type", typeof(TField).Name));
            }
            // Verify TMul is a numeric type
            if (!IsNumericType(typeof(TMul))) {
                throw new ArgumentException(
                    string.Format("Multiplier type {0} must be a numeric type", typeof(TMul).Name));
            }
        }
        private static bool IsNumericType(Type type) {
            return type == typeof(byte) || type == typeof(sbyte) || type == typeof(short) || type == typeof(ushort) ||
                   type == typeof(int) || type == typeof(uint) || type == typeof(long) || type == typeof(ulong) ||
                   type == typeof(float) || type == typeof(double) || type == typeof(decimal);
        }
        private TField MultiplyValues(TField fieldValue, TMul multiplierValue) {
            // Convert both to double for the multiplication
            double fieldDouble = Convert.ToDouble(fieldValue);
            double multiplierDouble = Convert.ToDouble(multiplierValue);
            double result = fieldDouble * multiplierDouble;
            // Convert back to TField
            return (TField)Convert.ChangeType(result, typeof(TField));
        }
        public TField GetValue() {
            var value = _parentTraverse.Field(_fieldName).GetValue();
            if (value == null)
                throw new InvalidOperationException(string.Format("Field {0} returned null", _fieldName));
            return (TField)value;
        }
        public void SetValue(TField value) {
            _parentTraverse.Field(_fieldName).SetValue(value);
            var verifyValue = GetValue();
            if (!verifyValue.Equals(value))
                throw new InvalidOperationException(
                    string.Format("Field {0} set to {1} but read back as {2}", _fieldName, value, verifyValue));
        }
        public void CaptureOriginal() {
            _originalValue = GetValue();
            if (Main.debug.Value)
                Console.WriteLine("Captured original value for {0}: {1}", _fieldName, _originalValue);
        }
        public void Apply() {
            try {
                if (_applyCondition != null && !_applyCondition(_parentTraverse.GetValue())) {
                    if (Main.debug.Value)
                        Console.WriteLine("Skipping {0}: condition not met", _fieldName);
                    return;
                }
                var newValue = MultiplyValues(_originalValue, _multiplier.Value);
                if (Main.debug.Value)
                    Console.WriteLine("Applying to {0}: {1} * {2} = {3}", _fieldName, _originalValue, _multiplier.Value,
                                      newValue);
                SetValue(newValue);
            } catch (Exception e) {
                throw new InvalidOperationException(string.Format("Failed to apply multiplication to {0}", _fieldName),
                                                    e);
            }
        }
        public void Restore() {
            if (Main.debug.Value)
                Console.WriteLine("Restoring {0} to original value: {1}", _fieldName, _originalValue);
            SetValue(_originalValue);
        }
        public void LogValue(string prefix) {
            if (!Main.debug.Value)
                return;
            var currentValue = GetValue();
            Console.WriteLine("{0} {1}; {2}: {3} (original: {4}, multiplier: {5})", prefix, _parentTraverse, _fieldName,
                              currentValue, _originalValue, _multiplier.Value);
        }
    }
    public class BooleanFieldConfiguration {
        private string _fieldName;
        private ConfigEntry _value;
        private Func> _conditionalValue;
        private Func _applyCondition;
        public string FieldName {
            get { return _fieldName; }
            set { _fieldName = value; }
        }
        public ConfigEntry Value {
            get { return _value; }
            set { _value = value; }
        }
        public Func> ConditionalValue {
            get { return _conditionalValue; }
            set { _conditionalValue = value; }
        }
        public Func ApplyCondition {
            get { return _applyCondition; }
            set { _applyCondition = value; }
        }
        public BooleanFieldConfiguration(string fieldName, ConfigEntry value) {
            _fieldName = fieldName;
            _value = value;
        }
        public BooleanFieldConfiguration(string fieldName, ConfigEntry value,
                                         Func> conditionalValue) {
            _fieldName = fieldName;
            _value = value;
            _conditionalValue = conditionalValue;
        }
        public BooleanFieldConfiguration(string fieldName, ConfigEntry value, Func applyCondition) {
            _fieldName = fieldName;
            _value = value;
            _applyCondition = applyCondition;
        }
        public BooleanFieldConfiguration(string fieldName, ConfigEntry value,
                                         Func> conditionalValue,
                                         Func applyCondition) {
            _fieldName = fieldName;
            _value = value;
            _conditionalValue = conditionalValue;
            _applyCondition = applyCondition;
        }
        public ConfigEntry GetValue(object __instance) {
            if (_conditionalValue == null) {
                return _value;
            }
            return _conditionalValue(__instance);
        }
        public bool ShouldApply(object __instance) {
            if (_applyCondition == null) {
                return true;
            }
            return _applyCondition(__instance);
        }
    }
    public class BooleanField : IFieldModifier {
        private readonly string _fieldName;
        private readonly ConfigEntry _value;
        private readonly Traverse _parentTraverse;
        private readonly Func _applyCondition;
        private bool _originalValue;
        public string FieldName {
            get { return _fieldName; }
        }
        public BooleanField(string fieldName, ConfigEntry value, Traverse parentTraverse,
                            Func applyCondition = null) {
            _fieldName = fieldName;
            _value = value;
            _parentTraverse = parentTraverse;
            _applyCondition = applyCondition;
            if (!parentTraverse.Field(fieldName).FieldExists()) {
                throw new ArgumentException(
                    string.Format("Field {0} does not exist on {1}", fieldName, parentTraverse));
            }
        }
        public bool GetValue() {
            var value = _parentTraverse.Field(_fieldName).GetValue();
            if (value == null)
                throw new InvalidOperationException(string.Format("Field {0} returned null", _fieldName));
            return (bool)value;
        }
        public void SetValue(bool value) {
            _parentTraverse.Field(_fieldName).SetValue(value);
            var verifyValue = GetValue();
            if (verifyValue != value)
                throw new InvalidOperationException(
                    string.Format("Field {0} set to {1} but read back as {2}", _fieldName, value, verifyValue));
        }
        public void CaptureOriginal() {
            _originalValue = GetValue();
            if (Main.debug.Value)
                Console.WriteLine("Captured original value for {0}: {1}", _fieldName, _originalValue);
        }
        public void Apply() {
            try {
                if (_applyCondition != null && !_applyCondition(_parentTraverse.GetValue())) {
                    if (Main.debug.Value)
                        Console.WriteLine("Skipping {0}: condition not met", _fieldName);
                    return;
                }
                if (_value.Value) {
                    if (Main.debug.Value)
                        Console.WriteLine("Applying to {0}: forcing to true", _fieldName);
                    SetValue(true);
                } else {
                    if (Main.debug.Value)
                        Console.WriteLine("Applying to {0}: leaving as {1} (config is false)", _fieldName, GetValue());
                }
            } catch (Exception e) {
                throw new InvalidOperationException(string.Format("Failed to apply value to {0}", _fieldName), e);
            }
        }
        public void Restore() {
            if (Main.debug.Value)
                Console.WriteLine("Restoring {0} to original value: {1}", _fieldName, _originalValue);
            SetValue(_originalValue);
        }
        public void LogValue(string prefix) {
            if (!Main.debug.Value)
                return;
            var currentValue = GetValue();
            Console.WriteLine("{0} {1}; {2}: {3} (original: {4}, config: {5})", prefix, _parentTraverse, _fieldName,
                              currentValue, _originalValue, _value.Value);
        }
    }
    /// 
    /// Represents an object with multiple fields that can be multiplied
    ///  
    /// The type of the object being managed 
    public class MultipliedObject {
        private readonly T _instance;
        private readonly Traverse _objectTraverse;
        private readonly Dictionary _fields;
        public MultipliedObject(T __instance) {
            _instance = __instance;
            _objectTraverse = Traverse.Create(__instance);
            _fields = new Dictionary();
        }
        public void AddField(FieldConfiguration config) {
            var multiplier = config.GetMultiplier(_instance);
            _fields[config.FieldName] =
                new MultipliedField(config.FieldName, multiplier, _objectTraverse, config.ShouldApply);
        }
        public void AddBooleanField(BooleanFieldConfiguration config) {
            var value = config.GetValue(_instance);
            _fields[config.FieldName] = new BooleanField(config.FieldName, value, _objectTraverse, config.ShouldApply);
        }
        public void CaptureFrom() {
            foreach (var field in _fields.Values) {
                field.CaptureOriginal();
            }
        }
        public void ApplyTo(IEnumerable fieldNames = null) {
            IEnumerable fieldsToApply = fieldNames ?? _fields.Keys;
            foreach (var fieldName in fieldsToApply.Where(name => _fields.ContainsKey(name)))
                _fields[fieldName].Apply();
        }
        public void RestoreTo(IEnumerable fieldNames = null) {
            IEnumerable fieldsToRestore = fieldNames ?? _fields.Keys;
            foreach (var fieldName in fieldsToRestore.Where(name => _fields.ContainsKey(name)))
                _fields[fieldName].Restore();
        }
        public void LogValues(string prefix) {
            if (!Main.debug.Value)
                return;
            foreach (var field in _fields.Values) {
                field.LogValue(prefix);
            }
        }
    }
    /// 
    /// Manages a collection of objects whose fields can be multiplied
    ///  
    /// The type of objects being managed 
    public class MultipliedObjectManager {
        private readonly Dictionary> _managedObjects;
        private readonly Action> _configureObject;
        public MultipliedObjectManager(Action> configureObject) {
            if (configureObject == null)
                throw new ArgumentNullException("configureObject");
            _configureObject = configureObject;
            _managedObjects = new Dictionary>();
        }
        private void SafeRemove(T __instance) {
            if (__instance == null)
                return;
            try {
                _managedObjects.Remove(__instance);
            } catch (Exception e) {
                Console.WriteLine("Error removing __instance from _managedObjects: {0}", e);
            }
        }
        public void OnObjectAttached(T __instance) {
            if (Main.debug.Value)
                Console.WriteLine("{0}.OnAttached", typeof(T).Name);
            if (__instance == null) {
                Console.WriteLine("Attempted to attach null __instance");
                return;
            }
            try {
                if (_managedObjects.ContainsKey(__instance)) {
                    if (Main.debug.Value)
                        Console.WriteLine("{0} already managed, skipping", typeof(T).Name);
                    return;
                }
                var multipliedObject = new MultipliedObject(__instance);
                _configureObject(multipliedObject);
                multipliedObject.CaptureFrom();
                _managedObjects.Add(__instance, multipliedObject);
                multipliedObject.LogValues("Patching");
                ApplyTo(__instance);
                multipliedObject.LogValues("Patched");
            } catch (Exception e) {
                Console.WriteLine("Error in OnObjectAttached: {0}", e);
            }
        }
        public void OnObjectDetached(T __instance) {
            if (Main.debug.Value)
                Console.WriteLine("{0}.OnDetaching", typeof(T).Name);
            if (__instance == null) {
                Console.WriteLine("Attempted to detach null __instance");
                return;
            }
            try {
                MultipliedObject multipliedObject;
                if (_managedObjects.TryGetValue(__instance, out multipliedObject)) {
                    if (Main.debug.Value)
                        multipliedObject.LogValues("Restoring");
                    try {
                        RestoreTo(__instance);
                        multipliedObject.LogValues("Restored");
                    } catch (Exception e) {
                        Console.WriteLine("Error restoring values: {0}", e);
                    }
                    SafeRemove(__instance);
                }
            } catch (Exception e) {
                Console.WriteLine("Error in OnObjectDetached: {0}", e);
            }
        }
        public void ApplyAll(IEnumerable fieldNames = null) {
            if (Main.debug.Value)
                Console.WriteLine("Modifying {0} {1}", _managedObjects.Count, typeof(T).Name);
            // Make a copy of the keys to avoid modification during enumeration
            var instances = _managedObjects.Keys.ToList();
            foreach (var __instance in instances) {
                try {
                    RestoreTo(__instance, fieldNames);
                    ApplyTo(__instance, fieldNames);
                } catch (Exception e) {
                    Console.WriteLine("Error applying to __instance: {0}", e);
                }
            }
        }
        public void ApplyTo(T __instance, IEnumerable fieldNames = null) {
            if (Main.debug.Value)
                Console.WriteLine("Applying {0}", typeof(T).Name);
            if (__instance == null)
                return;
            try {
                MultipliedObject obj;
                if (_managedObjects.TryGetValue(__instance, out obj))
                    obj.ApplyTo(fieldNames);
            } catch (Exception e) {
                Console.WriteLine("Error in ApplyTo: {0}", e);
            }
        }
        public void RestoreTo(T __instance, IEnumerable fieldNames = null) {
            if (Main.debug.Value)
                Console.WriteLine("Restoring {0}", typeof(T).Name);
            if (__instance == null)
                return;
            try {
                MultipliedObject obj;
                if (_managedObjects.TryGetValue(__instance, out obj))
                    obj.RestoreTo(fieldNames);
            } catch (Exception e) {
                Console.WriteLine("Error in RestoreTo: {0}", e);
            }
        }
    }
}