using System;
using System.Collections.Generic;
using System.Globalization;
using Google.ProtocolBuffers.Descriptors;

namespace Google.ProtocolBuffers.Serialization
{
    /// <summary>
    /// Allows reading messages from a name/value dictionary
    /// </summary>
    public class DictionaryReader : AbstractReader
    {
        private readonly IEnumerator<KeyValuePair<string, object>> _input;
        private bool _ready;

        /// <summary>
        /// Creates a dictionary reader from an enumeration of KeyValuePair data, like an IDictionary
        /// </summary>
        public DictionaryReader(IEnumerable<KeyValuePair<string, object>> input)
        {
            _input = input.GetEnumerator();
            _ready = _input.MoveNext();
        }

        /// <summary>
        /// No-op
        /// </summary>
        public override AbstractReader ReadStartMessage()
        {
            return this;
        }

        /// <summary>
        /// No-op
        /// </summary>
        public override void ReadEndMessage()
        {
        }

        /// <summary>
        /// Merges the contents of stream into the provided message builder
        /// </summary>
        public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
        {
            builder.WeakMergeFrom(this, registry);
            return builder;
        }

        /// <summary>
        /// Peeks at the next field in the input stream and returns what information is available.
        /// </summary>
        /// <remarks>
        /// This may be called multiple times without actually reading the field.  Only after the field
        /// is either read, or skipped, should PeekNext return a different value.
        /// </remarks>
        protected override bool PeekNext(out string field)
        {
            field = _ready ? _input.Current.Key : null;
            return _ready;
        }

        /// <summary>
        /// Causes the reader to skip past this field
        /// </summary>
        protected override void Skip()
        {
            _ready = _input.MoveNext();
        }

        private bool GetValue<T>(ref T value)
        {
            if (!_ready)
            {
                return false;
            }

            object obj = _input.Current.Value;
            if (obj is T)
            {
                value = (T) obj;
            }
            else
            {
                try
                {
                    if (obj is IConvertible)
                    {
                        value = (T) Convert.ChangeType(obj, typeof (T), CultureInfo.InvariantCulture);
                    }
                    else
                    {
                        value = (T) obj;
                    }
                }
                catch
                {
                    _ready = _input.MoveNext();
                    return false;
                }
            }
            _ready = _input.MoveNext();
            return true;
        }

        /// <summary>
        /// Returns true if it was able to read a Boolean from the input
        /// </summary>
        protected override bool Read(ref bool value)
        {
            return GetValue(ref value);
        }

        /// <summary>
        /// Returns true if it was able to read a Int32 from the input
        /// </summary>
        protected override bool Read(ref int value)
        {
            return GetValue(ref value);
        }

        /// <summary>
        /// Returns true if it was able to read a UInt32 from the input
        /// </summary>
        [CLSCompliant(false)]
        protected override bool Read(ref uint value)
        {
            return GetValue(ref value);
        }

        /// <summary>
        /// Returns true if it was able to read a Int64 from the input
        /// </summary>
        protected override bool Read(ref long value)
        {
            return GetValue(ref value);
        }

        /// <summary>
        /// Returns true if it was able to read a UInt64 from the input
        /// </summary>
        [CLSCompliant(false)]
        protected override bool Read(ref ulong value)
        {
            return GetValue(ref value);
        }

        /// <summary>
        /// Returns true if it was able to read a Single from the input
        /// </summary>
        protected override bool Read(ref float value)
        {
            return GetValue(ref value);
        }

        /// <summary>
        /// Returns true if it was able to read a Double from the input
        /// </summary>
        protected override bool Read(ref double value)
        {
            return GetValue(ref value);
        }

        /// <summary>
        /// Returns true if it was able to read a String from the input
        /// </summary>
        protected override bool Read(ref string value)
        {
            return GetValue(ref value);
        }

        /// <summary>
        /// Returns true if it was able to read a ByteString from the input
        /// </summary>
        protected override bool Read(ref ByteString value)
        {
            byte[] rawbytes = null;
            if (GetValue(ref rawbytes))
            {
                value = ByteString.CopyFrom(rawbytes);
                return true;
            }
            return false;
        }

        /// <summary>
        /// returns true if it was able to read a single value into the value reference.  The value
        /// stored may be of type System.String, System.Int32, or an IEnumLite from the IEnumLiteMap.
        /// </summary>
        protected override bool ReadEnum(ref object value)
        {
            return GetValue(ref value);
        }

        /// <summary>
        /// Merges the input stream into the provided IBuilderLite 
        /// </summary>
        protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
        {
            IDictionary<string, object> values = null;
            if (GetValue(ref values))
            {
                new DictionaryReader(values).Merge(builder, registry);
                return true;
            }
            return false;
        }

        public override bool ReadArray<T>(FieldType type, string field, ICollection<T> items)
        {
            object[] array = null;
            if (GetValue(ref array))
            {
                if (typeof (T) == typeof (ByteString))
                {
                    ICollection<ByteString> output = (ICollection<ByteString>) items;
                    foreach (byte[] item in array)
                    {
                        output.Add(ByteString.CopyFrom(item));
                    }
                }
                else
                {
                    foreach (T item in array)
                    {
                        items.Add(item);
                    }
                }
                return true;
            }
            return false;
        }

        public override bool ReadEnumArray(string field, ICollection<object> items)
        {
            object[] array = null;
            if (GetValue(ref array))
            {
                foreach (object item in array)
                {
                    items.Add(item);
                }
                return true;
            }
            return false;
        }

        public override bool ReadMessageArray<T>(string field, ICollection<T> items, IMessageLite messageType,
                                                 ExtensionRegistry registry)
        {
            object[] array = null;
            if (GetValue(ref array))
            {
                foreach (IDictionary<string, object> item in array)
                {
                    IBuilderLite builder = messageType.WeakCreateBuilderForType();
                    new DictionaryReader(item).Merge(builder);
                    items.Add((T) builder.WeakBuild());
                }
                return true;
            }
            return false;
        }

        public override bool ReadGroupArray<T>(string field, ICollection<T> items, IMessageLite messageType,
                                               ExtensionRegistry registry)
        {
            return ReadMessageArray(field, items, messageType, registry);
        }
    }
}