#region Copyright notice and license

// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
// http://github.com/jskeet/dotnet-protobufs/
// Original C++/Java/Python code:
// http://code.google.com/p/protobuf/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#endregion

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

#if !LITE
using Google.ProtocolBuffers.Collections;
using Google.ProtocolBuffers.Descriptors;

#endif

namespace Google.ProtocolBuffers
{
    /// <summary>
    /// TODO(jonskeet): Write summary text.
    /// </summary>
    public sealed class UninitializedMessageException : Exception
    {
        private readonly IList<string> missingFields;

        private UninitializedMessageException(IList<string> missingFields)
            : base(BuildDescription(missingFields))
        {
            this.missingFields = new List<string>(missingFields);
        }

        /// <summary>
        /// Returns a read-only list of human-readable names of
        /// required fields missing from this message. Each name
        /// is a full path to a field, e.g. "foo.bar[5].baz"
        /// </summary>
        public IList<string> MissingFields
        {
            get { return missingFields; }
        }

        /// <summary>
        /// Converts this exception into an InvalidProtocolBufferException.
        /// When a parsed message is missing required fields, this should be thrown
        /// instead of UninitializedMessageException.
        /// </summary>
        public InvalidProtocolBufferException AsInvalidProtocolBufferException()
        {
            return new InvalidProtocolBufferException(Message);
        }

        /// <summary>
        /// Constructs the description string for a given list of missing fields.
        /// </summary>
        private static string BuildDescription(IEnumerable<string> missingFields)
        {
            StringBuilder description = new StringBuilder("Message missing required fields: ");
            bool first = true;
            foreach (string field in missingFields)
            {
                if (first)
                {
                    first = false;
                }
                else
                {
                    description.Append(", ");
                }
                description.Append(field);
            }
            return description.ToString();
        }

        /// <summary>
        /// For Lite exceptions that do not known how to enumerate missing fields
        /// </summary>
        public UninitializedMessageException(IMessageLite message)
            : base(String.Format("Message {0} is missing required fields", message.GetType()))
        {
            missingFields = new List<string>();
        }

#if !LITE
        public UninitializedMessageException(IMessage message)
            : this(FindMissingFields(message))
        {
        }

        /// <summary>
        /// Returns a list of the full "paths" of missing required
        /// fields in the specified message.
        /// </summary>
        private static IList<String> FindMissingFields(IMessage message)
        {
            List<String> results = new List<String>();
            FindMissingFields(message, "", results);
            return results;
        }

        /// <summary>
        /// Recursive helper implementing FindMissingFields.
        /// </summary>
        private static void FindMissingFields(IMessage message, String prefix, List<String> results)
        {
            foreach (FieldDescriptor field in message.DescriptorForType.Fields)
            {
                if (field.IsRequired && !message.HasField(field))
                {
                    results.Add(prefix + field.Name);
                }
            }

            foreach (KeyValuePair<FieldDescriptor, object> entry in message.AllFields)
            {
                FieldDescriptor field = entry.Key;
                object value = entry.Value;

                if (field.MappedType == MappedType.Message)
                {
                    if (field.IsRepeated)
                    {
                        int i = 0;
                        foreach (object element in (IEnumerable) value)
                        {
                            if (element is IMessage)
                            {
                                FindMissingFields((IMessage) element, SubMessagePrefix(prefix, field, i++), results);
                            }
                            else
                            {
                                results.Add(prefix + field.Name);
                            }
                        }
                    }
                    else
                    {
                        if (message.HasField(field))
                        {
                            if (value is IMessage)
                            {
                                FindMissingFields((IMessage) value, SubMessagePrefix(prefix, field, -1), results);
                            }
                            else
                            {
                                results.Add(prefix + field.Name);
                            }
                        }
                    }
                }
            }
        }

        private static String SubMessagePrefix(String prefix, FieldDescriptor field, int index)
        {
            StringBuilder result = new StringBuilder(prefix);
            if (field.IsExtension)
            {
                result.Append('(')
                    .Append(field.FullName)
                    .Append(')');
            }
            else
            {
                result.Append(field.Name);
            }
            if (index != -1)
            {
                result.Append('[')
                    .Append(index)
                    .Append(']');
            }
            result.Append('.');
            return result.ToString();
        }
#endif
    }
}