﻿using System;
using System.Xml;

namespace Oni.Metadata
{
    internal class MetaEnum : MetaType
    {
        private MetaType baseType;
        private Type enumType;

        public MetaEnum(MetaType baseType, Type enumType) : base("Enum", baseType.Size)
        {
            if (baseType != MetaType.Byte
                && baseType != MetaType.Int16
                && baseType != MetaType.UInt16
                && baseType != MetaType.Int32
                && baseType != MetaType.UInt32
                && baseType != MetaType.Int64
                && baseType != MetaType.UInt64)
            {
                throw new ArgumentException("Invalid enum base type", "baseType");
            }

            this.baseType = baseType;
            this.enumType = enumType;
        }

        public MetaType BaseType => baseType;

        public Type EnumType => enumType;

        public bool IsFlags => Utils.IsFlagsEnum(enumType);

        protected override bool IsLeafImpl() => true;

        public override void Accept(IMetaTypeVisitor visitor) => visitor.VisitEnum(this);

        public void BinaryToXml(BinaryReader reader, XmlWriter writer)
        {
            object value;

            if (baseType == MetaType.Byte)
                value = System.Enum.ToObject(enumType, reader.ReadByte());
            else if (baseType == MetaType.Int16)
                value = System.Enum.ToObject(enumType, reader.ReadInt16());
            else if (baseType == MetaType.UInt16)
                value = System.Enum.ToObject(enumType, reader.ReadUInt16());
            else if (baseType == MetaType.Int32)
                value = System.Enum.ToObject(enumType, reader.ReadInt32());
            else if (baseType == MetaType.UInt32)
                value = System.Enum.ToObject(enumType, reader.ReadUInt32());
            else if (baseType == MetaType.Int64)
                value = System.Enum.ToObject(enumType, reader.ReadInt64());
            else
                value = System.Enum.ToObject(enumType, reader.ReadUInt64());

            string text = value.ToString().Replace(",", string.Empty);

            if (text == "None" && IsFlags)
                text = string.Empty;

            writer.WriteValue(text);
        }

        public void XmlToBinary(XmlReader reader, BinaryWriter writer)
        {
            string text = reader.ReadElementContentAsString();

            if (string.IsNullOrEmpty(text) && IsFlags)
            {
                if (baseType == MetaType.Byte)
                    writer.WriteByte(0);
                else if (baseType == MetaType.Int16 || baseType == MetaType.UInt16)
                    writer.WriteUInt16(0);
                else if (baseType == MetaType.Int32 || baseType == MetaType.UInt32)
                    writer.Write(0);
                else
                    writer.Write(0L);

                return;
            }

            object value = null;

            try
            {
                value = System.Enum.Parse(enumType, text.Trim().Replace(' ', ','));
            }
            catch
            {
            }

            if (value == null)
                throw new FormatException(string.Format("{0} is not a valid value name. Run onisplit -help enums to see a list of possible names.", text));

            if (baseType == MetaType.Byte)
                writer.WriteByte(Convert.ToByte(value));
            else if (baseType == MetaType.Int16)
                writer.Write(Convert.ToInt16(value));
            else if (baseType == MetaType.UInt16)
                writer.Write(Convert.ToUInt16(value));
            else if (baseType == MetaType.Int32)
                writer.Write(Convert.ToInt32(value));
            else if (baseType == MetaType.UInt32)
                writer.Write(Convert.ToUInt32(value));
            else if (baseType == MetaType.Int64)
                writer.Write(Convert.ToInt64(value));
            else
                writer.Write(Convert.ToUInt64(value));
        }

        public static T Parse<T>(string text) where T : struct
        {
            object value = null;

            if (string.IsNullOrEmpty(text) && Utils.IsFlagsEnum(typeof(T)))
            {
                value = System.Enum.Parse(typeof(T), "None");
            }
            else
            {
                try
                {
                    string[] values = text.Split(new char[0], StringSplitOptions.RemoveEmptyEntries);
                    text = string.Join(", ", values);
                    value = System.Enum.Parse(typeof(T), text, true);
                }
                catch
                {
                }
            }

            if (value == null)
                throw new FormatException(string.Format("{0} is not a valid value name. Run onisplit -help enums to see a list of possible names.", text));

            return (T)value;
        }

        public static string ToString<T>(T value) where T : struct
        {
            string text = value.ToString().Replace(",", string.Empty);

            if (text == "None" && Utils.IsFlagsEnum(typeof(T)))
                text = string.Empty;

            return text;
        }
    }
}
