using System;
using System.Globalization;

namespace RebootKit.Engine.Foundation {
    public enum CVarValueKind {
        Number, String
    }

    [Serializable]
    public struct CVarValue {
        public CVarValueKind kind;

        public double numberValue;
        public string stringValue;

        public CVarValue(int value) {
            kind = CVarValueKind.Number;
            numberValue = value;
            stringValue = null;
        }

        public CVarValue(float value) {
            kind = CVarValueKind.Number;
            numberValue = value;
            stringValue = null;
        }

        public CVarValue(double value) {
            kind = CVarValueKind.Number;
            numberValue = value;
            stringValue = null;
        }

        public CVarValue(string value) {
            kind = CVarValueKind.String;
            numberValue = 0;
            stringValue = value;
        }

        public void CopyFrom(CVarValue value) {
            kind = value.kind;
            numberValue = value.numberValue;
            stringValue = value.stringValue;
        }

        public override string ToString() {
            return kind switch {
                CVarValueKind.Number => numberValue.ToString(CultureInfo.InvariantCulture),
                CVarValueKind.String => $"{stringValue}",
                _ => throw new ArgumentOutOfRangeException()
            };
        }
    }

    [Flags]
    public enum CVarFlags {
        None = 0,
        Client = 1 << 0,
        Server = 1 << 1,
        ReadOnly = 1 << 2,
        User = 1 << 3,
    }

    [Serializable]
    public class ConfigVar {
        public CVarFlags flags;
        public string name;
        public string description;
        public CVarValue defaultValue;

        public ConfigVar(ConfigVar other) {
            flags = other.flags;
            name = other.name;
            description = other.description;
            defaultValue = other.defaultValue;
            Value = other.Value;
        }

        public ConfigVar(string name, CVarValue value, string description = "") {
            this.name = name;
            this.description = description;
            defaultValue = value;
            Value = defaultValue;
        }

        public ConfigVar(string name, int value, string description = "") {
            this.name = name;
            this.description = description;
            defaultValue = new CVarValue(value);
            Value = defaultValue;
        }

        public ConfigVar(string name, float value, string description = "") {
            this.name = name;
            this.description = description;
            defaultValue = new CVarValue(value);
            Value = defaultValue;
        }

        public ConfigVar(string name, double value, string description = "") {
            this.name = name;
            this.description = description;
            defaultValue = new CVarValue(value);
            Value = defaultValue;
        }

        public ConfigVar(string name, string value, string description = "") {
            this.name = name;
            this.description = description;
            defaultValue = new CVarValue(value);
            Value = defaultValue;
        }

        public CVarValue Value { get; private set; }

        public int IndexValue => (int) Value.numberValue;
        public float FloatValue => (float) Value.numberValue;
        public double NumberValue => Value.numberValue;
        public string StringValue => Value.stringValue;

        public static event Action<ConfigVar> StateChanged = delegate { };

        public void Set(int value) {
            if ((flags & CVarFlags.ReadOnly) != 0) {
                return;
            }

            Value = new CVarValue(value);
            StateChanged?.Invoke(this);
        }

        public void Set(float value) {
            if ((flags & CVarFlags.ReadOnly) != 0) {
                return;
            }

            Value = new CVarValue(value);
            StateChanged?.Invoke(this);
        }

        public void Set(string value) {
            if ((flags & CVarFlags.ReadOnly) != 0) {
                return;
            }

            Value = new CVarValue(value);
            StateChanged?.Invoke(this);
        }

        public void ParseFromString(string str) {
            if ((flags & CVarFlags.ReadOnly) != 0) {
                return;
            }

            if (float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out float f)) {
                Set(f);
            } else {
                Set(str);
            }
        }

        public void Reset() {
            if ((flags & CVarFlags.ReadOnly) != 0) {
                return;
            }

            Value = defaultValue;
            StateChanged?.Invoke(this);
        }

        public override string ToString() {
            return Value.ToString();
        }
    }
}