# Purpose: tables section
# Created: 12.03.2011
# Copyright (c) 2011-2018, Manfred Moitzi
# License: MIT License
from typing import TYPE_CHECKING, Iterable, Sequence, Iterator
from ezdxf.lldxf.tags import DXFTag
from ezdxf.lldxf.extendedtags import ExtendedTags
from ezdxf.lldxf.const import DXFAttributeError, DXFStructureError

from .table import Table, ViewportTable, StyleTable

if TYPE_CHECKING:
    from ezdxf.eztypes import Drawing, TagWriter, Tags


class TablesSection:
    name = 'TABLES'

    def __init__(self, entities: Iterable[Sequence[DXFTag]], drawing: 'Drawing'):
        self._drawing = drawing
        self._tables = {}
        if entities is None:
            section_head = [DXFTag(0, 'SECTION'), DXFTag(2, 'TABLES')]
            entities = [section_head]
        self._setup_tables(iter(entities))

    def __iter__(self) -> Iterable[Table]:
        return iter(self._tables.values())

    @staticmethod
    def key(name: str) -> str:
        return name.upper()

    def _setup_tables(self, entities: Iterator[Sequence[DXFTag]]) -> None:
        section_head = next(entities)
        if section_head[0] != (0, 'SECTION') or section_head[1] != (2, 'TABLES'):
            raise DXFStructureError("Critical structure error in TABLES section.")

        table_entities = []
        table_name = None
        for entity in entities:
            if isinstance(entity, ExtendedTags):
                entity = entity.noclass
            if entity[0] == (0, 'TABLE'):
                table_entities = [entity]  # collect table head!
                if len(entity) < 2 or entity[1].code != 2:
                    raise DXFStructureError(
                        'DXFStructureError: missing required table name tag (2, name) at start of table.')
                table_name = entity[1].value
            elif entity[0] == (0, 'ENDTAB'):  # do not collect (0, 'ENDTAB')
                self._new_table(table_name, table_entities)
                table_entities = []  # collect entities outside of tables, but ignore it
                table_name = None
            else:  # collect table entries
                table_entities.append(entity)

        self._create_missing_tables()

    def _new_table(self, name: str, table_entities: Iterable['Tags']) -> None:
        table_class = TABLESMAP[name]
        new_table = table_class(table_entities, self._drawing)
        self._tables[self.key(new_table.name)] = new_table

    def _setup_table(self, name):
        """
        Setup new empty table.

        Args:
            name: real table name like 'VPORT' for viewports

        """
        name = self.key(name)
        if self._drawing is not None:
            dxfversion = self._drawing.dxfversion
            handle = self._drawing.entitydb.get_unique_handle()
        else:  # test environment without Drawing() object
            dxfversion = 'AC1009'  # DXF R12
            handle = '0'

        if dxfversion <= 'AC1009':
            table_entities = [[
                DXFTag(0, 'TABLE'),
                DXFTag(2, name),
                DXFTag(70, 0)
            ]]
        else:
            table_entities = [[
                DXFTag(0, 'TABLE'),
                DXFTag(2, name),
                DXFTag(5, handle),
                DXFTag(330, '0'),
                DXFTag(100, 'AcDbSymbolTable'),
                DXFTag(70, 0)
            ]]
        self._new_table(name, table_entities)

    def _create_missing_tables(self) -> None:
        if 'LAYERS' not in self:
            self._setup_table('LAYER')
        if 'LINETYPES' not in self:
            self._setup_table('LTYPE')
        if 'STYLES' not in self:
            self._setup_table('STYLE')
        if 'DIMSTYLES' not in self:
            self._setup_table('DIMSTYLE')
        if 'VIEWPORTS' not in self:
            self._setup_table('VPORT')
        if 'APPIDS' not in self:
            self._setup_table('APPID')
        if 'UCS' not in self:
            self._setup_table('UCS')

    def __contains__(self, item: str) -> bool:
        return self.key(item) in self._tables

    def __getattr__(self, key: str) -> Table:
        key = self.key(key)
        try:
            return self._tables[key]
        except KeyError:  # internal exception
            raise DXFAttributeError(key)

    def __getitem__(self, key: str) -> Table:
        return self._tables[self.key(key)]

    def __delitem__(self, key: str) -> None:
        del self._tables[self.key(key)]

    def write(self, tagwriter: 'TagWriter') -> None:
        tagwriter.write_str('  0\nSECTION\n  2\nTABLES\n')
        for table_name in TABLE_ORDER:
            table = self._tables.get(table_name)
            if table is not None:
                table.write(tagwriter)
        tagwriter.write_tag2(0, 'ENDSEC')


TABLESMAP = {
    'LAYER': Table,
    'LTYPE': Table,
    'STYLE': StyleTable,
    'DIMSTYLE': Table,
    'VPORT': ViewportTable,
    'VIEW': Table,
    'UCS': Table,
    'APPID': Table,
    'BLOCK_RECORD': Table,
}

# The order of the tables may change, but the LTYPE table always precedes the LAYER table.
TABLE_ORDER = ('VIEWPORTS', 'LINETYPES', 'LAYERS', 'STYLES', 'VIEWS', 'UCS', 'APPIDS', 'DIMSTYLES', 'BLOCK_RECORDS')
