using System;
using System.Collections.Generic;
using ProtoBuf.Meta;
using System.Collections;
namespace ProtoBuf
{
    /// 
    /// Simple base class for supporting unexpected fields allowing
    /// for loss-less round-tips/merge, even if the data is not understod.
    /// The additional fields are (by default) stored in-memory in a buffer.
    /// 
    /// As an example of an alternative implementation, you might
    /// choose to use the file system (temporary files) as the back-end, tracking
    /// only the paths [such an object would ideally be IDisposable and use
    /// a finalizer to ensure that the files are removed].
    /// 
    public abstract class Extensible : IExtensible
    {
        // note: not marked ProtoContract - no local state, and can't 
        // predict sub-classes
        private IExtension extensionObject;
        IExtension IExtensible.GetExtensionObject(bool createIfMissing)
        {
            return GetExtensionObject(createIfMissing);
        }
        /// 
        /// Retrieves the extension object for the current
        /// instance, optionally creating it if it does not already exist.
        /// 
        /// Should a new extension object be
        /// created if it does not already exist?
        /// The extension object if it exists (or was created), or null
        /// if the extension object does not exist or is not available.
        /// The createIfMissing argument is false during serialization,
        /// and true during deserialization upon encountering unexpected fields.
        protected virtual IExtension GetExtensionObject(bool createIfMissing)
        {
            return GetExtensionObject(ref extensionObject, createIfMissing);
        }
        /// 
        /// Provides a simple, default implementation for extension support,
        /// optionally creating it if it does not already exist. Designed to be called by
        /// classes implementing .
        /// 
        /// Should a new extension object be
        /// created if it does not already exist?
        /// The extension field to check (and possibly update).
        /// The extension object if it exists (or was created), or null
        /// if the extension object does not exist or is not available.
        /// The createIfMissing argument is false during serialization,
        /// and true during deserialization upon encountering unexpected fields.
        public static IExtension GetExtensionObject(ref IExtension extensionObject, bool createIfMissing)
        {
            if (createIfMissing && extensionObject == null)
            {
                extensionObject = new BufferExtension();
            }
            return extensionObject;
        }
#if !NO_RUNTIME
        /// 
        /// Appends the value as an additional (unexpected) data-field for the instance.
        /// Note that for non-repeated sub-objects, this equates to a merge operation;
        /// for repeated sub-objects this adds a new instance to the set; for simple
        /// values the new value supercedes the old value.
        /// 
        /// Note that appending a value does not remove the old value from
        /// the stream; avoid repeatedly appending values for the same field.
        /// The type of the value to append.
        /// The extensible object to append the value to.
        /// The field identifier; the tag should not be defined as a known data-field for the instance.
        /// The value to append.
        public static void AppendValue(IExtensible instance, int tag, TValue value)
        {
            AppendValue(instance, tag, DataFormat.Default, value);
        }
        /// 
        /// Appends the value as an additional (unexpected) data-field for the instance.
        /// Note that for non-repeated sub-objects, this equates to a merge operation;
        /// for repeated sub-objects this adds a new instance to the set; for simple
        /// values the new value supercedes the old value.
        /// 
        /// Note that appending a value does not remove the old value from
        /// the stream; avoid repeatedly appending values for the same field.
        /// The data-type of the field.
        /// The data-format to use when encoding the value.
        /// The extensible object to append the value to.
        /// The field identifier; the tag should not be defined as a known data-field for the instance.
        /// The value to append.
        public static void AppendValue(IExtensible instance, int tag, DataFormat format, TValue value)
        {
            ExtensibleUtil.AppendExtendValue(RuntimeTypeModel.Default, instance, tag, format, value);
        }
        /// 
        /// Queries an extensible object for an additional (unexpected) data-field for the instance.
        /// The value returned is the composed value after merging any duplicated content; if the
        /// value is "repeated" (a list), then use GetValues instead.
        /// 
        /// The data-type of the field.
        /// The extensible object to obtain the value from.
        /// The field identifier; the tag should not be defined as a known data-field for the instance.
        /// The effective value of the field, or the default value if not found.
        public static TValue GetValue(IExtensible instance, int tag)
        {
            return GetValue(instance, tag, DataFormat.Default);
        }
        /// 
        /// Queries an extensible object for an additional (unexpected) data-field for the instance.
        /// The value returned is the composed value after merging any duplicated content; if the
        /// value is "repeated" (a list), then use GetValues instead.
        /// 
        /// The data-type of the field.
        /// The extensible object to obtain the value from.
        /// The field identifier; the tag should not be defined as a known data-field for the instance.
        /// The data-format to use when decoding the value.
        /// The effective value of the field, or the default value if not found.
        public static TValue GetValue(IExtensible instance, int tag, DataFormat format)
        {
            TryGetValue(instance, tag, format, out TValue value);
            return value;
        }
        /// 
        /// Queries an extensible object for an additional (unexpected) data-field for the instance.
        /// The value returned (in "value") is the composed value after merging any duplicated content;
        /// if the value is "repeated" (a list), then use GetValues instead.
        /// 
        /// The data-type of the field.
        /// The effective value of the field, or the default value if not found.
        /// The extensible object to obtain the value from.
        /// The field identifier; the tag should not be defined as a known data-field for the instance.
        /// True if data for the field was present, false otherwise.
        public static bool TryGetValue(IExtensible instance, int tag, out TValue value)
        {
            return TryGetValue(instance, tag, DataFormat.Default, out value);
        }
        /// 
        /// Queries an extensible object for an additional (unexpected) data-field for the instance.
        /// The value returned (in "value") is the composed value after merging any duplicated content;
        /// if the value is "repeated" (a list), then use GetValues instead.
        /// 
        /// The data-type of the field.
        /// The effective value of the field, or the default value if not found.
        /// The extensible object to obtain the value from.
        /// The field identifier; the tag should not be defined as a known data-field for the instance.
        /// The data-format to use when decoding the value.
        /// True if data for the field was present, false otherwise.
        public static bool TryGetValue(IExtensible instance, int tag, DataFormat format, out TValue value)
        {
            return TryGetValue(instance, tag, format, false, out value);
        }
        /// 
        /// Queries an extensible object for an additional (unexpected) data-field for the instance.
        /// The value returned (in "value") is the composed value after merging any duplicated content;
        /// if the value is "repeated" (a list), then use GetValues instead.
        /// 
        /// The data-type of the field.
        /// The effective value of the field, or the default value if not found.
        /// The extensible object to obtain the value from.
        /// The field identifier; the tag should not be defined as a known data-field for the instance.
        /// The data-format to use when decoding the value.
        /// Allow tags that are present as part of the definition; for example, to query unknown enum values.
        /// True if data for the field was present, false otherwise.
        public static bool TryGetValue(IExtensible instance, int tag, DataFormat format, bool allowDefinedTag, out TValue value)
        {
            value = default;
            bool set = false;
            foreach (TValue val in ExtensibleUtil.GetExtendedValues(instance, tag, format, true, allowDefinedTag))
            {
                // expecting at most one yield...
                // but don't break; need to read entire stream
                value = val;
                set = true;
            }
            return set;
        }
        /// 
        /// Queries an extensible object for an additional (unexpected) data-field for the instance.
        /// Each occurrence of the field is yielded separately, making this usage suitable for "repeated"
        /// (list) fields.
        /// 
        /// The extended data is processed lazily as the enumerator is iterated.
        /// The data-type of the field.
        /// The extensible object to obtain the value from.
        /// The field identifier; the tag should not be defined as a known data-field for the instance.
        /// An enumerator that yields each occurrence of the field.
        public static IEnumerable GetValues(IExtensible instance, int tag)
        {
            return ExtensibleUtil.GetExtendedValues(instance, tag, DataFormat.Default, false, false);
        }
        /// 
        /// Queries an extensible object for an additional (unexpected) data-field for the instance.
        /// Each occurrence of the field is yielded separately, making this usage suitable for "repeated"
        /// (list) fields.
        /// 
        /// The extended data is processed lazily as the enumerator is iterated.
        /// The data-type of the field.
        /// The extensible object to obtain the value from.
        /// The field identifier; the tag should not be defined as a known data-field for the instance.
        /// The data-format to use when decoding the value.
        /// An enumerator that yields each occurrence of the field.
        public static IEnumerable GetValues(IExtensible instance, int tag, DataFormat format)
        {
            return ExtensibleUtil.GetExtendedValues(instance, tag, format, false, false);
        }
#endif
        /// 
        /// Queries an extensible object for an additional (unexpected) data-field for the instance.
        /// The value returned (in "value") is the composed value after merging any duplicated content;
        /// if the value is "repeated" (a list), then use GetValues instead.
        /// 
        /// The data-type of the field.
        /// The model to use for configuration.
        /// The effective value of the field, or the default value if not found.
        /// The extensible object to obtain the value from.
        /// The field identifier; the tag should not be defined as a known data-field for the instance.
        /// The data-format to use when decoding the value.
        /// Allow tags that are present as part of the definition; for example, to query unknown enum values.
        /// True if data for the field was present, false otherwise.
        public static bool TryGetValue(TypeModel model, Type type, IExtensible instance, int tag, DataFormat format, bool allowDefinedTag, out object value)
        {
            value = null;
            bool set = false;
            foreach (object val in ExtensibleUtil.GetExtendedValues(model, type, instance, tag, format, true, allowDefinedTag))
            {
                // expecting at most one yield...
                // but don't break; need to read entire stream
                value = val;
                set = true;
            }
            return set;
        }
        /// 
        /// Queries an extensible object for an additional (unexpected) data-field for the instance.
        /// Each occurrence of the field is yielded separately, making this usage suitable for "repeated"
        /// (list) fields.
        /// 
        /// The extended data is processed lazily as the enumerator is iterated.
        /// The model to use for configuration.
        /// The data-type of the field.
        /// The extensible object to obtain the value from.
        /// The field identifier; the tag should not be defined as a known data-field for the instance.
        /// The data-format to use when decoding the value.
        /// An enumerator that yields each occurrence of the field.
        public static IEnumerable GetValues(TypeModel model, Type type, IExtensible instance, int tag, DataFormat format)
        {
            return ExtensibleUtil.GetExtendedValues(model, type, instance, tag, format, false, false);
        }
        /// 
        /// Appends the value as an additional (unexpected) data-field for the instance.
        /// Note that for non-repeated sub-objects, this equates to a merge operation;
        /// for repeated sub-objects this adds a new instance to the set; for simple
        /// values the new value supercedes the old value.
        /// 
        /// Note that appending a value does not remove the old value from
        /// the stream; avoid repeatedly appending values for the same field.
        /// The model to use for configuration.
        /// The data-format to use when encoding the value.
        /// The extensible object to append the value to.
        /// The field identifier; the tag should not be defined as a known data-field for the instance.
        /// The value to append.
        public static void AppendValue(TypeModel model, IExtensible instance, int tag, DataFormat format, object value)
        {
            ExtensibleUtil.AppendExtendValue(model, instance, tag, format, value);
        }
    }
}