///
/// Copyright (c) 2016 Dropbox, Inc. All rights reserved.
///
/// Auto-generated by Stone, do not modify.
///

import Foundation

/// Datatypes and serializers for the {{ namespace.name }} namespace
public class {{ fmt_class(namespace.name) }} {
{% for data_type in namespace.linearize_data_types() %}
    {{ data_type_doc(data_type) }}
    {% if is_struct_type(data_type) %}
    public class {{ fmt_class(data_type.name) }}: {{ 'CustomStringConvertible, JSONRepresentable' if not data_type.parent_type else fmt_type(data_type.parent_type) }} {
        {% for field in data_type.fields %}
        {{ struct_field_doc(field) }}
        public let {{ fmt_var(field.name) }}: {{ fmt_type(field.data_type) }}
        {% endfor %}
        {% if data_type.fields %}
        public init({{ func_args(struct_init_args(data_type)) }}) {
            {% for field in data_type.fields %}
            {% if determine_validator_type(field.data_type, fmt_var(field.name)) %}
            {{ determine_validator_type(field.data_type, fmt_var(field.name)) }}({{ fmt_var(field.name) }})
            {% endif %}
            self.{{ fmt_var(field.name) }} = {{ fmt_var(field.name) }}
            {% endfor %}
            {% if data_type.parent_type %}
            super.init({{ field_name_args(data_type.parent_type) }})
            {% endif %}
        }
        {% endif %}

        {% if not data_type.parent_type %}
        func json() throws -> JSON {
            try {{ fmt_class(data_type.name) }}Serializer().serialize(self)
        }
        {% endif %}

        {{ 'public var' if not data_type.parent_type else 'public override var' }} description: String {
            do {
                return "\(SerializeUtil.prepareJSONForSerialization(try {{ fmt_class(data_type.name) }}Serializer().serialize(self)))"
            } catch {
                return "Failed to generate description for {{ fmt_class(data_type.name) }}: \(error)"
            }
        }
    }
    public class {{ fmt_class(data_type.name) }}Serializer: JSONSerializer {
        public init() { }
        public func serialize(_ value: {{ fmt_class(data_type.name) }}) throws -> JSON {
            {% if data_type.all_fields %}
            {{ 'var' if data_type.has_enumerated_subtypes() else 'let' }} output = [
            {% for field in data_type.all_fields %}
            "{{ field.name }}": try {{ fmt_serial_obj(field.data_type) }}.serialize(value.{{ fmt_var(field.name) }}),
            {% endfor %}
            ]
            {% else %}
            {{ 'var' if data_type.has_enumerated_subtypes() else 'let' }} output = [String: JSON]()
            {% endif %}
            {% if data_type.has_enumerated_subtypes() %}
            switch value {
                {% for tags, subtype in data_type.get_all_subtypes_with_tags() if tags %}
                case let {{ fmt_var(tags[0]) }} as {{ fmt_type(subtype) }}:
                    for (k, v) in try Serialization.getFields({{ fmt_serial_obj(subtype) }}.serialize({{ fmt_var(tags[0]) }})) {
                        output[k] = v
                    }
                    output[".tag"] = .str("{{ tags[0] }}")
                {% endfor %}
                default:
                    throw JSONSerializerError.unexpectedSubtype(type: {{ fmt_class(data_type.name) }}.self, subtype: value)
            }
            {% endif %}
            return .dictionary(output)
        }
        public func deserialize(_ json: JSON) throws -> {{ fmt_class(data_type.name) }} {
            switch json {
                case .dictionary({{ "let dict" if data_type.all_fields or data_type.has_enumerated_subtypes() else "_" }}):
                    {% if data_type.has_enumerated_subtypes() %}
                    let tag = try Serialization.getTag(dict)
                    switch tag {
                        {% for tags, subtype in data_type.get_all_subtypes_with_tags() if tags %}
                        case "{{ tags[0] }}":
                            return try {{ fmt_serial_obj(subtype) }}.deserialize(json)
                        {% endfor %}
                        default:
                            {% if data_type.is_catch_all() %}
                            {% for field in data_type.all_fields %}
                            let {{ fmt_var(field.name) }} = try {{ fmt_serial_obj(field.data_type) }}.deserialize(dict["{{ field.name }}"] ?? {{ fmt_default_value(field) if field.has_default else '.null' }})
                            {% endfor %}
                            return {{ fmt_class(data_type.name) }}({{ field_name_args(data_type) }})
                            {% else %}
                            throw JSONSerializerError.unknownTag(type: {{ fmt_class(data_type.name) }}.self, json: json, tag: tag)
                            {% endif %}
                    }
                    {% else %}
                    {% for field in data_type.all_fields %}
                    let {{ fmt_var(field.name) }} = try {{ fmt_serial_obj(field.data_type) }}.deserialize(dict["{{ field.name }}"] ?? {{ fmt_default_value(field) if field.has_default else '.null' }})
                    {% endfor %}
                    return {{ fmt_class(data_type.name) }}({{ field_name_args(data_type) }})
                    {% endif %}
                default:
                    throw JSONSerializerError.deserializeError(type: {{ fmt_class(data_type.name) }}.self, json: json)
            }
        }
    }
    {% elif is_union_type(data_type) %}
    public enum {{ fmt_class(data_type.name) }}: CustomStringConvertible, JSONRepresentable {
        {% for field in data_type.all_fields %}
        {{ union_field_doc(field) }}
        case {{ fmt_var(field.name) }}{{ format_tag_type(field.data_type) }}
        {% endfor %}

        func json() throws -> JSON {
            try {{ fmt_class(data_type.name) }}Serializer().serialize(self)
        }

        public var description: String {
            do {
                return "\(SerializeUtil.prepareJSONForSerialization(try {{ fmt_class(data_type.name) }}Serializer().serialize(self)))"
            } catch {
                return "Failed to generate description for {{ fmt_class(data_type.name) }}: \(error)"
            }
        }
    }
    public class {{ fmt_class(data_type.name) }}Serializer: JSONSerializer {
        public init() { }
        public func serialize(_ value: {{ fmt_class(data_type.name) }}) throws -> JSON {
            switch value {
                {% for field in data_type.all_fields %}
                case .{{ fmt_var(field.name) }}{{ '' if is_void_type(field.data_type) else '(let arg)' }}:
                    {% if is_void_type(field.data_type) %}
                    var d = [String: JSON]()
                    {% elif is_struct_type(field.data_type) and not field.data_type.has_enumerated_subtypes() %}
                    var d = try Serialization.getFields({{ fmt_serial_obj(field.data_type) }}.serialize(arg))
                    {% else %}
                    var d = try ["{{ field.name }}": {{ fmt_serial_obj(field.data_type) }}.serialize(arg)]
                    {% endif %}
                    d[".tag"] = .str("{{ field.name }}")
                    return .dictionary(d)
                {% endfor %}
            }
        }
        public func deserialize(_ json: JSON) throws -> {{ fmt_class(data_type.name) }} {
            switch json {
                case .dictionary(let d):
                    let tag = try Serialization.getTag(d)
                    switch tag {
                        {% for field in data_type.all_fields %}
                        case "{{ field.name }}":
                            {% if is_void_type(field.data_type) %}
                            return {{ tag_type(data_type, field) }}
                            {% else %}
                            {% if is_struct_type(field.data_type) and not field.data_type.has_enumerated_subtypes() %}
                            let v = try {{ fmt_serial_obj(field.data_type) }}.deserialize(json)
                            {% else %}
                            let v = try {{ fmt_serial_obj(field.data_type) }}.deserialize(d["{{field.name}}"] ?? .null)
                            {% endif %}
                            return {{ tag_type(data_type, field) }}(v)
                            {% endif %}
                        {% endfor %}
                        default:
                            {% if data_type.catch_all_field %}
                            return {{ tag_type(data_type, data_type.catch_all_field) }}
                            {% else %}
                            throw JSONSerializerError.unknownTag(type: {{ fmt_class(data_type.name) }}.self, json: json, tag: tag)
                            {% endif %}
                    }
                default:
                    throw JSONSerializerError.deserializeError(type: {{ fmt_class(data_type.name) }}.self, json: json)
            }
        }
    }
    {% endif %}

{% endfor %}
{% if namespace.routes %}

    /// Stone Route Objects

    {% for route in namespace.routes %}
    static let {{ fmt_func(route.name, route.version) }} = Route(
        name: "{{ fmt_route_name(route) }}",
        version: {{ route.version }},
        namespace: "{{ namespace.name }}",
        deprecated: {{ 'true' if route.deprecated is not none else 'false' }},
        argSerializer: {{ fmt_serial_obj(route.arg_data_type) }},
        responseSerializer: {{ fmt_serial_obj(route.result_data_type) }},
        errorSerializer: {{ fmt_serial_obj(route.error_data_type) }},
        attributes: RouteAttributes({{ route_schema_attrs(route_schema, route) }})
    )
    {% endfor %}
{% endif %}
}

