# Created: 08.04.2018
# Copyright (c) 2018, Manfred Moitzi
# License: MIT-License
from typing import TYPE_CHECKING, Iterable, Dict
from ezdxf.lldxf.const import DXFIndexError

from .graphics import ExtendedTags, DXFAttr, DefSubclass, DXFAttributes, XType
from .graphics import none_subclass, entity_subclass, ModernGraphicEntity
from .dxfobjects import DXFObject
from .object_manager import ObjectManager

if TYPE_CHECKING:
    from ezdxf.eztypes import Drawing, Tags

# example: processing: D:\source\dxftest\CADKitSamples\Lock-Off.dxf


_MLINE_TPL = """0
MLINE
5
0
330
0
100
AcDbEntity
8
0
100
AcDbMline
2
STANDARD
340
0
40
1.0
70
0
71
1
72
1
73
1
10
0.0
20
0.0
30
0.0
210
0.0
220
0.0
230
1.0
"""

mline_subclass = DefSubclass('AcDbMline', {
    'mline_style': DXFAttr(2),
    'mline_style_id': DXFAttr(340),
    'scale_factor': DXFAttr(40, default=1),
    'justification': DXFAttr(70),  # 0 = Top; 1 = Zero; 2 = Bottom
    'flags': DXFAttr(71),  # Flags (bit-coded values):
    # 1 = Has at least one vertex (code 72 is greater than 0)
    # 2 = Closed
    # 4 = Suppress start caps
    # 8 = Suppress end caps
    'n_vertices': DXFAttr(72),  # Number of vertices
    'n_style_elements': DXFAttr(73),  # Number of elements in MLINESTYLE definition
    'start_point': DXFAttr(10, xtype=XType.point3d),  # in WCS!
    'extrusion': DXFAttr(210, xtype=XType.point3d),  # but all vertices in WCS!
    # 11: vertex coordinates (multiple entries; one entry for each vertex)
    # 12: Direction vector of segment starting at this vertex (multiple entries; one for each vertex)
    # 13: Direction vector of miter at this vertex (multiple entries: one for each vertex)
    # 73: Number of parameters for this element (repeats for each element in segment)
    # 41: Element parameters (repeats based on previous code 74)
    # 75: Number of area fill parameters for this element (repeats for each element in segment)
    # 42: Area fill parameters (repeats based on previous code 75)
})

# The group code 41 parameterization is a list of real values, one real per group code 41. The list may contain zero or
# more items. The first group code 41 value is the distance from the segment vertex along the miter vector to the point
# where the line element's path intersects the miter vector. The next group code 41 value is the distance along the line
# element's path from the point defined by the first group 41 to the actual start of the line element. The next is the
# distance from the start of the line element to the first break (or cut) in the line element. The successive group
# code 41 values continue to list the start and stop points of the line element in this segment of the mline. Linetypes
# do not affect group 41 lists.
#
# The group code 42 parameterization is also a list of real values. Similar to the 41 parameterization, it describes the
# parameterization of the fill area for this mline segment. The values are interpreted identically to the 41 parameters
# and when taken as a whole for all line elements in the mline segment, they define the boundary of the fill area for
# the mline segment.
#
# A common example of the use of the group code 42 mechanism is when an unfilled mline crosses over a filled mline and
# mledit is used to cause the filled mline to appear unfilled in the crossing area. This would result in two group 42s
# for each line element in the affected mline segment; one for the fill stop and one for the fill start.
#
# The 2 group codes in mline entities and mlinestyle objects are redundant fields. These groups should not be modified
# under any circumstances, although it is safe to read them and use their values. The correct fields to modify are as
# follows:
#
# Mline
# The 340 group in the same object, which indicates the proper MLINESTYLE object.
#
# Mlinestyle
# The 3 group value in the MLINESTYLE dictionary, which precedes the 350 group that has the handle or entity name of
# the current mlinestyle.


class MLine(ModernGraphicEntity):
    # Requires AC1021/R2007
    __slots__ = ()
    TEMPLATE = ExtendedTags.from_text(_MLINE_TPL)
    DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, mline_subclass)

    def get_points(self):
        return []


_MLINE_STYLE_TPL = """0
MLINESTYLE
5
0
102
{ACAD_REACTORS
102
}
330
0
100
AcDbMlineStyle
2
STANDARD
70
0
3

62
256
51
90.0
52
90.0
71
2
49
0.5
62
256
6
BYLAYER
49
-0.5
62
256
6
BYLAYER
"""

mline_style_subclass = DefSubclass('AcDbMlineStyle', {
    'name': DXFAttr(2),
    'flags': DXFAttr(70),  # Flags (bit-coded):
    # 1 =Fill on
    # 2 = Display miters
    # 16 = Start square end (line) cap
    # 32 = Start inner arcs cap
    # 64 = Start round (outer arcs) cap
    # 256 = End square (line) cap
    # 512 = End inner arcs cap
    # 1024 = End round (outer arcs) cap
    'description': DXFAttr(3),  # Style description (string, 255 characters maximum)
    'fill_color': DXFAttr(62),  # Fill color (integer, default = 256)
    'start_angle': DXFAttr(51),  # Start angle (real, default is 90 degrees)
    'end_angle': DXFAttr(52),  # End angle (real, default is 90 degrees)
    'n_elements': DXFAttr(71),  # Number of elements
    # 49: Element offset (real, no default). Multiple entries can exist; one entry for each element
    # 62: Element color (integer, default = 0). Multiple entries can exist; one entry for each element
    # 6: Element linetype (string, default = BYLAYER). Multiple entries can exist; one entry for each element
})


class MLineStyle(DXFObject):
    # Requires AC1021/R2007
    __slots__ = ()
    TEMPLATE = ExtendedTags.from_text(_MLINE_STYLE_TPL)
    DXFATTRIBS = DXFAttributes(none_subclass, mline_style_subclass)

    @property
    def AcDbMLineStyle(self) -> 'Tags':
        return self.tags.subclasses[1]

    def get_elements(self) -> Iterable[Dict]:
        tags = self.AcDbMLineStyle
        try:
            start = tags.tag_index(71)
        except DXFIndexError:
            return
        else:
            collector = None
            for code, value in tags[start+1:]:
                if code == 49:
                    if collector is not None:
                        yield collector
                    collector = {'offset': value}
                elif code == 62:
                    collector['color'] = value
                elif code == 6:
                    collector['linetype'] = value
            if collector is not None:
                yield collector


class MLineStyleManager(ObjectManager):
    def __init__(self, drawing: 'Drawing'):
        super().__init__(drawing, dict_name='ACAD_MLINESTYLE', object_type='MLINESTYLE')
        self.create_required_entries()

    def create_required_entries(self) -> None:
        for name in ('STANDARD', ):
            if name not in self.object_dict:
                self.new(name)
