from typing import List, Optional, NewType, Tuple, NamedTuple, Type
import attr
from jinja2 import Template as JinjaTemplate, StrictUndefined

from genyrator.entities.Entity import Entity
from genyrator.path import create_relative_path

OutPath = NewType('OutPath', Tuple[List[str], str])
Import = NamedTuple('Import',
                    [('module_name', str),
                     ('imports',     List[str]), ])


@attr.s
class Template(object):
    template_name:      str =               attr.ib()
    template_file_name: str =               attr.ib()
    template_file_path: List[str] =         attr.ib()
    relative_path:      List[str] =         attr.ib()
    out_path:           Optional[OutPath] = attr.ib()

    def create_template(self):
        path = create_relative_path(
            [*self.template_file_path, self.template_file_name]
        )
        with open(path) as f:
            template = JinjaTemplate(f.read(), undefined=StrictUndefined)
        return template

    def render(self):
        return self.create_template().render(template=self)


def create_template(
        constructor,
        template_path: Optional[List[str]] = None,
        out_path:      Optional[OutPath] = None,
        **kwargs,
) -> Template:
    relative_path = template_path[0:-1]
    path = ['genyrator', 'templates'] + relative_path
    template_name = template_path[-1]
    return constructor(
        template_name=template_name,
        template_file_name='{}.j2'.format(template_name),
        template_file_path=path,
        out_path=out_path,
        relative_path=relative_path,
        **kwargs,
    )


@attr.s
class RootInit(Template):
    db_import_path: str = attr.ib()
    module_name:    str = attr.ib()


@attr.s
class RootSchema(Template):
    module_name: str =          attr.ib()
    entities:    List[Entity] = attr.ib()


@attr.s
class ConvertDict(Template):
    module_name: str = attr.ib()


@attr.s
class SQLAlchemyModel(Template):
    module_name: str =    attr.ib()
    db_import_path: str = attr.ib()
    entity: Entity =      attr.ib()


@attr.s
class ModelToDict(Template):
    module_name: str = attr.ib()


@attr.s
class Config(Template):
    module_name: str = attr.ib()


@attr.s
class SQLAlchemyModelInit(Template):
    module_name:    str =          attr.ib()
    db_import_path: str =          attr.ib()
    imports:        List[Import] = attr.ib()


@attr.s
class RestplusModel(Template):
    entity: Entity = attr.ib()


@attr.s
class Resource(Template):
    module_name:       str =    attr.ib()
    db_import_path:    str =    attr.ib()
    entity:            Entity = attr.ib()
    restplus_template: str =    attr.ib()
    TypeOption:        Type =   attr.ib()


@attr.s
class ResourcesInit(Template):
    entities:        List[Entity] = attr.ib()
    module_name:     str =          attr.ib()
    api_name:        str =          attr.ib()
    api_description: str =          attr.ib()


@attr.s
class DomainModel(Template):
    entity:      Entity = attr.ib()
    module_name: str =    attr.ib()

    def sqlalchemy_model_imports(self):
        return list(set([
            rel.target_entity_class_name
            for rel in self.entity.relationships
        ]))


@attr.s
class ConvertProperties(Template):
    module_name: str = attr.ib()


@attr.s
class ConvertModels(Template):
    module_name: str = attr.ib()


@attr.s
class JoinEntities(Template):
    module_name: str = attr.ib()


@attr.s
class ConvertDictToMarshmallow(Template):
    module_name:    str = attr.ib()
    db_import_path: str = attr.ib()


@attr.s
class Fixture(Template):
    db_import_path: str = attr.ib()
    module_name: str =    attr.ib()
    entity: Entity =      attr.ib()

[((8, 10, 8, 51), 'typing.NewType', 'NewType', ({(8, 18, 8, 27): '"""OutPath"""', (8, 29, 8, 50): 'Tuple[List[str], str]'}, {}), "('OutPath', Tuple[List[str], str])", False, 'from typing import List, Optional, NewType, Tuple, NamedTuple, Type\n'), ((9, 9, 11, 51), 'typing.NamedTuple', 'NamedTuple', ({(9, 20, 9, 28): '"""Import"""', (10, 20, 11, 50): "[('module_name', str), ('imports', List[str])]"}, {}), "('Import', [('module_name', str), ('imports', List[str])])", False, 'from typing import List, Optional, NewType, Tuple, NamedTuple, Type\n'), ((16, 44, 16, 53), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((17, 44, 17, 53), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((18, 44, 18, 53), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((19, 44, 19, 53), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((20, 44, 20, 53), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((55, 26, 55, 35), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((56, 26, 56, 35), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((61, 32, 61, 41), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((62, 32, 62, 41), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((67, 23, 67, 32), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((72, 26, 72, 35), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((73, 26, 73, 35), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((74, 26, 74, 35), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((79, 23, 79, 32), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((84, 23, 84, 32), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((89, 35, 89, 44), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((90, 35, 90, 44), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((91, 35, 91, 44), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((96, 21, 96, 30), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((101, 32, 101, 41), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((102, 32, 102, 41), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((103, 32, 103, 41), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((104, 32, 104, 41), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((105, 32, 105, 41), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((110, 36, 110, 45), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((111, 36, 111, 45), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((112, 36, 112, 45), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((113, 36, 113, 45), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((118, 26, 118, 35), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((119, 26, 119, 35), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((130, 23, 130, 32), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((135, 23, 135, 32), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((140, 23, 140, 32), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((145, 26, 145, 35), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((146, 26, 146, 35), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((151, 26, 151, 35), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((152, 26, 152, 35), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((153, 26, 153, 35), 'attr.ib', 'attr.ib', ({}, {}), '()', False, 'import attr\n'), ((23, 15, 25, 9), 'genyrator.path.create_relative_path', 'create_relative_path', ({(24, 12, 24, 63): '[*self.template_file_path, self.template_file_name]'}, {}), '([*self.template_file_path, self.template_file_name])', False, 'from genyrator.path import create_relative_path\n')]