from ..language.printer import print_ast
from ..type.definition import (GraphQLEnumType, GraphQLInputObjectType,
                               GraphQLInterfaceType, GraphQLObjectType,
                               GraphQLScalarType, GraphQLUnionType)
from .ast_from_value import ast_from_value


def print_schema(schema):
    return _print_filtered_schema(schema, _is_defined_type)


def print_introspection_schema(schema):
    return _print_filtered_schema(schema, _is_introspection_type)


def _is_defined_type(typename):
    return not _is_introspection_type(typename) and not _is_builtin_scalar(typename)


def _is_introspection_type(typename):
    return typename.startswith('__')


_builtin_scalars = frozenset(['String', 'Boolean', 'Int', 'Float', 'ID'])


def _is_builtin_scalar(typename):
    return typename in _builtin_scalars


def _print_filtered_schema(schema, type_filter):
    return '\n\n'.join(
        _print_type(type)
        for typename, type in sorted(schema.get_type_map().items())
        if type_filter(typename)
    ) + '\n'


def _print_type(type):
    if isinstance(type, GraphQLScalarType):
        return _print_scalar(type)

    elif isinstance(type, GraphQLObjectType):
        return _print_object(type)

    elif isinstance(type, GraphQLInterfaceType):
        return _print_interface(type)

    elif isinstance(type, GraphQLUnionType):
        return _print_union(type)

    elif isinstance(type, GraphQLEnumType):
        return _print_enum(type)

    assert isinstance(type, GraphQLInputObjectType)
    return _print_input_object(type)


def _print_scalar(type):
    return 'scalar {}'.format(type.name)


def _print_object(type):
    interfaces = type.get_interfaces()
    implemented_interfaces = \
        ' implements {}'.format(', '.join(i.name for i in interfaces)) if interfaces else ''

    return (
        'type {}{} {{\n'
        '{}\n'
        '}}'
    ).format(type.name, implemented_interfaces, _print_fields(type))


def _print_interface(type):
    return (
        'interface {} {{\n'
        '{}\n'
        '}}'
    ).format(type.name, _print_fields(type))


def _print_union(type):
    return 'union {} = {}'.format(type.name, ' | '.join(str(t) for t in type.get_possible_types()))


def _print_enum(type):
    return (
        'enum {} {{\n'
        '{}\n'
        '}}'
    ).format(type.name, '\n'.join('  ' + v.name for v in type.get_values()))


def _print_input_object(type):
    return (
        'input {} {{\n'
        '{}\n'
        '}}'
    ).format(type.name, '\n'.join('  ' + _print_input_value(field) for field in type.get_fields().values()))


def _print_fields(type):
    return '\n'.join('  {}{}: {}'.format(f.name, _print_args(f), f.type) for f in type.get_fields().values())


def _print_args(field):
    if not field.args:
        return ''

    return '({})'.format(', '.join(_print_input_value(arg) for arg in field.args))


def _print_input_value(arg):
    if arg.default_value is not None:
        default_value = ' = ' + print_ast(ast_from_value(arg.default_value, arg.type))
    else:
        default_value = ''

    return '{}: {}{}'.format(arg.name, arg.type, default_value)


__all__ = ['print_schema', 'print_introspection_schema']
