# Created: 16.03.2011
# Copyright (c) 2011-2018, Manfred Moitzi
# License: MIT License
from typing import TYPE_CHECKING, Iterable, Union, cast, Optional
import logging
from ezdxf.lldxf.types import DXFTag
from ezdxf.lldxf.extendedtags import ExtendedTags
from ezdxf.lldxf.attributes import DXFAttr, DXFAttributes, DefSubclass, XType, VIRTUAL_TAG
from ezdxf.lldxf.const import DXFKeyError, LINEWEIGHT_BYBLOCK, DXFVersionError
from ezdxf.legacy import tableentries as legacy
from ezdxf.dxfentity import DXFEntity
from ezdxf.tools.complex_ltype import lin_compiler
from ezdxf.render.arrows import ARROWS

logger = logging.getLogger('ezdxf')

if TYPE_CHECKING:
    from ezdxf.eztypes import Drawing, ComplexLineTypePart

_LAYERTEMPLATE = """0
LAYER
5
LayerHandle
100
AcDbSymbolTableRecord
100
AcDbLayerTableRecord
2
LayerName
70
0
62
7
6
Continuous
290
1
390
0
"""

# code 390 is required for AutoCAD
# Pointer/handle to PlotStyleName
# uses tag(390, ...) from the '0' layer

none_subclass = DefSubclass(None, {
    'handle': DXFAttr(5),
    'owner': DXFAttr(330),
})

symbol_subclass = DefSubclass('AcDbSymbolTableRecord', {})

layer_subclass = DefSubclass('AcDbLayerTableRecord', {
    'name': DXFAttr(2),  # layer name
    'flags': DXFAttr(70),
    'color': DXFAttr(62),  # dxf color index
    'linetype': DXFAttr(6),  # linetype name
    'plot': DXFAttr(290),  # don't plot this layer if 0 else 1
    'line_weight': DXFAttr(370),  # enum value???
    'lineweight': DXFAttr(370),  # enum value???
    'plot_style_name': DXFAttr(390),  # handle to PlotStyleName object
    'material': DXFAttr(347),  # handle to Material object
})


class Layer(legacy.Layer):
    __slots__ = ()
    TEMPLATE = ExtendedTags.from_text(_LAYERTEMPLATE)
    DXFATTRIBS = DXFAttributes(none_subclass, symbol_subclass, layer_subclass)

    @classmethod
    def new(cls, handle: str, dxfattribs: dict = None, drawing: 'Drawing' = None) -> 'Layer':
        layer = super(Layer, cls).new(handle, dxfattribs, drawing)
        # just for testing scenarios where drawing is None
        if drawing is not None:
            layer.dxf.plot_style_name = drawing.rootdict['ACAD_PLOTSTYLENAME']
        return layer


_STYLETEMPLATE = """0
STYLE
5
0
100
AcDbSymbolTableRecord
100
AcDbTextStyleTableRecord
2
STYLENAME
70
0
40
0.0
41
1.0
50
0.0
71
0
42
0.2
3
arial.ttf
4

"""
style_subclass = DefSubclass('AcDbTextStyleTableRecord', {
    'name': DXFAttr(2),
    'flags': DXFAttr(70),
    'height': DXFAttr(40),  # fixed height, 0 if not fixed
    'width': DXFAttr(41),  # width factor
    'oblique': DXFAttr(50),  # oblique angle in degree, 0 = vertical
    'generation_flags': DXFAttr(71),  # 2 = backward, 4 = mirrored in Y
    'last_height': DXFAttr(42),  # last height used
    'font': DXFAttr(3),  # primary font file name
    'bigfont': DXFAttr(4),  # big font name, blank if none
})


class Style(legacy.Style):
    __slots__ = ()
    TEMPLATE = ExtendedTags.from_text(_STYLETEMPLATE)
    DXFATTRIBS = DXFAttributes(none_subclass, symbol_subclass, style_subclass)


_LTYPETEMPLATE = """0
LTYPE
5
0
100
AcDbSymbolTableRecord
100
AcDbLinetypeTableRecord
2
LTYPENAME
70
0
3
LTYPEDESCRIPTION
72
65
"""
linetype_subclass = DefSubclass('AcDbLinetypeTableRecord', {
    'name': DXFAttr(2),
    'description': DXFAttr(3),
    'length': DXFAttr(40),
    'items': DXFAttr(73),
})


class Linetype(legacy.Linetype):
    __slots__ = ()
    TEMPLATE = ExtendedTags.from_text(_LTYPETEMPLATE)
    DXFATTRIBS = DXFAttributes(none_subclass, symbol_subclass, linetype_subclass)

    def _setup_pattern(self, pattern: Union[Iterable[float], str], length: float) -> None:
        complex_line_type = True if isinstance(pattern, str) else False
        if complex_line_type:  # a .lin like line type definition string
            self._setup_complex_pattern(pattern, length)
        else:
            # pattern: [2.0, 1.25, -0.25, 0.25, -0.25] - 1. element is total pattern length
            # pattern elements: >0 line, <0 gap, =0 point
            subclass = self.tags.get_subclass('AcDbLinetypeTableRecord')
            subclass.append(DXFTag(73, len(pattern) - 1))
            subclass.append(DXFTag(40, float(pattern[0])))
            for element in pattern[1:]:
                subclass.append(DXFTag(49, float(element)))
                subclass.append(DXFTag(74, 0))

    def _setup_complex_pattern(self, pattern: str, length: float) -> None:
        tokens = lin_compiler(pattern)
        subclass = self.tags.get_subclass('AcDbLinetypeTableRecord')
        subclass.append(DXFTag(73, 0))  # temp length of 0
        subclass.append(DXFTag(40, length))
        count = 0
        for token in tokens:
            if isinstance(token, DXFTag):
                if subclass[-1].code == 49:  # useless 74 only after 49 :))
                    subclass.append(DXFTag(74, 0))
                subclass.append(token)
                count += 1
            else:  # TEXT or SHAPE
                tags = cast('ComplexLineTypePart', token).complex_ltype_tags(self.drawing)
                subclass.extend(tags)
        subclass.append(DXFTag(74, 0))  # useless 74 at the end :))
        subclass.update(DXFTag(73, count))


_APPIDTEMPLATE = """0
APPID
5
0
100
AcDbSymbolTableRecord
100
AcDbRegAppTableRecord
2
APPIDNAME
70
0
"""
appid_subclass = DefSubclass('AcDbRegAppTableRecord', {
    'name': DXFAttr(2),
    'flags': DXFAttr(70),
})


class AppID(legacy.AppID):
    __slots__ = ()
    TEMPLATE = ExtendedTags.from_text(_APPIDTEMPLATE)
    DXFATTRIBS = DXFAttributes(none_subclass, symbol_subclass, appid_subclass)


_DIMSTYLETEMPLATE = """0
DIMSTYLE
105
0
100
AcDbSymbolTableRecord
100
AcDbDimStyleTableRecord
2
STANDARD
70
0
3

4

40
1.0
41
3.0
42
2.0
43
9.0
44
5.0
46
0.0
47
0.0
48
0.0
140
3.0
141
2.0
142
0.0
143
25.399999999999999
144
1.0
145
0.0
146
1.0
147
2.0
71
0
72
0
73
1
74
1
75
0
76
0
77
0
78
0
170
0
171
2
172
0
173
0
174
0
175
0
176
0
177
0
178
0
"""
handle105_subclass = DefSubclass(None, {
    'handle': DXFAttr(105),
    'owner': DXFAttr(330),
})

dimstyle_subclass = DefSubclass('AcDbDimStyleTableRecord', {
    'name': DXFAttr(2),
    'flags': DXFAttr(70),
    'dimpost': DXFAttr(3),
    'dimapost': DXFAttr(4),
    # redirect dimblk/dimblk1/dimblk2 -> dimblk_handle/dimblk1_handle/dimblk2_handle
    'dimblk': DXFAttr(5, xtype=XType.callback, getter='get_dimblk', setter='set_dimblk'),
    'dimblk1': DXFAttr(6, xtype=XType.callback, getter='get_dimblk1', setter='set_dimblk1'),
    'dimblk2': DXFAttr(7, xtype=XType.callback, getter='get_dimblk2', setter='set_dimblk2'),
    'dimscale': DXFAttr(40),
    'dimasz': DXFAttr(41),
    'dimexo': DXFAttr(42),
    'dimdli': DXFAttr(43),
    'dimexe': DXFAttr(44),
    'dimrnd': DXFAttr(45),
    'dimdle': DXFAttr(46),
    'dimtp': DXFAttr(47),
    'dimtm': DXFAttr(48),
    'dimfxl': DXFAttr(49, dxfversion='AC1021'),  # undocumented: length of extension line if fixed (dimfxlon = 1)
    'dimtxt': DXFAttr(140),
    'dimcen': DXFAttr(141),
    'dimtsz': DXFAttr(142),
    'dimaltf': DXFAttr(143),
    'dimlfac': DXFAttr(144),
    'dimtvp': DXFAttr(145),
    'dimtfac': DXFAttr(146),
    'dimgap': DXFAttr(147),
    'dimaltrnd': DXFAttr(148),
    'dimtfill': DXFAttr(69, dxfversion='AC1021'),  # 0=None, 1=canvas color, 2=dimtfillclr
    'dimtfillclr': DXFAttr(70, dxfversion='AC1021'),  # color index for dimtfill==2
    'dimtol': DXFAttr(71),
    'dimlim': DXFAttr(72),
    'dimtih': DXFAttr(73),
    'dimtoh': DXFAttr(74),
    'dimse1': DXFAttr(75),
    'dimse2': DXFAttr(76),
    'dimtad': DXFAttr(77),
    'dimzin': DXFAttr(78),
    'dimazin': DXFAttr(79),
    'dimalt': DXFAttr(170),
    'dimaltd': DXFAttr(171),
    'dimtofl': DXFAttr(172),
    'dimsah': DXFAttr(173),
    'dimtix': DXFAttr(174),
    'dimsoxd': DXFAttr(175),
    'dimclrd': DXFAttr(176),
    'dimclre': DXFAttr(177),
    'dimclrt': DXFAttr(178),
    'dimadec': DXFAttr(179),
    'dimunit': DXFAttr(270),
    'dimdec': DXFAttr(271),
    'dimtdec': DXFAttr(272),
    'dimaltu': DXFAttr(273),
    'dimalttd': DXFAttr(274),
    'dimaunit': DXFAttr(275),
    'dimfrac': DXFAttr(276),
    'dimlunit': DXFAttr(277),
    'dimdsep': DXFAttr(278),
    'dimtmove': DXFAttr(279),
    'dimjust': DXFAttr(280),
    'dimsd1': DXFAttr(281),
    'dimsd2': DXFAttr(282),
    'dimtolj': DXFAttr(283),
    'dimtzin': DXFAttr(284),
    'dimaltz': DXFAttr(285),
    'dimalttz': DXFAttr(286),
    'dimfit': DXFAttr(287),  # obsolete, now use DIMATFIT and DIMTMOVE
    'dimupt': DXFAttr(288),
    'dimatfit': DXFAttr(289),
    'dimfxlon': DXFAttr(290, dxfversion='AC1021'),  # undocumented: 1 = fixed extension line length
    'dimtxsty_handle': DXFAttr(340),  # handle of referenced STYLE entry
    # virtual DXF attribute 'dimtxsty': set/get referenced STYLE by name as callback
    'dimtxsty': DXFAttr(VIRTUAL_TAG, xtype=XType.callback, getter='get_text_style', setter='set_text_style'),
    # virtual DXF attribute 'dimldrblk': set/get referenced STYLE by name as callback
    'dimldrblk': DXFAttr(VIRTUAL_TAG, xtype=XType.callback, getter='get_dimldrblk', setter='set_dimldrblk'),
    'dimldrblk_handle': DXFAttr(341),  # handle of referenced BLOCK_RECORD
    'dimblk_handle': DXFAttr(342),  # handle of referenced BLOCK_RECORD
    'dimblk1_handle': DXFAttr(343),  # handle of referenced BLOCK_RECORD
    'dimblk2_handle': DXFAttr(344),  # handle of referenced BLOCK_RECORD

    'dimltype_handle': DXFAttr(345, dxfversion='AC1021'),  # handle of linetype for dimension line
    # virtual DXF attribute 'dimldtype': set/get referenced LINETYPE by name as callback
    'dimltype': DXFAttr(VIRTUAL_TAG,
                        xtype=XType.callback,
                        getter='get_linetype',
                        setter='set_linetype',
                        dxfversion='AC1021'),

    'dimltex1_handle': DXFAttr(346, dxfversion='AC1021'),  # handle of linetype for extension line 1
    # virtual DXF attribute 'dimltex1': set/get referenced LINETYPE by name as callback
    'dimltex1': DXFAttr(VIRTUAL_TAG,
                        xtype=XType.callback,
                        getter='get_ext1_linetype',
                        setter='set_ext1_linetype',
                        dxfversion='AC1021'),

    'dimltex2_handle': DXFAttr(347, dxfversion='AC1021'),  # handle of linetype for extension line 2
    # virtual DXF attribute 'dimltex2': set/get referenced LINETYPE by name as callback
    'dimltex2': DXFAttr(VIRTUAL_TAG,
                        xtype=XType.callback,
                        getter='get_ext2_linetype',
                        setter='set_ext2_linetype',
                        dxfversion='AC1021'),

    'dimlwd': DXFAttr(371, default=LINEWEIGHT_BYBLOCK),  # dimension line lineweight enum value, default BYBLOCK
    'dimlwe': DXFAttr(372, default=LINEWEIGHT_BYBLOCK),  # extension line lineweight enum value, default BYBLOCK
})


class DimStyle(legacy.DimStyle):
    __slots__ = ()
    TEMPLATE = ExtendedTags.from_text(_DIMSTYLETEMPLATE)
    DXFATTRIBS = DXFAttributes(handle105_subclass, symbol_subclass, dimstyle_subclass)
    CODE_TO_DXF_ATTRIB = dict(DXFATTRIBS.build_group_code_items(legacy.dim_filter))

    def _set_blk_handle(self, attr: str, arrow_name: str) -> None:
        if arrow_name == ARROWS.closed_filled:
            # special arrow, no handle needed (is '0' if set)
            # do not create block by default, this will be done if arrow is used
            # and block record handle is not needed here
            self.del_dxf_attrib(attr)
            return

        blocks = self.drawing.blocks
        if ARROWS.is_acad_arrow(arrow_name):
            # create block, because need block record handle is needed here
            block_name = ARROWS.create_block(blocks, arrow_name)
        else:
            block_name = arrow_name

        blk = blocks.get(block_name)
        self.set_dxf_attrib(attr, blk.block_record_handle)

    def _get_arrow_block_name(self, name: str) -> str:
        handle = self.get_dxf_attrib(name, None)
        if handle in (None, '0'):
            # unset handle or handle '0' is default closed filled arrow
            return ARROWS.closed_filled
        else:
            block_name = get_block_name_by_handle(handle, self.drawing)
            return ARROWS.arrow_name(block_name)  # if arrow return standard arrow name else just the block name

    def get_text_style(self) -> str:
        handle = self.get_dxf_attrib('dimtxsty_handle', None)
        if handle:
            return get_text_style_by_handle(handle, self.drawing)
        else:
            logging.warning('DIMSTYLE "{}": text style handle not set.'.format(self.dxf.name))
            return 'STANARD'

    def set_text_style(self, name: str) -> None:
        style = self.drawing.styles.get(name)
        self.set_dxf_attrib('dimtxsty_handle', style.dxf.handle)

    def get_dimblk(self) -> str:
        return self._get_arrow_block_name('dimblk_handle')

    def set_dimblk(self, name) -> None:
        self._set_blk_handle('dimblk_handle', name)

    def get_dimblk1(self) -> str:
        return self._get_arrow_block_name('dimblk1_handle')

    def set_dimblk1(self, name) -> None:
        self._set_blk_handle('dimblk1_handle', name)

    def get_dimblk2(self) -> str:
        return self._get_arrow_block_name('dimblk2_handle')

    def set_dimblk2(self, name) -> None:
        self._set_blk_handle('dimblk2_handle', name)

    def get_dimldrblk(self) -> str:
        return self._get_arrow_block_name('dimldrblk_handle')

    def set_dimldrblk(self, name) -> None:
        self._set_blk_handle('dimldrblk_handle', name)

    def get_ltype_name(self, dimvar: str) -> Optional[str]:
        if self.dxfversion < 'AC1021':
            logger.debug('Linetype support for DIMSTYLE requires DXF R2007 or later.')

        handle = self.get_dxf_attrib(dimvar, None)
        if handle:
            ltype = self.drawing.get_dxf_entity(handle)
            return ltype.dxf.name
        else:
            return None

    def get_linetype(self):
        return self.get_ltype_name('dimltype_handle')

    def get_ext1_linetype(self):
        return self.get_ltype_name('dimltex1_handle')

    def get_ext2_linetype(self):
        return self.get_ltype_name('dimltex2_handle')

    def get_ltype_handle(self, linetype_name: str) -> str:
        ltype = self.drawing.linetypes.get(linetype_name)
        return ltype.dxf.handle

    def set_linetype(self, name: str) -> None:
        self.dxf.dimltype_handle = self.get_ltype_handle(name)

    def set_ext1_linetype(self, name: str) -> None:
        self.dxf.dimltex1_handle = self.get_ltype_handle(name)

    def set_ext2_linetype(self, name: str) -> None:
        self.dxf.dimltex2_handle = self.get_ltype_handle(name)

    def set_linetypes(self, dimline=None, ext1=None, ext2=None) -> None:
        if self.dxfversion >= 'AC1021':
            if dimline is not None:
                self.set_linetype(dimline)
            if ext1 is not None:
                self.set_ext1_linetype(ext1)
            if ext2 is not None:
                self.set_ext2_linetype(ext2)
        else:
            logger.debug('Linetype support requires DXF R2007 or later.')


def get_text_style_by_handle(handle, drawing: 'Drawing', default='STANDARD') -> str:
    try:
        entry = drawing.get_dxf_entity(handle)
    except DXFKeyError:
        logging.warning('Invalid text style handle "{}".'.format(handle))
        text_style_name = default
    else:
        text_style_name = entry.dxf.name
    return text_style_name


def get_block_name_by_handle(handle, drawing: 'Drawing', default='') -> str:
    try:
        entry = drawing.get_dxf_entity(handle)
    except DXFKeyError:
        block_name = default
    else:
        block_name = entry.dxf.name
    return block_name


_UCSTEMPLATE = """0
UCS
5
0
100
AcDbSymbolTableRecord
100
AcDbUCSTableRecord
2
UCSNAME
70
0
10
0.0
20
0.0
30
0.0
11
1.0
21
0.0
31
0.0
12
0.0
22
1.0
32
0.0
"""
ucs_subclass = DefSubclass('AcDbUCSTableRecord', {
    'name': DXFAttr(2),
    'flags': DXFAttr(70),
    'origin': DXFAttr(10, xtype=XType.point3d),
    'xaxis': DXFAttr(11, xtype=XType.point3d),
    'yaxis': DXFAttr(12, xtype=XType.point3d),
})


class UCS(legacy.UCS):
    __slots__ = ()
    TEMPLATE = ExtendedTags.from_text(_UCSTEMPLATE)
    DXFATTRIBS = DXFAttributes(none_subclass, symbol_subclass, ucs_subclass)


_VIEWTEMPLATE = """0
VIEW
5
0
100
AcDbSymbolTableRecord
100
AcDbViewTableRecord
2
VIEWNAME
70
0
10
0.0
20
0.0
11
1.0
21
1.0
31
1.0
12
0.0
22
0.0
32
0.0
40
70.
41
1.0
42
50.0
43
0.0
44
0.0
50
0.0
71
0
"""
view_subclass = DefSubclass('AcDbViewTableRecord', {
    'name': DXFAttr(2),
    'flags': DXFAttr(70),
    'height': DXFAttr(40),
    'width': DXFAttr(41),
    'center_point': DXFAttr(10, xtype=XType.point2d),
    'direction_point': DXFAttr(11, xtype=XType.point3d),
    'target_point': DXFAttr(12, xtype=XType.point3d),
    'lens_length': DXFAttr(42),
    'front_clipping': DXFAttr(43),
    'back_clipping': DXFAttr(44),
    'view_twist': DXFAttr(50),
    'view_mode': DXFAttr(71),
})


class View(legacy.View):
    __slots__ = ()
    TEMPLATE = ExtendedTags.from_text(_VIEWTEMPLATE)
    DXFATTRIBS = DXFAttributes(none_subclass, symbol_subclass, view_subclass)


_VPORTTEMPLATE = """0
VPORT
5
0
100
AcDbSymbolTableRecord
100
AcDbViewportTableRecord
2
VPORTNAME
70
0
10
0.0
20
0.0
11
1.0
21
1.0
12
70.0
22
50.0
13
0.0
23
0.0
14
0.5
24
0.5
15
0.5
25
0.5
16
0.0
26
0.0
36
1.0
17
0.0
27
0.0
37
0.0
40
70.
41
1.34
42
50.0
43
0.0
44
0.0
50
0.0
51
0.0
71
0
72
1000
73
1
74
3
75
0
76
0
77
0
78
0
"""
vport_subclass = DefSubclass('AcDbViewportTableRecord', {
    'name': DXFAttr(2),
    'flags': DXFAttr(70),
    'lower_left': DXFAttr(10, xtype=XType.point2d),
    'upper_right': DXFAttr(11, xtype=XType.point2d),
    'center_point': DXFAttr(12, xtype=XType.point2d),
    'snap_base': DXFAttr(13, xtype=XType.point2d),
    'snap_spacing': DXFAttr(14, xtype=XType.point2d),
    'grid_spacing': DXFAttr(15, xtype=XType.point2d),
    'direction_point': DXFAttr(16, xtype=XType.point3d),
    'target_point': DXFAttr(17, xtype=XType.point3d),
    'height': DXFAttr(40),
    'aspect_ratio': DXFAttr(41),
    'lens_length': DXFAttr(42),
    'front_clipping': DXFAttr(43),
    'back_clipping': DXFAttr(44),
    'snap_rotation': DXFAttr(50),
    'view_twist': DXFAttr(51),
    'status': DXFAttr(68),
    'view_mode': DXFAttr(71),
    'circle_zoom': DXFAttr(72),
    'fast_zoom': DXFAttr(73),
    'ucs_icon': DXFAttr(74),
    'snap_on': DXFAttr(75),
    'grid_on': DXFAttr(76),
    'snap_style': DXFAttr(77),
    'snap_isopair': DXFAttr(78),
})


class VPort(legacy.VPort):
    __slots__ = ()
    TEMPLATE = ExtendedTags.from_text(_VPORTTEMPLATE)
    DXFATTRIBS = DXFAttributes(none_subclass, symbol_subclass, vport_subclass)


_BLOCKRECORDTEMPLATE = """0
BLOCK_RECORD
5
0
330
0
100
AcDbSymbolTableRecord
100
AcDbBlockTableRecord
2
BLOCK_RECORD_NAME
340
0
"""
blockrec_subclass = DefSubclass('AcDbBlockTableRecord', {
    'name': DXFAttr(2),
    'layout': DXFAttr(340),
})


class BlockRecord(DXFEntity):
    """ Internal Object - use at your own risk!

    Required fields:
    owner: Soft-pointer ID/handle to owner object
    layout: Hard-pointer ID/handle to associated LAYOUT object - is '0' if block definition
    """
    __slots__ = ()
    TEMPLATE = ExtendedTags.from_text(_BLOCKRECORDTEMPLATE)
    DXFATTRIBS = DXFAttributes(none_subclass, symbol_subclass, blockrec_subclass)
