#nullable enable
using System;
using System.Linq;

namespace Barotrauma
{
    class CheckDataAction : BinaryOptionAction
    {
        [Serialize("", IsPropertySaveable.Yes)]
        public Identifier Identifier { get; set; } = Identifier.Empty;

        [Serialize("", IsPropertySaveable.Yes)]
        public string Condition { get; set; } = "";

        [Serialize(false, IsPropertySaveable.Yes, "Forces the comparison to use string instead of attempting to parse it as a boolean or a float first")]
        public bool ForceString { get; set; }

        [Serialize(false, IsPropertySaveable.Yes, "Performs the comparison against a metadata by identifier instead of a constant value")]
        public bool CheckAgainstMetadata { get; set; }

        protected object? value2;
        protected object? value1;

        protected PropertyConditional.OperatorType Operator { get; set; }

        public CheckDataAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) 
        {
            if (string.IsNullOrEmpty(Condition))
            {
                Condition = element.GetAttributeString("value", string.Empty)!;
                if (string.IsNullOrEmpty(Condition))
                {
                    DebugConsole.ThrowError($"Error in scripted event \"{parentEvent.Prefab.Identifier}\". CheckDataAction with no condition set ({element}).");
                }
            }
        }

        public CheckDataAction(ContentXElement element, string parentDebugString) : base(null, element)
        {
            if (string.IsNullOrEmpty(Condition))
            {
                Condition = element.GetAttributeString("value", string.Empty)!;
                if (string.IsNullOrEmpty(Condition))
                {
                    DebugConsole.ThrowError($"Error in scripted event \"{parentDebugString}\". CheckDataAction with no condition set ({element}).");
                }
            }
        }

        public bool GetSuccess()
        {
            return DetermineSuccess() ?? false;
        }

        protected override bool? DetermineSuccess()
        {
            if (GameMain.GameSession?.GameMode is not CampaignMode campaignMode) { return false; }

            string[] splitString = Condition.Split(' ');
            string value;
            if (splitString.Length > 0)
            {
                //the first part of the string is the operator, skip it
                value = string.Join(" ", splitString.Skip(1));
            }
            else
            {
                DebugConsole.ThrowError($"{Condition} is too short, it should start with an operator followed by a boolean or a floating point value.");
                return false;
            }

            string op = splitString[0];
            Operator = PropertyConditional.GetOperatorType(op);
            if (Operator == PropertyConditional.OperatorType.None) { return false; }

            if (CheckAgainstMetadata)
            {
                object? metadata1 = campaignMode.CampaignMetadata.GetValue(Identifier);
                object? metadata2 = campaignMode.CampaignMetadata.GetValue(value.ToIdentifier());

                if (metadata1 == null || metadata2 == null)
                {
                    return Operator switch
                    {
                        PropertyConditional.OperatorType.Equals => metadata1 == metadata2,
                        PropertyConditional.OperatorType.NotEquals => metadata1 != metadata2,
                        _ => false
                    };
                }

                if (!ForceString)
                {
                    switch (metadata1)
                    {
                        case bool bool1 when metadata2 is bool bool2:
                            return CompareBool(bool1, bool2) ?? false;
                        case float float1 when metadata2 is float float2:
                            return CompareFloat(float1, float2) ?? false;
                    }
                }

                if (metadata1 is string string1 && metadata2 is string string2)
                {
                    return CompareString(string1, string2) ?? false;
                }

                return false;
            }

            if (!ForceString)
            {
                bool? tryBoolean = TryBoolean(campaignMode, value);
                if (tryBoolean != null) { return tryBoolean; }

                bool? tryFloat = TryFloat(campaignMode, value);
                if (tryFloat != null) { return tryFloat; }
            }

            bool? tryString = TryString(campaignMode, value);
            if (tryString != null) { return tryString; }

            return false;
        }

        private bool? TryBoolean(CampaignMode campaignMode, string value)
        {
            if (bool.TryParse(value, out bool b))
            {
                return CompareBool(GetBool(campaignMode), b);
            }

            DebugConsole.Log($"{value} != bool");
            return null;
        }

        private bool? CompareBool(bool val1, bool val2)
        {
            value1 = val1;
            value2 = val2;
            switch (Operator)
            {
                case PropertyConditional.OperatorType.Equals:
                    return val1 == val2;
                case PropertyConditional.OperatorType.NotEquals:
                    return val1 != val2;
                default:
                    DebugConsole.Log($"Only \"Equals\" and \"Not equals\" operators are allowed for a boolean (was {Operator} for {val2}).");
                    return false;
            }
        }

        private bool? TryFloat(CampaignMode campaignMode, string value)
        {
            if (float.TryParse(value, out float f))
            {
                return CompareFloat(GetFloat(campaignMode), f);
            }

            DebugConsole.Log($"{value} != float");
            return null;
        }

        private bool? CompareFloat(float val1, float val2)
        {
            value1 = val1;
            value2 = val2;
            switch (Operator)
            {
                case PropertyConditional.OperatorType.Equals:
                    return MathUtils.NearlyEqual(val1, val2);
                case PropertyConditional.OperatorType.GreaterThan:
                    return val1 > val2;
                case PropertyConditional.OperatorType.GreaterThanEquals:
                    return val1 >= val2;
                case PropertyConditional.OperatorType.LessThan:
                    return val1 < val2;
                case PropertyConditional.OperatorType.LessThanEquals:
                    return val1 <= val2;
                case PropertyConditional.OperatorType.NotEquals:
                    return !MathUtils.NearlyEqual(val1, val2);
            }

            return null;
        }

        private bool? TryString(CampaignMode campaignMode, string value)
        {
            return CompareString(GetString(campaignMode), value);
        }

        private bool? CompareString(string val1, string val2)
        {
            value1 = val1;
            value2 = val2;
            bool equals = string.Equals(val1, val2, StringComparison.OrdinalIgnoreCase);
            switch (Operator)
            { 
                case PropertyConditional.OperatorType.Equals:
                    return equals;
                case PropertyConditional.OperatorType.NotEquals:
                    return !equals;
                default:
                    DebugConsole.Log($"Only \"Equals\" and \"Not equals\" operators are allowed for a string (was {Operator} for {val2}).");
                    return null;
            }
        }

        protected virtual bool GetBool(CampaignMode campaignMode)
        {
            return campaignMode.CampaignMetadata.GetBoolean(Identifier);
        }
        
        protected virtual float GetFloat(CampaignMode campaignMode)
        {
            return campaignMode.CampaignMetadata.GetFloat(Identifier);
        }

        private string GetString(CampaignMode campaignMode)
        {
            return campaignMode.CampaignMetadata.GetString(Identifier);
        }

        public override string ToDebugString()
        {
            string condition = "?";
            if (value2 != null && value1 != null)
            {
                condition = $"{value1.ColorizeObject()} {Operator.ColorizeObject()} {value2.ColorizeObject()}";
            }

            return $"{ToolBox.GetDebugSymbol(succeeded.HasValue)} {nameof(CheckDataAction)} -> (Data: {Identifier.ColorizeObject()}, Success: {succeeded.ColorizeObject()}, Expression: {condition})";
        }
    }
}