from contextlib import contextmanager

from rust import RustHelperBackend
from stone import ir
from stone.backends.helpers import split_words


def fmt_shouting_snake(name):
    return '_'.join([word.upper() for word in split_words(name)])


class RustBackend(RustHelperBackend):
    def __init__(self, target_folder_path, args):
        super(RustBackend, self).__init__(target_folder_path, args)
        self._modules = []
        self.preserve_aliases = True

    # File Generators

    def generate(self, api):
        self._all_types = {ns.name: {typ.name: typ for typ in ns.data_types}
                           for ns in api.namespaces.values()}
        for namespace in api.namespaces.values():
            self._emit_namespace(namespace)
        self._generate_mod_file()

    def _generate_mod_file(self):
        with self.output_to_relative_path('mod.rs'):
            self._emit_header()
            for module in self._modules:
                self.emit(u'#[cfg(feature = "dbx_{}")]'.format(module))
                self.emit(u'pub mod {};'.format(
                    self.namespace_name_raw(module)))
                self.emit()
            with self.block(u'pub(crate) fn eat_json_fields<\'de, V>(map: &mut V)'
                            u' -> Result<(), V::Error>'
                            u' where V: ::serde::de::MapAccess<\'de>'):
                with self.block(u'while let Some(_) = map.next_entry::<&str, ::serde_json::Value>()?'):
                    self.emit(u'/* ignore */')
                self.emit(u'Ok(())')

    # Type Emitters

    def _emit_namespace(self, namespace):
        ns = self.namespace_name(namespace)
        with self.output_to_relative_path(ns + '.rs'):
            self._current_namespace = namespace.name
            self._emit_header()

            if namespace.doc is not None:
                self._emit_doc(namespace.doc, prefix=u'//!', qualify_type_names=True)
                self.emit()

            for alias in namespace.aliases:
                self._emit_alias(alias)
            if namespace.aliases:
                self.emit()

            for fn in namespace.routes:
                self._emit_route(ns, fn)

            for typ in namespace.data_types:
                self._current_type = typ
                if isinstance(typ, ir.Struct):
                    if typ.has_enumerated_subtypes():
                        self._emit_polymorphic_struct(typ)
                    else:
                        self._emit_struct(typ)
                elif isinstance(typ, ir.Union):
                    self._emit_union(typ)
                else:
                    raise RuntimeError('WARNING: unhandled type "{}" of field "{}"'
                                       .format(type(typ).__name__, typ.name))

        self._modules.append(namespace.name)

    def _emit_header(self):
        self.emit(u'// DO NOT EDIT')
        self.emit(u'// This file was @generated by Stone')
        self.emit()
        self.emit(u'#![allow(')
        self.emit(u'    clippy::too_many_arguments,')
        self.emit(u'    clippy::large_enum_variant,')
        self.emit(u'    clippy::doc_markdown,')
        self.emit(u')]')
        self.emit()

    def _emit_struct(self, struct):
        struct_name = self.struct_name(struct)
        self._emit_doc(struct.doc)
        self.emit(u'#[derive(Debug)]')
        with self.block(u'pub struct {}'.format(struct_name)):
            for field in struct.all_fields:
                self._emit_doc(field.doc)
                self.emit(u'pub {}: {},'.format(
                    self.field_name(field),
                    self._rust_type(field.data_type)))
        self.emit()

        if not struct.all_required_fields:
            self._impl_default_for_struct(struct)
            self.emit()

        if struct.all_required_fields:
            with self._impl_struct(struct):
                if struct.all_required_fields:
                    self._emit_new_for_struct(struct)
                self.emit()
            self.emit()

        self._impl_serde_for_struct(struct)

    def _emit_polymorphic_struct(self, struct):
        enum_name = self.enum_name(struct)
        self._emit_doc(struct.doc)
        self.emit(u'#[derive(Debug)]')
        with self.block(u'pub enum {}'.format(enum_name)):
            for subtype in struct.get_enumerated_subtypes():
                self.emit(u'{}({}),'.format(
                    self.enum_variant_name(subtype),
                    self._rust_type(subtype.data_type)))
            if struct.is_catch_all():
                self.emit(u'_Unknown')
        self.emit()

        self._impl_serde_for_polymorphic_struct(struct)

    def _emit_union(self, union):
        enum_name = self.enum_name(union)
        self._emit_doc(union.doc)
        self.emit(u'#[derive(Debug)]')
        with self.block(u'pub enum {}'.format(enum_name)):
            for field in union.all_fields:
                if field.catch_all:
                    # Handle the 'Other' variant at the end.
                    continue
                self._emit_doc(field.doc)
                variant_name = self.enum_variant_name(field)
                if isinstance(field.data_type, ir.Void):
                    self.emit(u'{},'.format(variant_name))
                else:
                    self.emit(u'{}({}),'.format(variant_name, self._rust_type(field.data_type)))
            if not union.closed:
                self.emit_wrapped_text(
                        u'Catch-all used for unrecognized values returned from the server.'
                        u' Encountering this value typically indicates that this SDK version is'
                        u' out of date.',
                        prefix=u'/// ', width=100)
                self.emit(u'Other,')
        self.emit()

        self._impl_serde_for_union(union)

        if union.name.endswith('Error'):
            self._impl_error(enum_name)

    def _emit_route(self, ns, fn):
        route_name = self.route_name(fn)
        self._emit_doc(fn.doc)
        host = fn.attrs.get('host', 'api')
        if host == 'api':
            endpoint = u'crate::client_trait::Endpoint::Api'
        elif host == 'content':
            endpoint = u'crate::client_trait::Endpoint::Content'
        elif host == 'notify':
            endpoint = u'crate::client_trait::Endpoint::Notify'
        else:
            raise RuntimeError(u'ERROR: unsupported endpoint: {}'.format(host))

        if fn.version > 1:
            name_with_version = "{}_v{}".format(fn.name, fn.version)
        else:
            name_with_version = fn.name

        arg_void = isinstance(fn.arg_data_type, ir.Void)
        style = fn.attrs.get('style', 'rpc')
        if style == 'rpc':
            with self.emit_rust_function_def(
                    route_name,
                    [u'client: &dyn crate::client_trait::HttpClient']
                        + ([] if arg_void else
                            [u'arg: &{}'.format(self._rust_type(fn.arg_data_type))]),
                    u'crate::Result<Result<{}, {}>>'.format(
                        self._rust_type(fn.result_data_type),
                        self._rust_type(fn.error_data_type)),
                    access=u'pub'):
                self.emit_rust_fn_call(
                    u'crate::client_helpers::request',
                    [u'client',
                        endpoint,
                        u'crate::client_trait::Style::Rpc',
                        u'"{}/{}"'.format(ns, name_with_version),
                        u'&()' if arg_void else u'arg',
                        u'None'])
        elif style == 'download':
            with self.emit_rust_function_def(
                    route_name,
                    [u'client: &dyn crate::client_trait::HttpClient']
                        + ([] if arg_void else
                            [u'arg: &{}'.format(self._rust_type(fn.arg_data_type))])
                        + [u'range_start: Option<u64>',
                            u'range_end: Option<u64>'],
                    u'crate::Result<Result<crate::client_trait::HttpRequestResult<{}>, {}>>'.format(
                        self._rust_type(fn.result_data_type),
                        self._rust_type(fn.error_data_type)),
                    access=u'pub'):
                self.emit_rust_fn_call(
                    u'crate::client_helpers::request_with_body',
                    [u'client',
                        endpoint,
                        u'crate::client_trait::Style::Download',
                        u'"{}/{}"'.format(ns, name_with_version),
                        u'&()' if arg_void else u'arg',
                        u'None',
                        u'range_start',
                        u'range_end'])
        elif style == 'upload':
            with self.emit_rust_function_def(
                    route_name,
                    [u'client: &dyn crate::client_trait::HttpClient']
                        + ([] if arg_void else
                            [u'arg: &{}'.format(self._rust_type(fn.arg_data_type))])
                        + [u'body: &[u8]'],
                    u'crate::Result<Result<{}, {}>>'.format(
                        self._rust_type(fn.result_data_type),
                        self._rust_type(fn.error_data_type)),
                    access=u'pub'):
                self.emit_rust_fn_call(
                    u'crate::client_helpers::request',
                    [u'client',
                        endpoint,
                        u'crate::client_trait::Style::Upload',
                        u'"{}/{}"'.format(ns, name_with_version),
                        u'&()' if arg_void else u'arg',
                        u'Some(body)'])
        else:
            raise RuntimeError(u'ERROR: unknown route style: {}'.format(style))
        self.emit()

    def _emit_alias(self, alias):
        alias_name = self.alias_name(alias)
        self.emit(u'pub type {} = {};'.format(alias_name, self._rust_type(alias.data_type)))

    # Serialization

    def _impl_serde_for_struct(self, struct):
        """
        Emit internal_deserialize() and possibly internal_deserialize_opt().
        internal_deserialize[_opt] takes a map and deserializes it into the struct. It reads the
        fields in whatever order; missing fields will be given their default value, or an error
        returned if they have no default. Errors will also be raised if a field is present more
        than once.
        The _opt deserializer returns a None if it reads exactly zero map keys, and is used for
        cases where the JSON has a tag, but omits all the fields to signify a null value. It is
        only emitted for types which have at least one required field, because if all fields are
        optional, there's no way to differentiate between a null value and one where all fields
        are default.
        """

        type_name = self.struct_name(struct)
        field_list_name = u'{}_FIELDS'.format(fmt_shouting_snake(struct.name))
        self.generate_multiline_list(
            list(u'"{}"'.format(field.name) for field in struct.all_fields),
            before='const {}: &[&str] = &'.format(field_list_name),
            after=';',
            delim=(u'[', u']'))
        # Only emit the _opt deserializer if there are required fields.
        optional = len(struct.all_required_fields) > 0
        with self._impl_struct(struct):
            if optional:
                # Convenience wrapper around _opt for the more common case where the struct is
                # NOT optional.
                with self.emit_rust_function_def(
                        u'internal_deserialize<\'de, V: ::serde::de::MapAccess<\'de>>',
                        [u'map: V'],
                        u'Result<{}, V::Error>'.format(type_name),
                        access=u'pub(crate)'):
                    self.emit(u'Self::internal_deserialize_opt(map, false)'
                              u'.map(Option::unwrap)')
                self.emit()
            else:
                self.emit(u'// no _opt deserializer')
            with self.emit_rust_function_def(
                    (u'internal_deserialize_opt' if optional else u'internal_deserialize')
                    + u'<\'de, V: ::serde::de::MapAccess<\'de>>',
                    [u'mut map: V']
                    + ([u'optional: bool'] if optional else []),
                    (u'Result<Option<{}>, V::Error>' if optional else u'Result<{}, V::Error>')
                    .format(type_name),
                    access=u'pub(crate)'):
                if len(struct.all_fields) == 0:
                    self.emit(u'// ignore any fields found; none are presently recognized')
                    self.emit(u'crate::eat_json_fields(&mut map)?;')
                    if optional:
                        self.emit(u'Ok(None)')
                    else:
                        self.emit(u'Ok({} {{}})'.format(type_name))
                else:
                    for field in struct.all_fields:
                        self.emit(u'let mut field_{} = None;'.format(self.field_name(field)))
                    if optional:
                        self.emit(u'let mut nothing = true;')
                    with self.block(u'while let Some(key) = map.next_key::<&str>()?'):
                        if optional:
                            self.emit(u'nothing = false;')
                        with self.block(u'match key'):
                            for field in struct.all_fields:
                                field_name = self.field_name(field)
                                with self.block(u'"{}" =>'.format(field.name)):
                                    with self.block(u'if field_{}.is_some()'.format(field_name)):
                                        self.emit(u'return Err(::serde::de::Error::duplicate_field('
                                                  u'"{}"));'
                                                  .format(field.name))
                                    self.emit(u'field_{} = Some(map.next_value()?);'
                                              .format(field_name))
                            with self.block(u'_ =>'):
                                self.emit(u'// unknown field allowed and ignored')
                                self.emit(u'map.next_value::<::serde_json::Value>()?;')
                    if optional:
                        with self.block(u'if optional && nothing'):
                            self.emit(u'return Ok(None);')
                    with self.block(u'let result = {}'.format(type_name), delim=(u'{', u'};')):
                        for field in struct.all_fields:
                            field_name = self.field_name(field)
                            if isinstance(field.data_type, ir.Nullable):
                                self.emit(u'{}: field_{},'.format(field_name, field_name))
                            elif field.has_default:
                                if isinstance(field.data_type, ir.String) \
                                        and not field.default:
                                    self.emit(u'{}: field_{}.unwrap_or_else(String::new),'
                                              .format(field_name, field_name))
                                elif ir.is_primitive_type(ir.unwrap_aliases(field.data_type)[0]):
                                    self.emit(u'{}: field_{}.unwrap_or({}),'
                                              .format(field_name,
                                                      field_name,
                                                      self._default_value(field)))
                                else:
                                    self.emit(u'{}: field_{}.unwrap_or_else(|| {}),'
                                              .format(field_name,
                                                      field_name,
                                                      self._default_value(field)))
                            else:
                                self.emit(u'{}: field_{}.ok_or_else(|| '
                                          u'::serde::de::Error::missing_field("{}"))?,'
                                          .format(field_name, field_name, field.name))
                    if optional:
                        self.emit(u'Ok(Some(result))')
                    else:
                        self.emit(u'Ok(result)')
            if struct.all_fields:
                self.emit()
                with self.emit_rust_function_def(
                        u'internal_serialize<S: ::serde::ser::Serializer>',
                        [u'&self', u's: &mut S::SerializeStruct'],
                        u'Result<(), S::Error>',
                        access=u'pub(crate)'):
                    self.emit(u'use serde::ser::SerializeStruct;')
                    self.generate_multiline_list(
                        list(u's.serialize_field("{}", &self.{})'
                             .format(field.name, self.field_name(field))
                             for field in struct.all_fields),
                        delim=(u'', u''),
                        sep='?;',
                        skip_last_sep=True)
        self.emit()
        with self._impl_deserialize(self.struct_name(struct)):
            self.emit(u'// struct deserializer')
            self.emit(u'use serde::de::{MapAccess, Visitor};')
            self.emit(u'struct StructVisitor;')
            with self.block(u'impl<\'de> Visitor<\'de> for StructVisitor'):
                self.emit(u'type Value = {};'.format(type_name))
                with self.emit_rust_function_def(
                        u'expecting',
                        [u'&self', u'f: &mut ::std::fmt::Formatter<\'_>'],
                        u'::std::fmt::Result'):
                    self.emit(u'f.write_str("a {} struct")'.format(struct.name))
                with self.emit_rust_function_def(
                        u'visit_map<V: MapAccess<\'de>>',
                        [u'self', u'map: V'],
                        u'Result<Self::Value, V::Error>'):
                    self.emit(u'{}::internal_deserialize(map)'.format(type_name))
            self.emit(u'deserializer.deserialize_struct("{}", {}, StructVisitor)'
                      .format(struct.name,
                              field_list_name))
        self.emit()
        with self._impl_serialize(type_name):
            self.emit(u'// struct serializer')
            self.emit(u'use serde::ser::SerializeStruct;')
            if not struct.all_fields:
                self.emit(u'serializer.serialize_struct("{}", 0)?.end()'.format(struct.name))
            else:
                self.emit(u'let mut s = serializer.serialize_struct("{}", {})?;'
                          .format(struct.name,
                                  len(struct.all_fields)))
                self.emit(u'self.internal_serialize::<S>(&mut s)?;')
                self.emit(u's.end()')
        self.emit()

    def _impl_serde_for_polymorphic_struct(self, struct):
        type_name = self.enum_name(struct)
        with self._impl_deserialize(type_name):
            self.emit(u'// polymorphic struct deserializer')
            self.emit(u'use serde::de::{self, MapAccess, Visitor};')
            self.emit(u'struct EnumVisitor;')
            with self.block(u'impl<\'de> Visitor<\'de> for EnumVisitor'):
                self.emit(u'type Value = {};'.format(type_name))
                with self.emit_rust_function_def(
                        u'expecting',
                        [u'&self', u'f: &mut ::std::fmt::Formatter<\'_>'],
                        u'::std::fmt::Result'):
                    self.emit(u'f.write_str("a {} structure")'.format(struct.name))
                with self.emit_rust_function_def(
                        u'visit_map<V: MapAccess<\'de>>',
                        [u'self', u'mut map: V'],
                        u'Result<Self::Value, V::Error>'):
                    with self.block(u'let tag = match map.next_key()?', after=';'):
                        self.emit(u'Some(".tag") => map.next_value()?,')
                        self.emit(u'_ => return Err(de::Error::missing_field(".tag"))')
                    with self.block(u'match tag'):
                        for subtype in struct.get_enumerated_subtypes():
                            variant_name = self.enum_variant_name(subtype)
                            if isinstance(subtype.data_type, ir.Void):
                                self.emit(u'"{}" => Ok({}::{}),'
                                          .format(subtype.name, type_name, variant_name))
                            elif isinstance(ir.unwrap_aliases(subtype.data_type)[0], ir.Struct) \
                                    and not subtype.data_type.has_enumerated_subtypes():
                                self.emit(u'"{}" => Ok({}::{}({}::internal_deserialize(map)?)),'
                                          .format(subtype.name,
                                                  type_name,
                                                  variant_name,
                                                  self._rust_type(subtype.data_type)))
                            else:
                                with self.block(u'"{}" =>'.format(subtype.name)):
                                    with self.block(u'if map.next_key()? != Some("{}")'
                                                    .format(subtype.name)):
                                        self.emit(u'Err(de::Error::missing_field("{}"));'
                                                  .format(subtype.name))
                                    self.emit(u'Ok({}::{}(map.next_value()?))'
                                              .format(type_name, variant_name))
                        if struct.is_catch_all():
                            with self.block(u'_ =>'):
                                # TODO(wfraser): it'd be cool to grab any fields in the parent,
                                # which are common to all variants, and stick them in the
                                # '_Unknown' enum vaiant.
                                # For now, just consume them and return a nullary variant.
                                self.emit(u'crate::eat_json_fields(&mut map)?;')
                                self.emit(u'Ok({}::_Unknown)'.format(type_name))
                        else:
                            self.emit(u'_ => Err(de::Error::unknown_variant(tag, VARIANTS))')
            self.generate_multiline_list(
                list(u'"{}"'.format(subtype.name)
                     for field in struct.get_enumerated_subtypes()),
                before='const VARIANTS: &[&str] = &',
                after=';',
                delim=(u'[', u']'))
            self.emit(u'deserializer.deserialize_struct("{}", VARIANTS, EnumVisitor)'.format(
                struct.name))
        self.emit()
        with self._impl_serialize(type_name):
            self.emit(u'// polymorphic struct serializer')
            self.emit(u'use serde::ser::SerializeStruct;')
            with self.block(u'match *self'):
                for subtype in struct.get_enumerated_subtypes():
                    variant_name = self.enum_variant_name(subtype)
                    with self.block(u'{}::{}(ref x) =>'.format(type_name, variant_name)):
                        self.emit(u'let mut s = serializer.serialize_struct("{}", {})?;'
                                  .format(type_name, len(subtype.data_type.all_fields) + 1))
                        self.emit(u's.serialize_field(".tag", "{}")?;'.format(subtype.name))
                        for field in subtype.data_type.all_fields:
                            self.emit(u's.serialize_field("{}", &x.{})?;'
                                      .format(field.name,
                                              self.field_name(field)))
                        self.emit(u's.end()')
                if struct.is_catch_all():
                    self.emit(u'{}::_Unknown => Err(::serde::ser::Error::custom("cannot serialize '
                              u'unknown variant"))'.format(
                                    type_name))
        self.emit()

    def _impl_serde_for_union(self, union):
        type_name = self.enum_name(union)
        with self._impl_deserialize(type_name):
            self.emit(u'// union deserializer')
            self.emit(u'use serde::de::{self, MapAccess, Visitor};')
            self.emit(u'struct EnumVisitor;')
            with self.block(u'impl<\'de> Visitor<\'de> for EnumVisitor'):
                self.emit(u'type Value = {};'.format(type_name))
                with self.emit_rust_function_def(
                        u'expecting',
                        [u'&self', u'f: &mut ::std::fmt::Formatter<\'_>'],
                        u'::std::fmt::Result'):
                    self.emit(u'f.write_str("a {} structure")'.format(union.name))
                with self.emit_rust_function_def(
                        u'visit_map<V: MapAccess<\'de>>',
                        [u'self', u'mut map: V'],
                        u'Result<Self::Value, V::Error>'):
                    with self.block(u'let tag: &str = match map.next_key()?', after=';'):
                        self.emit(u'Some(".tag") => map.next_value()?,')
                        self.emit(u'_ => return Err(de::Error::missing_field(".tag"))')
                    if len(union.all_fields) == 1 and union.all_fields[0].catch_all:
                        self.emit(u'// open enum with no defined variants')
                        self.emit(u'let _ = tag;') # hax
                        self.emit(u'crate::eat_json_fields(&mut map)?;')
                        self.emit(u'Ok({}::Other)'.format(type_name))
                    else:
                        with self.block(u'match tag'):
                            for field in union.all_fields:
                                if field.catch_all:
                                    # Handle the 'Other' variant at the end.
                                    continue
                                variant_name = self.enum_variant_name(field)
                                ultimate_type = ir.unwrap(field.data_type)[0]
                                if isinstance(field.data_type, ir.Void):
                                    with self.block(u'"{}" =>'.format(field.name)):
                                        self.emit(u'crate::eat_json_fields(&mut map)?;')
                                        self.emit(u'Ok({}::{})'.format(type_name, variant_name))
                                elif isinstance(ultimate_type, ir.Struct) \
                                        and not ultimate_type.has_enumerated_subtypes():
                                    if isinstance(ir.unwrap_aliases(field.data_type)[0], ir.Nullable):
                                        # A nullable here means we might have more fields that can be
                                        # deserialized into the inner type, or we might have nothing,
                                        # meaning None.
                                        if not ultimate_type.all_required_fields:
                                            raise RuntimeError('{}.{}: an optional struct with no'
                                                               ' required fields is ambiguous'
                                                               .format(union.name, field.name))
                                        self.emit(u'"{}" => Ok({}::{}({}::internal_deserialize_opt('
                                                  u'map, true)?)),'
                                                  .format(field.name,
                                                          type_name,
                                                          variant_name,
                                                          self._rust_type(ultimate_type)))
                                    else:
                                        self.emit(u'"{}" => Ok({}::{}({}::internal_deserialize(map)?)),'
                                                  .format(field.name,
                                                          type_name,
                                                          variant_name,
                                                          self._rust_type(field.data_type)))
                                else:
                                    with self.block(u'"{}" =>'.format(field.name)):
                                        with self.block(u'match map.next_key()?'):
                                            self.emit(u'Some("{}") => Ok({}::{}(map.next_value()?)),'
                                                      .format(field.name,
                                                              type_name,
                                                              variant_name))
                                            if isinstance(ir.unwrap_aliases(field.data_type)[0],
                                                          ir.Nullable):
                                                # if it's null, the field can be omitted entirely
                                                self.emit(u'None => Ok({}::{}(None)),'
                                                          .format(type_name, variant_name))
                                            else:
                                                self.emit(u'None => Err('
                                                          u'de::Error::missing_field("{}")),'
                                                          .format(field.name))
                                            self.emit(u'_ => Err(de::Error::unknown_field('
                                                      u'tag, VARIANTS))')
                            if not union.closed:
                                with self.block(u'_ =>'):
                                    self.emit(u'crate::eat_json_fields(&mut map)?;')
                                    self.emit(u'Ok({}::Other)'.format(type_name))
                            else:
                                self.emit(u'_ => Err(de::Error::unknown_variant(tag, VARIANTS))')
            self.generate_multiline_list(
                    list(u'"{}"'.format(field.name) for field in union.all_fields),
                    before='const VARIANTS: &[&str] = &',
                    after=';',
                    delim=(u'[', u']'),)
            self.emit(u'deserializer.deserialize_struct("{}", VARIANTS, EnumVisitor)'.format(
                union.name))
        self.emit()
        with self._impl_serialize(type_name):
            self.emit(u'// union serializer')
            if len(union.all_fields) == 1 and union.all_fields[0].catch_all:
                # special case: an open union with no variants defined.
                self.emit(u'#![allow(unused_variables)]')
                self.emit(u'Err(::serde::ser::Error::custom("cannot serialize an open union with '
                          u'no defined variants"))')
            else:
                self.emit(u'use serde::ser::SerializeStruct;')
                with self.block(u'match *self'):
                    for field in union.all_fields:
                        if field.catch_all:
                            # Handle the 'Other' variant at the end.
                            continue
                        variant_name = self.enum_variant_name(field)
                        if isinstance(field.data_type, ir.Void):
                            with self.block(u'{}::{} =>'.format(type_name, variant_name)):
                                self.emit(u'// unit')
                                self.emit(u'let mut s = serializer.serialize_struct("{}", 1)?;'
                                          .format(union.name))
                                self.emit(u's.serialize_field(".tag", "{}")?;'.format(field.name))
                                self.emit(u's.end()')
                        else:
                            ultimate_type = ir.unwrap(field.data_type)[0]
                            needs_x = not (isinstance(field.data_type, ir.Struct)
                                           and not field.data_type.all_fields)
                            ref_x = 'ref x' if needs_x else '_'
                            with self.block(u'{}::{}({}) =>'.format(
                                    type_name, variant_name, ref_x)):
                                if self.is_enum_type(ultimate_type):
                                    # Inner type is a union or polymorphic struct; need to always
                                    # emit another nesting level.
                                    self.emit(u'// union or polymporphic struct')
                                    self.emit(u'let mut s = serializer.serialize_struct("{}", 2)?;'
                                              .format(union.name))
                                    self.emit(u's.serialize_field(".tag", "{}")?;'
                                              .format(field.name))
                                    self.emit(u's.serialize_field("{}", x)?;'.format(field.name))
                                    self.emit(u's.end()')
                                elif isinstance(ir.unwrap_aliases(field.data_type)[0], ir.Nullable):
                                    self.emit(u'// nullable (struct or primitive)')
                                    # If it's nullable and the value is None, just emit the tag and
                                    # nothing else, otherwise emit the fields directly at the same
                                    # level.
                                    num_fields = 1 if ir.is_primitive_type(ultimate_type) \
                                        else len(ultimate_type.all_fields) + 1
                                    self.emit(u'let n = if x.is_some() {{ {} }} else {{ 1 }};'
                                              .format(num_fields + 1))
                                    self.emit(u'let mut s = serializer.serialize_struct("{}", n)?;'
                                              .format(union.name))
                                    self.emit(u's.serialize_field(".tag", "{}")?;'
                                              .format(field.name))
                                    with self.block(u'if let Some(ref x) = x'):
                                        if ir.is_primitive_type(ultimate_type):
                                            self.emit(u's.serialize_field("{}", &x)?;'
                                                      .format(field.name))
                                        else:
                                            self.emit(u'x.internal_serialize::<S>(&mut s)?;')
                                    self.emit(u's.end()')
                                elif isinstance(ultimate_type, ir.Struct):
                                    self.emit(u'// struct')
                                    self.emit(u'let mut s = serializer.serialize_struct("{}", {})?;'
                                              .format(union.name,
                                                      len(ultimate_type.all_fields) + 1))
                                    self.emit(u's.serialize_field(".tag", "{}")?;'
                                              .format(field.name))
                                    if ultimate_type.all_fields:
                                        self.emit(u'x.internal_serialize::<S>(&mut s)?;')
                                    self.emit(u's.end()')
                                else:
                                    self.emit(u'// primitive')
                                    self.emit(u'let mut s = serializer.serialize_struct("{}", 2)?;'
                                              .format(union.name))
                                    self.emit(u's.serialize_field(".tag", "{}")?;'
                                              .format(field.name))
                                    self.emit(u's.serialize_field("{}", x)?;'.format(field.name))
                                    self.emit(u's.end()')
                    if not union.closed:
                        self.emit(u'{}::Other => Err(::serde::ser::Error::custom('
                                  u'"cannot serialize \'Other\' variant"))'
                                  .format(type_name))
        self.emit()

    # Helpers

    def _emit_doc(self, doc_string, prefix=u'///', qualify_type_names=False):
        if doc_string is not None:
            for idx, chunk in enumerate(doc_string.split(u'\n\n')):
                if idx != 0: self.emit(prefix)
                docf = lambda tag, val: self._docf(qualify_type_names, tag, val)
                self.emit_wrapped_text(
                        self.process_doc(chunk, docf),
                        prefix=prefix + u' ', width=100)

    def _docf(self, qualify_type_names, tag, val):
        # qualify_type_names is meant to be used when generating documentation at the file scope
        # using "//!". Because these are semantically outside the module, types referenced in them
        # need to be qualified with the name of the module. Normally this gets done automatically
        # using self._current_namespace, but for file comments this value is wrong.
        if tag == 'route':
            if ':' in val:
                val, version = val.split(':')
                version = int(version)
            else:
                version = 1
            if '.' in val:
                ns, route = val.split('.')
                rust_fn = self.route_name_raw(route, version)
                label = ns + '::' + rust_fn
                target = 'super::' + label
            else:
                target = self.route_name_raw(val, version)
                label = target
            return '[`{}()`]({})'.format(label, target)
        elif tag == 'field':
            if '.' in val:
                cls_name, field = val.rsplit('.', 1)
                assert('.' not in cls_name)  # dunno if this is even allowed, but we don't handle it
                typ = self._all_types[self._current_namespace][cls_name]
                type_name = self._rust_type(typ)
                if self.is_enum_type(typ):
                    if isinstance(typ, ir.Struct) and typ.has_enumerated_subtypes() \
                            and field in (field.name for field in typ.fields):
                        # This is actually a link to a field in a polymorphic struct, not a enum
                        # variant. Because Rust doesn't have polymorphism, we make the fields be
                        # present on all enum variants, so this is a link to a field in the current
                        # type. Rustdoc doesn't let you link to a field, just the type, but we're
                        # already at that page, so don't bother with emitting an actual link.
                        # Hopefully we're documenting one of the variants right now, or else this
                        # is going to look weird.
                        field = self.field_name_raw(field)
                        return '`{}`'.format(field)
                    field = self.enum_variant_name_raw(field)
                    return '[`{}::{}`]({}::{})'.format(type_name, field, type_name, field)
                else:
                    field = self.field_name_raw(field)
                    # we can't link to the field itself, so just link to the struct
                    return '[`{}::{}`]({})'.format(type_name, field, type_name)
            else:
                # link is relative to the current type
                type_name = self._rust_type(self._current_type)
                if self.is_enum_type(self._current_type):
                    variant_name = self.enum_variant_name_raw(val)
                    return '[`{}`]({}::{})'.format(
                        variant_name, type_name, variant_name)
                else:
                    field_name = self.field_name_raw(val)
                    # we could, but don't bother linking to the struct because we're already there.
                    # return '[`{}`]({})'.format(field_name, current_rust_type)
                    return '`{}`'.format(field_name)
        elif tag == 'type':
            if '.' in val:
                ns, typ_name = val.split('.')
                typ = self._all_types[ns][typ_name]
                rust_name = self._rust_type(typ, no_qualify=True)
                full_rust_name = self._rust_type(typ)
                return '[`{}::{}`]({})'.format(
                    ns, rust_name, full_rust_name)
            else:
                typ = self._all_types[self._current_namespace][val]
                rust_name = self._rust_type(typ)
                if qualify_type_names:
                    ns = self.namespace_name_raw(self._current_namespace)
                    return '[`{}`]({}::{})'.format(rust_name, ns, rust_name)
                else:
                    return '[`{}`]({})'.format(rust_name, rust_name)
        elif tag == 'link':
            title, url = val.rsplit(' ', 1)
            return '[{}]({})'.format(title, url)
        elif tag == 'val':
            if val == 'null':
                return '`None`'
            else:
                return '`{}`'.format(val)
        else:
            print("WARNING: unrecognized link tag '{}'".format(tag))
            return '`{}`'.format(val)

    @contextmanager
    def _impl_deserialize(self, type_name):
        with self.block(u'impl<\'de> ::serde::de::Deserialize<\'de> for {}'.format(type_name)), \
                self.emit_rust_function_def(
                    u'deserialize<D: ::serde::de::Deserializer<\'de>>',
                    [u'deserializer: D'],
                    u'Result<Self, D::Error>'):
            yield

    @contextmanager
    def _impl_serialize(self, type_name):
        with self.block(u'impl ::serde::ser::Serialize for {}'.format(type_name)), \
                self.emit_rust_function_def(
                    u'serialize<S: ::serde::ser::Serializer>',
                    [u'&self', u'serializer: S'],
                    u'Result<S::Ok, S::Error>'):
            yield

    def _impl_default_for_struct(self, struct):
        struct_name = self.struct_name(struct)
        with self.block(u'impl Default for {}'.format(struct_name)):
            with self.emit_rust_function_def(u'default', [], u'Self'):
                with self.block(struct_name):
                    for field in struct.all_fields:
                        self.emit(u'{}: {},'.format(
                            self.field_name(field), self._default_value(field)))

    def _impl_struct(self, struct):
        return self.block(u'impl {}'.format(self.struct_name(struct)))

    def _emit_new_for_struct(self, struct):
        struct_name = self.struct_name(struct)
        with self.emit_rust_function_def(
                u'new',
                [u'{}: {}'.format(self.field_name(field), self._rust_type(field.data_type))
                    for field in struct.all_required_fields],
                u'Self',
                access=u'pub'):
            with self.block(struct_name):
                for field in struct.all_required_fields:
                    # shorthand assignment
                    self.emit(u'{},'.format(self.field_name(field)))
                for field in struct.all_optional_fields:
                    self.emit(u'{}: {},'.format(
                        self.field_name(field),
                        self._default_value(field)))

        for field in struct.all_optional_fields:
            self.emit()
            field_name = self.field_name(field)
            with self.emit_rust_function_def(
                    u'with_{}'.format(field_name),
                    [u'mut self', u'value: {}'.format(self._rust_type(field.data_type))],
                    u'Self',
                    access=u'pub'):
                self.emit(u'self.{} = value;'.format(field_name))
                self.emit(u'self')

    def _default_value(self, field):
        if isinstance(field.data_type, ir.Nullable):
            return u'None'
        elif ir.is_numeric_type(ir.unwrap_aliases(field.data_type)[0]):
            return field.default
        elif isinstance(field.default, ir.TagRef):
            default_variant = None
            for variant in field.default.union_data_type.all_fields:
                if variant.name == field.default.tag_name:
                    default_variant = variant
            if default_variant is None:
                raise RuntimeError('ERROR: didn\'t find matching variant of {}: {}'
                                   .format(field.data_type.name, field.default.tag_name))
            return u'{}::{}'.format(
                self._rust_type(field.default.union_data_type),
                self.enum_variant_name(default_variant))
        elif isinstance(field.data_type, ir.Boolean):
            if field.default:
                return u'true'
            else:
                return u'false'
        elif isinstance(field.data_type, ir.String):
            if not field.default:
                return u'String::new()'
            else:
                return u'"{}".to_owned()'.format(field.default)
        else:
            print(u'WARNING: unhandled default value {}'.format(field.default))
            print(u'    in field: {}'.format(field))
            if isinstance(field.data_type, ir.Alias):
                print(u'    unwrapped alias: {}'.format(ir.unwrap_aliases(field.data_type)[0]))
            return field.default

    def _needs_explicit_default(self, field):
        return field.has_default \
                and not (isinstance(field, ir.Nullable)
                         or (isinstance(field.data_type, ir.Boolean) and not field.default))

    def _impl_error(self, type_name):
        with self.block(u'impl ::std::error::Error for {}'.format(type_name)):
            with self.emit_rust_function_def(u'description', [u'&self'], u'&str'):
                self.emit(u'"{}"'.format(type_name))
        self.emit()
        with self.block(u'impl ::std::fmt::Display for {}'.format(type_name)):
            with self.emit_rust_function_def(
                    u'fmt',
                    [u'&self', u'f: &mut ::std::fmt::Formatter<\'_>'],
                    u'::std::fmt::Result'):
                self.emit(u'write!(f, "{:?}", *self)')
        self.emit()

    # Naming Rules

    def _rust_type(self, typ, no_qualify=False):
        if isinstance(typ, ir.Nullable):
            return u'Option<{}>'.format(self._rust_type(typ.data_type, no_qualify))
        elif isinstance(typ, ir.Void):
            return u'()'
        elif isinstance(typ, ir.Bytes):
            return u'Vec<u8>'
        elif isinstance(typ, ir.Int32):
            return u'i32'
        elif isinstance(typ, ir.UInt32):
            return u'u32'
        elif isinstance(typ, ir.Int64):
            return u'i64'
        elif isinstance(typ, ir.UInt64):
            return u'u64'
        elif isinstance(typ, ir.Float32):
            return u'f32'
        elif isinstance(typ, ir.Float64):
            return u'f64'
        elif isinstance(typ, ir.Boolean):
            return u'bool'
        elif isinstance(typ, ir.String):
            return u'String'
        elif isinstance(typ, ir.Timestamp):
            return u'String /*Timestamp*/'  # TODO
        elif isinstance(typ, ir.List):
            return u'Vec<{}>'.format(self._rust_type(typ.data_type, no_qualify))
        elif isinstance(typ, ir.Map):
            return u'::std::collections::HashMap<{}, {}>'.format(
                self._rust_type(typ.key_data_type, no_qualify),
                self._rust_type(typ.value_data_type, no_qualify))
        elif isinstance(typ, ir.Alias):
            if typ.namespace.name == self._current_namespace or no_qualify:
                return self.alias_name(typ)
            else:
                return u'super::{}::{}'.format(
                    self.namespace_name(typ.namespace),
                    self.alias_name(typ))
        elif isinstance(typ, ir.UserDefined):
            if isinstance(typ, ir.Struct):
                name = self.struct_name(typ)
            elif isinstance(typ, ir.Union):
                name = self.enum_name(typ)
            else:
                raise RuntimeError(u'ERROR: user-defined type "{}" is neither Struct nor Union???'
                                   .format(typ))
            if typ.namespace.name == self._current_namespace or no_qualify:
                return name
            else:
                return u'super::{}::{}'.format(
                    self.namespace_name(typ.namespace),
                    name)
        else:
            raise RuntimeError(u'ERROR: unhandled type "{}"'.format(typ))
