IProtoSerializerWithWireType DecorateValueSerializer(Type type, BinaryDataFormat?dynamicTypeDataFormat, ref ValueFormat format, IProtoSerializerWithWireType ser)
        {
            // Uri decorator is applied after default value
            // because default value for Uri is treated as string

            if (ser.ExpectedType == _model.MapType(typeof(string)) && type == _model.MapType(typeof(Uri)))
            {
                ser = new UriDecorator(_model, ser);
            }
#if PORTABLE
            else if (ser.ExpectedType == _model.MapType(typeof(string)) && type.FullName == typeof(Uri).FullName)
            {
                // In PCLs, the Uri type may not match (WinRT uses Internal/Uri, .Net uses System/Uri)
                ser = new ReflectedUriDecorator(type, _model, ser);
            }
#endif
            if (dynamicTypeDataFormat != null)
            {
                ser = new NetObjectValueDecorator(type, format == ValueFormat.Reference || format == ValueFormat.LateReference, dynamicTypeDataFormat.Value, !_model.ProtoCompatibility.SuppressNullWireType, _model);
            }
            else if (MetaType.IsNetObjectValueDecoratorNecessary(_model, format))
            {
                ser = new NetObjectValueDecorator(
                    ser,
                    Helpers.GetNullableUnderlyingType(type) != null,
                    format == ValueFormat.Reference || format == ValueFormat.LateReference,
                    format == ValueFormat.LateReference && CanTypeBeAsLateReferenceOnBuildStage(_model.GetKey(type, false, true), _model),
                    !_model.ProtoCompatibility.SuppressNullWireType,
                    _model);
            }
            else
            {
                format = ValueFormat.Compact;
            }

            return(ser);
        }
        IProtoSerializerWithWireType BuildValueFinalSerializer(ValueSerializationSettings settings, bool isMemberOrNested, out WireType wireType, int levelNumber)
        {
            object defaultValue;
            var    l = CompleteLevel(settings, levelNumber, out defaultValue).Basic;

            // to ensure that model can be copied and used again
            for (int i = 1; i <= 3; i++)
            {
                var l2 = CompleteLevel(settings, levelNumber, out defaultValue);
                Debug.Assert(l.Equals(l2.Basic));
            }

            Debug.Assert(l.ContentBinaryFormatHint != null, "l.ContentBinaryFormatHint != null");
            Debug.Assert(l.WriteAsDynamicType != null, "l.WriteAsDynamicType != null");
            Debug.Assert(l.Collection.Append != null, "l.Collection.Append != null");

            // postpone all checks for types when adding member till BuildSerializer, resolve everything only on buildserializer! till that have only local not inherited settings.
            // do not allow EnumPassthru and other settings to affect anything until buildling serializer
            wireType = 0;
            Type itemType = l.Collection.ItemType ?? l.EffectiveType;

            bool itemTypeCanBeNull = CanTypeBeNull(itemType);

            bool isPacked = CanBePackedCollection(l);

            IProtoSerializerWithWireType ser = null;

            if (l.Collection.IsCollection)
            {
                Type     nestedItemType    = null;
                Type     nestedDefaultType = null;
                int      idx  = _model.FindOrAddAuto(itemType, false, true, false);
                MetaType type = idx < 0 ? null : _model[itemType];
                if (!type?.GetFinalSettingsCopy().IgnoreListHandling ?? true)
                {
                    MetaType.ResolveListTypes(_model, itemType, ref nestedItemType, ref nestedDefaultType);
                }

                bool itemIsNestedCollection = nestedItemType != null;

                // primitive types except System.Object may be handled as nested through recursion
                bool tryHandleAsRegistered = !isMemberOrNested || itemType == _model.MapType(typeof(object));

                if (tryHandleAsRegistered)
                {
                    var nestedLevel = settings.GetSettingsCopy(levelNumber + 1);
                    nestedLevel = PrepareNestedLevelForBuild(nestedLevel, itemType);
                    settings.SetSettings(nestedLevel, levelNumber + 1);
                    // should use its level settings and merge from type, ...
                    ser = BuildValueFinalSerializer(settings, true, out wireType, levelNumber + 1);

                    //object dummy = null;

                    //ser = TryGetCoreSerializer(l.ContentBinaryFormatHint.Value, nestedLevel.Basic.EffectiveType, out wireType, ref nestedLevel.Basic.Format, nestedLevel.Basic.WriteAsDynamicType.GetValueOrDefault(), l.Collection.Append.Value, isPacked, true, ref dummy);
                    //if (ser != null)
                    //    ThrowIfHasMoreLevels(settings, levelNumber + 1, l, ", no more nested type detected");
                }
                else if (!itemIsNestedCollection)
                {
                    var nestedLevel = settings.GetSettingsCopy(levelNumber + 1);
                    nestedLevel = PrepareNestedLevelForBuild(nestedLevel, itemType);
                    nestedLevel.Basic.Collection.ItemType = null; // IgnoreListHandling or not a collection
                    settings.SetSettings(nestedLevel, levelNumber + 1);

                    ser = BuildValueFinalSerializer(settings, true, out wireType, levelNumber + 1);
                }

                if (ser == null && itemIsNestedCollection)
                {
                    // if we already tried to lookup registered type no need to do it again

                    MetaType metaType;
                    if (_model.FindOrAddAuto(itemType, false, true, false, out metaType) >= 0)
                    {
                        nestedDefaultType = metaType.GetFinalSettingsCopy().ConstructType ?? nestedDefaultType ?? metaType.Type;
                    }

                    var nestedLevel = settings.GetSettingsCopy(levelNumber + 1);

                    if (nestedLevel.Basic.Collection.ConcreteType == null)
                    {
                        nestedLevel.Basic.Collection.ConcreteType = nestedDefaultType;
                    }

                    if (nestedLevel.IsNotAssignable)
                    {
                        throw new ProtoException("Nested collection item should be assignable");
                    }

                    nestedLevel.Basic.Collection.Append = false;
                    // TODO throw if set to true: throw new ProtoException("AppendCollection is not supported for nested types: " + objectType.Name);

                    if (nestedLevel.Basic.Collection.ItemType == null)
                    {
                        nestedLevel.Basic.Collection.ItemType = nestedItemType;
                    }
                    else if (!Helpers.IsAssignableFrom(nestedItemType, nestedLevel.Basic.Collection.ItemType))
                    {
                        throw new ProtoException(
                                  "Nested collection item type " + nestedLevel.Basic.Collection.ItemType + " is not assignable to " + nestedItemType + " for declared collection type " +
                                  l.EffectiveType);
                    }

                    nestedLevel = PrepareNestedLevelForBuild(nestedLevel, itemType);

                    settings.SetSettings(nestedLevel, levelNumber + 1);

                    WireType wt;
                    ser = BuildValueFinalSerializer(
                        settings,
                        true,
                        out wt,
                        levelNumber + 1);

                    isPacked = false;
                }
            }
            else
            {
                // handled outside and not wrapped with collection
                if (!isMemberOrNested)
                {
                    l.Format = ValueFormat.Compact;
                }

                isPacked = false; // it's not even a collection
                ser      = TryGetCoreSerializer(l.ContentBinaryFormatHint.Value, itemType, out wireType, ref l.Format, l.WriteAsDynamicType.Value, l.Collection.Append.Value, isPacked, true, ref defaultValue);
                if (ser != null)
                {
                    ThrowIfHasMoreLevels(settings, levelNumber, l, ", no more nested type detected");
                }
            }

            if (ser == null)
            {
                throw new InvalidOperationException("No serializer defined for type: " + itemType.FullName);
            }

            if (itemTypeCanBeNull &&
                (
                    (Helpers.GetNullableUnderlyingType(itemType) != null && Helpers.GetNullableUnderlyingType(ser.ExpectedType) == null)
                    // TODO get rid of ugly casting, maybe use builder pattern
                    || (!Helpers.IsValueType(itemType) && !(ser is NetObjectValueDecorator))
                ))
            {
                // nested level may be not collection and already wrapped with nonull, may later add check whether handled as registered vs as nested
                if (!(ser is NoNullDecorator))
                {
                    // if not wrapped with net obj - wrap with write-null check
                    ser = new NoNullDecorator(_model, ser, l.Collection.ItemType != null); // throw only for collection elements, otherwise don't write
                }
            }

            if (itemType != ser.ExpectedType && (!l.WriteAsDynamicType.Value || !Helpers.IsAssignableFrom(ser.ExpectedType, itemType)))
            {
                throw new ProtoException(string.Format("Wrong type in the tail; expected {0}, received {1}", ser.ExpectedType, itemType));
            }

            // apply lists if appropriate
            if (l.Collection.IsCollection)
            {
                bool protoCompatibility = l.Collection.Format == CollectionFormat.Protobuf || l.Collection.Format == CollectionFormat.ProtobufNotPacked;

                WireType packedReadWt;
                if (!protoCompatibility)
                {
                    packedReadWt = WireType.None;
                    Debug.Assert(!isPacked); // should be determinated before passing to TryGetCoreSerializer
                    isPacked = false;
                }
                else
                {
                    packedReadWt = CanPack(l.Collection.ItemType, l.ContentBinaryFormatHint)
                                       ? l.Collection.PackedWireTypeForRead.GetValueOrDefault(wireType)
                                       : WireType.None;
                }

                if (l.EffectiveType.IsArray)
                {
                    if (l.EffectiveType.GetArrayRank() == 1)
                    {
                        ser = new ArrayDecorator(
                            _model,
                            ser,
                            isPacked,
                            packedReadWt,
                            l.EffectiveType,
                            !l.Collection.Append.Value,
                            l.Collection.ArrayLengthReadLimit.Value,
                            protoCompatibility);
                    }
                    else
                    {
                        if (protoCompatibility)
                        {
                            throw new NotSupportedException("Multi-dimensional arrays are supported only in Enhanced collection format");
                        }

                        ser = new MultiDimensionalArrayDecorator(
                            _model,
                            ser,
                            l.EffectiveType,
                            !l.Collection.Append.Value,
                            l.Collection.ArrayLengthReadLimit.Value);
                    }
                }
                else
                {
                    ser = ListDecorator.Create(
                        _model,
                        l.EffectiveType,
                        l.Collection.ConcreteType,
                        ser,
                        isPacked,
                        packedReadWt,
                        !l.Collection.Append.Value,
                        protoCompatibility,
                        true);
                }

                if (isMemberOrNested)
                {
                    if (MetaType.IsNetObjectValueDecoratorNecessary(_model, l.Format))
                    {
                        ser = new NetObjectValueDecorator(
                            ser,
                            Helpers.GetNullableUnderlyingType(l.EffectiveType) != null,
                            l.Format == ValueFormat.Reference || l.Format == ValueFormat.LateReference,
                            l.Format == ValueFormat.LateReference && CanTypeBeAsLateReferenceOnBuildStage(_model.GetKey(l.EffectiveType, false, true), _model),
                            !_model.ProtoCompatibility.SuppressNullWireType,
                            _model);
                    }
                    else if (!Helpers.IsValueType(l.EffectiveType) || Helpers.GetNullableUnderlyingType(l.EffectiveType) != null)
                    {
                        ser = new NoNullDecorator(_model, ser, false);
                    }
                }


                if (l.EffectiveType != ser.ExpectedType && (!l.WriteAsDynamicType.Value || !Helpers.IsAssignableFrom(ser.ExpectedType, itemType)))
                {
                    throw new ProtoException(string.Format("Wrong type in the tail; expected {0}, received {1}", ser.ExpectedType, itemType));
                }
            }

            if (levelNumber == 0 && defaultValue != null)
            {
                ser = new DefaultValueDecorator(_model, defaultValue, ser);
            }

            return(ser);
        }