using System.ComponentModel;
using System.Collections.Generic;

namespace Ink.Runtime
{
    // Order is significant for type coersion.
    // If types aren't directly compatible for an operation,
    // they're coerced to the same type, downward.
    // Higher value types "infect" an operation.
    // (This may not be the most sensible thing to do, but it's worked so far!)
    public enum ValueType
    {
        // Bool is new addition, keep enum values the same, with Int==0, Float==1 etc,
        // but for coersion rules, we want to keep bool with a lower value than Int
        // so that it converts in the right direction
        Bool = -1, 
        // Used in coersion
        Int,
        Float,
        List,
        String,

        // Not used for coersion described above
        DivertTarget,
        VariablePointer
    }

    public abstract class Value : Runtime.Object
    {
        public abstract ValueType valueType { get; }
        public abstract bool isTruthy { get; }

        public abstract Value Cast(ValueType newType);

        public abstract object valueObject { get; }

        public static Value Create(object val)
        {
            // Implicitly lose precision from any doubles we get passed in
            if (val is double) {
                double doub = (double)val;
                val = (float)doub;
            }

            if( val is bool ) {
                return new BoolValue((bool)val);
            } else if (val is int) {
                return new IntValue ((int)val);
            } else if (val is long) {
                return new IntValue ((int)(long)val);
            } else if (val is float) {
                return new FloatValue ((float)val);
            } else if (val is double) {
                return new FloatValue ((float)(double)val);
            } else if (val is string) {
                return new StringValue ((string)val);
            } else if (val is Path) {
                return new DivertTargetValue ((Path)val);
            } else if (val is InkList) {
                return new ListValue ((InkList)val);
            }

            return null;
        }

        public override Object Copy()
        {
            return Create (valueObject);
        }

        protected StoryException BadCastException (ValueType targetType)
        {
            return new StoryException ("Can't cast "+this.valueObject+" from " + this.valueType+" to "+targetType);
        }
    }

    public abstract class Value<T> : Value
    {
        public T value { get; set; }

        public override object valueObject {
            get {
                return (object)value;
            }
        }

        public Value (T val)
        {
            value = val;
        }

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

    public class BoolValue : Value<bool>
    {
        public override ValueType valueType { get { return ValueType.Bool; } }
        public override bool isTruthy { get { return value; } }

        public BoolValue(bool boolVal) : base(boolVal)
        {
        }

        public BoolValue() : this(false) {}

        public override Value Cast(ValueType newType)
        {
            if (newType == valueType) {
                return this;
            }

            if (newType == ValueType.Int) {
                return new IntValue (this.value ? 1 : 0);
            }

            if (newType == ValueType.Float) {
                return new FloatValue (this.value ? 1.0f : 0.0f);
            }

            if (newType == ValueType.String) {
                return new StringValue(this.value ? "true" : "false");
            }

            throw BadCastException (newType);
        }

        public override string ToString ()
        {
            // Instead of C# "True" / "False"
            return value ? "true" : "false";
        }
    }

    public class IntValue : Value<int>
    {
        public override ValueType valueType { get { return ValueType.Int; } }
        public override bool isTruthy { get { return value != 0; } }

        public IntValue(int intVal) : base(intVal)
        {
        }

        public IntValue() : this(0) {}

        public override Value Cast(ValueType newType)
        {
            if (newType == valueType) {
                return this;
            }

            if (newType == ValueType.Bool) {
                return new BoolValue (this.value == 0 ? false : true);
            }

            if (newType == ValueType.Float) {
                return new FloatValue ((float)this.value);
            }

            if (newType == ValueType.String) {
                return new StringValue("" + this.value);
            }

            throw BadCastException (newType);
        }
    }

    public class FloatValue : Value<float>
    {
        public override ValueType valueType { get { return ValueType.Float; } }
        public override bool isTruthy { get { return value != 0.0f; } }

        public FloatValue(float val) : base(val)
        {
        }

        public FloatValue() : this(0.0f) {}

        public override Value Cast(ValueType newType)
        {
            if (newType == valueType) {
                return this;
            }

            if (newType == ValueType.Bool) {
                return new BoolValue (this.value == 0.0f ? false : true);
            }

            if (newType == ValueType.Int) {
                return new IntValue ((int)this.value);
            }

            if (newType == ValueType.String) {
                return new StringValue("" + this.value.ToString(System.Globalization.CultureInfo.InvariantCulture));
            }

            throw BadCastException (newType);
        }
    }

    public class StringValue : Value<string>
    {
        public override ValueType valueType { get { return ValueType.String; } }
        public override bool isTruthy { get { return value.Length > 0; } }

        public bool isNewline { get; private set; }
        public bool isInlineWhitespace { get; private set; }
        public bool isNonWhitespace {
            get {
                return !isNewline && !isInlineWhitespace;
            }
        }

        public StringValue(string str) : base(str)
        {
            // Classify whitespace status
            isNewline = value == "\n";
            isInlineWhitespace = true;
            foreach (var c in value) {
                if (c != ' ' && c != '\t') {
                    isInlineWhitespace = false;
                    break;
                }
            }
        }

        public StringValue() : this("") {}

        public override Value Cast(ValueType newType)
        {
            if (newType == valueType) {
                return this;
            }

            if (newType == ValueType.Int) {

                int parsedInt;
                if (int.TryParse (value, out parsedInt)) {
                    return new IntValue (parsedInt);
                } else {
                    return null;
                }
            }

            if (newType == ValueType.Float) {
                float parsedFloat;
                if (float.TryParse (value, System.Globalization.NumberStyles.Float ,System.Globalization.CultureInfo.InvariantCulture, out parsedFloat)) {
                    return new FloatValue (parsedFloat);
                } else {
                    return null;
                }
            }

            throw BadCastException (newType);
        }
    }

    public class DivertTargetValue : Value<Path>
    {
        public Path targetPath { get { return this.value; } set { this.value = value; } }
        public override ValueType valueType { get { return ValueType.DivertTarget; } }
        public override bool isTruthy { get { throw new System.Exception("Shouldn't be checking the truthiness of a divert target"); } }
            
        public DivertTargetValue(Path targetPath) : base(targetPath)
        {
        }

        public DivertTargetValue() : base(null)
        {}

        public override Value Cast(ValueType newType)
        {
            if (newType == valueType)
                return this;
            
            throw BadCastException (newType);
        }

        public override string ToString ()
        {
            return "DivertTargetValue(" + targetPath + ")";
        }
    }

    // TODO: Think: Erm, I get that this contains a string, but should
    // we really derive from Value<string>? That seems a bit misleading to me.
    public class VariablePointerValue : Value<string>
    {
        public string variableName { get { return this.value; } set { this.value = value; } }
        public override ValueType valueType { get { return ValueType.VariablePointer; } }
        public override bool isTruthy { get { throw new System.Exception("Shouldn't be checking the truthiness of a variable pointer"); } }

        // Where the variable is located
        // -1 = default, unknown, yet to be determined
        // 0  = in global scope
        // 1+ = callstack element index + 1 (so that the first doesn't conflict with special global scope)
        public int contextIndex { get; set; }

        public VariablePointerValue(string variableName, int contextIndex = -1) : base(variableName)
        {
            this.contextIndex = contextIndex;
        }

        public VariablePointerValue() : this(null)
        {
        }

        public override Value Cast(ValueType newType)
        {
            if (newType == valueType)
                return this;

            throw BadCastException (newType);
        }

        public override string ToString ()
        {
            return "VariablePointerValue(" + variableName + ")";
        }

        public override Object Copy()
        {
            return new VariablePointerValue (variableName, contextIndex);
        }
    }

    public class ListValue : Value<InkList>
    {
        public override ValueType valueType {
            get {
                return ValueType.List;
            }
        }

        // Truthy if it is non-empty
        public override bool isTruthy {
            get {
                return value.Count > 0;
            }
        }
                
        public override Value Cast (ValueType newType)
        {
            if (newType == ValueType.Int) {
                var max = value.maxItem;
                if( max.Key.isNull )
                    return new IntValue (0);
                else
                    return new IntValue (max.Value);
            }

            else if (newType == ValueType.Float) {
                var max = value.maxItem;
                if (max.Key.isNull)
                    return new FloatValue (0.0f);
                else
                    return new FloatValue ((float)max.Value);
            }

            else if (newType == ValueType.String) {
                var max = value.maxItem;
                if (max.Key.isNull)
                    return new StringValue ("");
                else {
                    return new StringValue (max.Key.ToString());
                }
            }

            if (newType == valueType)
                return this;

            throw BadCastException (newType);
        }

        public ListValue () : base(null) {
            value = new InkList ();
        }

        public ListValue (InkList list) : base (null)
        {
            value = new InkList (list);
        }

        public ListValue (InkListItem singleItem, int singleValue) : base (null)
        {
            value = new InkList {
                {singleItem, singleValue}
            };
        }

        public static void RetainListOriginsForAssignment (Runtime.Object oldValue, Runtime.Object newValue)
        {
            var oldList = oldValue as ListValue;
            var newList = newValue as ListValue;

            // When assigning the emtpy list, try to retain any initial origin names
            if (oldList && newList && newList.value.Count == 0)
                newList.value.SetInitialOriginNames (oldList.value.originNames);
        }
    }
        
}
 
