using System;
using System.Collections.Generic;
using System.Text;

namespace Substrate.NBT
{
    public delegate void MissingTagHandler (Object o, TagEventArgs e);
    public delegate void InvalidTagTypeHandler (Object o, TagEventArgs e);
    public delegate void InvalidTagValueHandler (Object o, TagEventArgs e);

    public interface INBTVerifier
    {
        event MissingTagHandler MissingTag;
        event InvalidTagTypeHandler InvalidTagType;
        event InvalidTagValueHandler InvalidTagValue;

        bool Verify ();
    }

    public class TagEventArgs : EventArgs
    {
        protected string _tagName;
        protected TagValue _tag;
        protected NBTSchemaNode _schema;

        public string TagName
        {
            get { return _tagName; }
        }

        public TagEventArgs (string tagName)
            : base()
        {
            _tagName = tagName;
        }

        public TagEventArgs (string tagName, TagValue tag)
            : base()
        {
            _tag = tag;
            _tagName = tagName;
        }

        public TagEventArgs (NBTSchemaNode schema, TagValue tag)
            : base()
        {
            _tag = tag;
            _schema = schema;
        }
    }

    public class NBTVerifier : INBTVerifier
    {
        private TagValue _root;
        private NBTSchemaNode _schema;

        public event MissingTagHandler MissingTag;
        public event InvalidTagTypeHandler InvalidTagType;
        public event InvalidTagValueHandler InvalidTagValue;

        private Dictionary<string, TagValue> _scratch = new Dictionary<string,TagValue>();

        public NBTVerifier () { }

        public NBTVerifier (TagValue root, NBTSchemaNode schema)
        {
            _root = root;
            _schema = schema;
        }

        public bool Verify ()
        {
            return Verify(_root, _schema);
        }

        private bool Verify (TagValue tag, NBTSchemaNode schema)
        {
            if (tag == null) {
                OnMissingTag(new TagEventArgs(schema.Name));
                return false;
            }

            NBTScalerNode scaler = schema as NBTScalerNode;
            if (scaler != null) {
                return VerifyScaler(tag, scaler);
            }

            NBTStringNode str = schema as NBTStringNode;
            if (str != null) {
                return VerifyString(tag, str);
            }

            NBTArrayNode array = schema as NBTArrayNode;
            if (array != null) {
                return VerifyArray(tag, array);
            }

            NBTListNode list = schema as NBTListNode;
            if (list != null) {
                return VerifyList(tag, list);
            }

            NBTCompoundNode compound = schema as NBTCompoundNode;
            if (compound != null) {
                return VerifyCompound(tag, compound);
            }

            return false;
        }

        private bool VerifyScaler (TagValue tag, NBTScalerNode schema)
        {
            if (!tag.IsCastableTo(schema.Type)) {
                OnInvalidTagType(new TagEventArgs(schema.Name, tag));
                return false;
            }

            return true;
        }

        private bool VerifyString (TagValue tag, NBTStringNode schema)
        {
            TagString stag = tag as TagString;
            if (stag == null) {
                OnInvalidTagType(new TagEventArgs(schema, tag));
                return false;
            }
            if (schema.Length > 0 && stag.Length > schema.Length) {
                OnInvalidTagValue(new TagEventArgs(schema, tag));
                return false;
            }
            if (schema.Value != null && stag.Data != schema.Value) {
                OnInvalidTagValue(new TagEventArgs(schema, tag));
                return false;
            }

            return true;
        }


        private bool VerifyArray (TagValue tag, NBTArrayNode schema)
        {
            TagByteArray atag = tag as TagByteArray;
            if (atag == null) {
                OnInvalidTagType(new TagEventArgs(schema, tag));
                return false;
            }
            if (schema.Length > 0 && atag.Length != schema.Length) {
                OnInvalidTagValue(new TagEventArgs(schema, tag));
                return false;
            }

            return true;
        }

        private bool VerifyList (TagValue tag, NBTListNode schema)
        {
            TagList ltag = tag as TagList;
            if (ltag == null) {
                OnInvalidTagType(new TagEventArgs(schema, tag));
                return false;
            }
            if (ltag.Count > 0 && ltag.ValueType != schema.Type) {
                OnInvalidTagValue(new TagEventArgs(schema, tag));
                return false;
            }
            if (schema.Length > 0 && ltag.Count != schema.Length) {
                OnInvalidTagValue(new TagEventArgs(schema, tag));
                return false;
            }

            // Patch up empty lists
            //if (schema.Length == 0) {
            //    tag = new NBT_List(schema.Type);
            //}

            bool pass = true;

            // If a subschema is set, test all items in list against it

            if (schema.SubSchema != null) {
                foreach (TagValue v in ltag) {
                    pass = Verify(v, schema.SubSchema) && pass;
                }
            }

            return pass;
        }

        private bool VerifyCompound (TagValue tag, NBTCompoundNode schema)
        {
            TagCompound ctag = tag as TagCompound;
            if (ctag == null) {
                OnInvalidTagType(new TagEventArgs(schema, tag));
                return false;
            }

            bool pass = true;

            foreach (NBTSchemaNode node in schema) {
                TagValue value;
                ctag.TryGetValue(node.Name, out value);

                if (value == null) {
                    if ((node.Options & NBTOptions.CREATE_ON_MISSING) == NBTOptions.CREATE_ON_MISSING) {
                        _scratch[node.Name] = node.BuildDefaultTree();
                        continue;
                    }
                    else if ((node.Options & NBTOptions.OPTIONAL) == NBTOptions.OPTIONAL) {
                        continue;
                    }
                }

                pass = Verify(value, node) && pass;
            }

            foreach (KeyValuePair<string, TagValue> item in _scratch) {
                ctag[item.Key] = item.Value;
            }

            _scratch.Clear();

            return pass;
        }

        #region Event Handlers

        protected void OnMissingTag (TagEventArgs e)
        {
            if (MissingTag != null) {
                MissingTag(this, e);
            }
        }

        protected void OnInvalidTagType (TagEventArgs e)
        {
            if (InvalidTagType != null) {
                InvalidTagType(this, e);
            }
        }

        protected void OnInvalidTagValue (TagEventArgs e)
        {
            if (InvalidTagValue != null) {
                InvalidTagValue(this, e);
            }
        }

        #endregion
    }
}