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, 'typing.NewType', 'NewType', False, 'from typing import List, Optional, NewType, Tuple, NamedTuple, Type\n'), (9, 'typing.NamedTuple', 'NamedTuple', False, 'from typing import List, Optional, NewType, Tuple, NamedTuple, Type\n'), (16, 'attr.ib', 'attr.ib', False, 'import attr\n'), (17, 'attr.ib', 'attr.ib', False, 'import attr\n'), (18, 'attr.ib', 'attr.ib', False, 'import attr\n'), (19, 'attr.ib', 'attr.ib', False, 'import attr\n'), (20, 'attr.ib', 'attr.ib', False, 'import attr\n'), (55, 'attr.ib', 'attr.ib', False, 'import attr\n'), (56, 'attr.ib', 'attr.ib', False, 'import attr\n'), (61, 'attr.ib', 'attr.ib', False, 'import attr\n'), (62, 'attr.ib', 'attr.ib', False, 'import attr\n'), (67, 'attr.ib', 'attr.ib', False, 'import attr\n'), (72, 'attr.ib', 'attr.ib', False, 'import attr\n'), (73, 'attr.ib', 'attr.ib', False, 'import attr\n'), (74, 'attr.ib', 'attr.ib', False, 'import attr\n'), (79, 'attr.ib', 'attr.ib', False, 'import attr\n'), (84, 'attr.ib', 'attr.ib', False, 'import attr\n'), (89, 'attr.ib', 'attr.ib', False, 'import attr\n'), (90, 'attr.ib', 'attr.ib', False, 'import attr\n'), (91, 'attr.ib', 'attr.ib', False, 'import attr\n'), (96, 'attr.ib', 'attr.ib', False, 'import attr\n'), (101, 'attr.ib', 'attr.ib', False, 'import attr\n'), (102, 'attr.ib', 'attr.ib', False, 'import attr\n'), (103, 'attr.ib', 'attr.ib', False, 'import attr\n'), (104, 'attr.ib', 'attr.ib', False, 'import attr\n'), (105, 'attr.ib', 'attr.ib', False, 'import attr\n'), (110, 'attr.ib', 'attr.ib', False, 'import attr\n'), (111, 'attr.ib', 'attr.ib', False, 'import attr\n'), (112, 'attr.ib', 'attr.ib', False, 'import attr\n'), (113, 'attr.ib', 'attr.ib', False, 'import attr\n'), (118, 'attr.ib', 'attr.ib', False, 'import attr\n'), (119, 'attr.ib', 'attr.ib', False, 'import attr\n'), (130, 'attr.ib', 'attr.ib', False, 'import attr\n'), (135, 'attr.ib', 'attr.ib', False, 'import attr\n'), (140, 'attr.ib', 'attr.ib', False, 'import attr\n'), (145, 'attr.ib', 'attr.ib', False, 'import attr\n'), (146, 'attr.ib', 'attr.ib', False, 'import attr\n'), (151, 'attr.ib', 'attr.ib', False, 'import attr\n'), (152, 'attr.ib', 'attr.ib', False, 'import attr\n'), (153, 'attr.ib', 'attr.ib', False, 'import attr\n'), (23, 'genyrator.path.create_relative_path', 'create_relative_path', False, 'from genyrator.path import create_relative_path\n')]