# cython: auto_cpdef=True

from os import path

import json

from ._schema_common import (
    PRIMITIVES, NAMED_TYPES, SCHEMA_DEFS, UnknownType, SchemaParseException
)


cpdef inline extract_record_type(schema):
    if isinstance(schema, dict):
        return schema['type']

    if isinstance(schema, list):
        return 'union'

    return schema


cpdef inline str extract_logical_type(schema):
    if not isinstance(schema, dict):
        return None
    rt = schema['type']
    lt = schema.get('logicalType')
    if lt:
        # TODO: Building this string every time is going to be relatively slow.
        return '{}-{}'.format(rt, lt)
    return None


def schema_name(schema, parent_ns):
    try:
        name = schema['name']
    except KeyError:
        msg = (
            '"name" is a required field missing from ' +
            'the schema: {}'.format(schema)
        )
        raise SchemaParseException(msg)

    namespace = schema.get('namespace', parent_ns)
    if not namespace:
        return namespace, name

    return namespace, '%s.%s' % (namespace, name)


def extract_named_schemas_into_repo(schema, repo, transformer, parent_ns=None):
    if isinstance(schema, list):
        for index, enum_schema in enumerate(schema):
            namespaced_name = extract_named_schemas_into_repo(
                enum_schema,
                repo,
                transformer,
                parent_ns,
            )
            if namespaced_name:
                schema[index] = namespaced_name
        return

    if not isinstance(schema, dict):
        # If a reference to another schema is an unqualified name, but not one
        # of the primitive types, then we should add the current enclosing
        # namespace to reference name.
        if schema not in PRIMITIVES:
            if '.' not in schema and parent_ns:
                schema = parent_ns + '.' + schema

            if schema not in repo:
                raise UnknownType(schema)
            return schema
        return

    schema_type = schema.get('type')
    if schema_type == 'array':
        namespaced_name = extract_named_schemas_into_repo(
            schema['items'],
            repo,
            transformer,
            parent_ns,
        )
        if namespaced_name:
            schema['items'] = namespaced_name
        return
    elif schema_type == 'map':
        namespaced_name = extract_named_schemas_into_repo(
            schema['values'],
            repo,
            transformer,
            parent_ns,
        )
        if namespaced_name:
            schema['values'] = namespaced_name
        return
    else:
        # dict-y type schema
        if schema_type in NAMED_TYPES:
            namespace, name = schema_name(schema, parent_ns)

            repo[name] = transformer(schema)

        for field in schema.get('fields', []):
            namespaced_name = extract_named_schemas_into_repo(
                field['type'],
                repo,
                transformer,
                namespace,
            )
            if namespaced_name:
                field['type'] = namespaced_name


def load_schema(schema_path):
    '''
    Returns a schema loaded from the file at `schema_path`.

    Will recursively load referenced schemas assuming they can be found in
    files in the same directory and named with the convention
    `<type_name>.avsc`.
    '''
    with open(schema_path) as fd:
        schema = json.load(fd)
    schema_dir, schema_file = path.split(schema_path)
    return _load_schema(schema, schema_dir)


def _load_schema(schema, schema_dir):
    try:
        from fastavro import acquaint_schema
        acquaint_schema(schema)
    except UnknownType as e:
        try:
            avsc = path.join(schema_dir, '%s.avsc' % e.name)
            sub_schema = load_schema(avsc)
        except IOError:
            raise e

        if isinstance(schema, dict):
            return _load_schema([sub_schema, schema], schema_dir)
        else:
            # schema is already a list
            schema.insert(sub_schema, 0)
            return _load_schema(schema, schema_dir)
    return schema


def populate_schema_defs(schema):
    extract_named_schemas_into_repo(
        schema,
        SCHEMA_DEFS,
        lambda schema: schema,
    )
