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()

[((247, 288), 'typing.NewType', 'NewType', (['"""OutPath"""', 'Tuple[List[str], str]'], {}), "('OutPath', Tuple[List[str], str])\n", (254, 288), False, 'from typing import List, Optional, NewType, Tuple, NamedTuple, Type\n'), ((298, 366), 'typing.NamedTuple', 'NamedTuple', (['"""Import"""', "[('module_name', str), ('imports', List[str])]"], {}), "('Import', [('module_name', str), ('imports', List[str])])\n", (308, 366), False, 'from typing import List, Optional, NewType, Tuple, NamedTuple, Type\n'), ((492, 501), 'attr.ib', 'attr.ib', ([], {}), '()\n', (499, 501), False, 'import attr\n'), ((546, 555), 'attr.ib', 'attr.ib', ([], {}), '()\n', (553, 555), False, 'import attr\n'), ((600, 609), 'attr.ib', 'attr.ib', ([], {}), '()\n', (607, 609), False, 'import attr\n'), ((654, 663), 'attr.ib', 'attr.ib', ([], {}), '()\n', (661, 663), False, 'import attr\n'), ((708, 717), 'attr.ib', 'attr.ib', ([], {}), '()\n', (715, 717), False, 'import attr\n'), ((1683, 1692), 'attr.ib', 'attr.ib', ([], {}), '()\n', (1690, 1692), False, 'import attr\n'), ((1719, 1728), 'attr.ib', 'attr.ib', ([], {}), '()\n', (1726, 1728), False, 'import attr\n'), ((1799, 1808), 'attr.ib', 'attr.ib', ([], {}), '()\n', (1806, 1808), False, 'import attr\n'), ((1841, 1850), 'attr.ib', 'attr.ib', ([], {}), '()\n', (1848, 1850), False, 'import attr\n'), ((1913, 1922), 'attr.ib', 'attr.ib', ([], {}), '()\n', (1920, 1922), False, 'import attr\n'), ((1992, 2001), 'attr.ib', 'attr.ib', ([], {}), '()\n', (1999, 2001), False, 'import attr\n'), ((2028, 2037), 'attr.ib', 'attr.ib', ([], {}), '()\n', (2035, 2037), False, 'import attr\n'), ((2064, 2073), 'attr.ib', 'attr.ib', ([], {}), '()\n', (2071, 2073), False, 'import attr\n'), ((2136, 2145), 'attr.ib', 'attr.ib', ([], {}), '()\n', (2143, 2145), False, 'import attr\n'), ((2203, 2212), 'attr.ib', 'attr.ib', ([], {}), '()\n', (2210, 2212), False, 'import attr\n'), ((2295, 2304), 'attr.ib', 'attr.ib', ([], {}), '()\n', (2302, 2304), False, 'import attr\n'), ((2340, 2349), 'attr.ib', 'attr.ib', ([], {}), '()\n', (2347, 2349), False, 'import attr\n'), ((2385, 2394), 'attr.ib', 'attr.ib', ([], {}), '()\n', (2392, 2394), False, 'import attr\n'), ((2457, 2466), 'attr.ib', 'attr.ib', ([], {}), '()\n', (2464, 2466), False, 'import attr\n'), ((2535, 2544), 'attr.ib', 'attr.ib', ([], {}), '()\n', (2542, 2544), False, 'import attr\n'), ((2577, 2586), 'attr.ib', 'attr.ib', ([], {}), '()\n', (2584, 2586), False, 'import attr\n'), ((2619, 2628), 'attr.ib', 'attr.ib', ([], {}), '()\n', (2626, 2628), False, 'import attr\n'), ((2661, 2670), 'attr.ib', 'attr.ib', ([], {}), '()\n', (2668, 2670), False, 'import attr\n'), ((2703, 2712), 'attr.ib', 'attr.ib', ([], {}), '()\n', (2710, 2712), False, 'import attr\n'), ((2790, 2799), 'attr.ib', 'attr.ib', ([], {}), '()\n', (2797, 2799), False, 'import attr\n'), ((2836, 2845), 'attr.ib', 'attr.ib', ([], {}), '()\n', (2843, 2845), False, 'import attr\n'), ((2882, 2891), 'attr.ib', 'attr.ib', ([], {}), '()\n', (2889, 2891), False, 'import attr\n'), ((2928, 2937), 'attr.ib', 'attr.ib', ([], {}), '()\n', (2935, 2937), False, 'import attr\n'), ((3003, 3012), 'attr.ib', 'attr.ib', ([], {}), '()\n', (3010, 3012), False, 'import attr\n'), ((3039, 3048), 'attr.ib', 'attr.ib', ([], {}), '()\n', (3046, 3048), False, 'import attr\n'), ((3286, 3295), 'attr.ib', 'attr.ib', ([], {}), '()\n', (3293, 3295), False, 'import attr\n'), ((3360, 3369), 'attr.ib', 'attr.ib', ([], {}), '()\n', (3367, 3369), False, 'import attr\n'), ((3433, 3442), 'attr.ib', 'attr.ib', ([], {}), '()\n', (3440, 3442), False, 'import attr\n'), ((3521, 3530), 'attr.ib', 'attr.ib', ([], {}), '()\n', (3528, 3530), False, 'import attr\n'), ((3557, 3566), 'attr.ib', 'attr.ib', ([], {}), '()\n', (3564, 3566), False, 'import attr\n'), ((3628, 3637), 'attr.ib', 'attr.ib', ([], {}), '()\n', (3635, 3637), False, 'import attr\n'), ((3664, 3673), 'attr.ib', 'attr.ib', ([], {}), '()\n', (3671, 3673), False, 'import attr\n'), ((3700, 3709), 'attr.ib', 'attr.ib', ([], {}), '()\n', (3707, 3709), False, 'import attr\n'), ((765, 838), 'genyrator.path.create_relative_path', 'create_relative_path', (['[*self.template_file_path, self.template_file_name]'], {}), '([*self.template_file_path, self.template_file_name])\n', (785, 838), False, 'from genyrator.path import create_relative_path\n')]