import pyembroidery
from pyembroidery import (
    CONTINGENCY_SEQUIN_UTILIZE,
    decode_embroidery_command,
    STITCH,
    COLOR_CHANGE,
    NEEDLE_SET,
    TRIM,
    JUMP,
    SEQUIN_MODE,
    SEQUIN_EJECT,
    STOP,
    SLOW,
    FAST,
    END,
    get_common_name_dictionary,
    COMMAND_MASK,
)
from pyembroidery.WriteHelper import write_string_utf8

WRITES_SPEEDS = True
SEQUIN_CONTINGENCY = CONTINGENCY_SEQUIN_UTILIZE


def write(pattern, f, settings=None):
    writer = GenericWriter(pattern, f, settings)
    writer.write()


class GenericWriter:
    """
    Generic Writer will write generic data fit to a formatted set of strings.

    Blocks are established by the first segment.
    Colors are established by the first segment or the first segment after a color change or by a needle_set.
    Documents are established by the first segment, or the first segment after an end.

    Segment is the default for any-command. Specific commands override segment.

    stitch is the default stitch.
    stitch_first overrides stitch for the first stitch in a block.
    stitch_last overrides stitch for the last stitch in a block.

    trims and jumps occurring before a color change belong to the previous color.
    trims and jumps occurring after a color change belong to the next color.

    Missing segments are treated as if they never existed. Value properties will differ if segments are excluded.
    """

    def __init__(self, pattern, f, settings):
        self.pattern = pattern
        self.f = f
        self.settings = settings
        self.metadata_entry = settings.get("metadata_entry", None)
        self.thread_entry = settings.get("thread_entry", None)
        self.pattern_start = settings.get("pattern_start", None)
        self.pattern_end = settings.get("pattern_end", None)
        self.document_start = settings.get("document_start", None)
        self.document_end = settings.get("document_end", None)
        self.color_start = settings.get("color_start", None)
        self.color_end = settings.get("color_end", None)
        self.color_join = settings.get("color_join", None)
        self.block_start = settings.get("block_start", None)
        self.block_end = settings.get("block_end", None)
        self.block_join = settings.get("block_join", None)
        self.segment_start = settings.get("segment_start", None)
        self.segment = settings.get("segment", None)
        self.segment_end = settings.get("segment_end", None)
        self.segment_join = settings.get("segment_join", None)
        self.stitch_first = settings.get("stitch_first", None)
        self.stitch_last = settings.get("stitch_last", None)
        self.stitch = settings.get("stitch", None)
        self.stop = settings.get("stop", None)
        self.jump = settings.get("jump", None)
        self.trim = settings.get("trim", None)
        self.needle_set = settings.get("needle_set", None)
        self.color_change = settings.get("color_change", None)
        self.sequin = settings.get("sequin", None)
        self.sequin_mode = settings.get("sequin_mode", None)
        self.slow = settings.get("slow", None)
        self.fast = settings.get("fast", None)
        self.end = settings.get("end", None)

        self.format_dictionary = {}
        self.pattern_established = False
        self.document_established = False
        self.color_established = False
        self.block_established = False
        self.document_index = -1
        self.thread = None
        self.thread_index = -1
        self.stitch_index = -1
        self.color_index = -1
        self.block_index = -1
        self.dx = 0
        self.dy = 0
        self.xx = 0
        self.yy = 0
        self.last_x = 0
        self.last_y = 0
        self.z = 0.0
        self.z_increment = settings.get("stitch_z_travel", 10.0)
        self.command_index = 0

        self.current_stitch = None
        self.x = None
        self.y = None
        self.command = None
        self.cmd = None
        self.thread = None
        self.needle = None
        self.order = None
        self.cmd_str = None

        self.block_closing = False
        self.color_closing = False
        self.document_closing = False

        self.block_opening = False
        self.color_opening = False
        self.document_opening = False

    def write_opens(self):
        if self.document_opening:
            self.document_opening = False
            if self.document_start is not None:
                write_string_utf8(self.f, self.document_start.format_map(self.format_dictionary))
        if self.color_opening:
            self.color_opening = False
            if self.color_join is not None and self.color_index != 0:
                write_string_utf8(self.f, self.color_join.format_map(self.format_dictionary))
            if self.color_start is not None:
                write_string_utf8(self.f, self.color_start.format_map(self.format_dictionary))
        if self.block_opening:
            self.block_opening = False
            if self.block_join is not None and self.block_index != 0:
                write_string_utf8(self.f, self.block_join.format_map(self.format_dictionary))
            if self.block_start is not None:
                write_string_utf8(self.f, self.block_start.format_map(self.format_dictionary))

    def write_closes(self):
        if self.block_closing:
            self.block_closing = False
            if self.block_end is not None:
                write_string_utf8(self.f, self.block_end.format_map(self.format_dictionary))
        if self.color_closing:
            self.color_closing = False
            if self.color_end is not None:
                write_string_utf8(self.f, self.color_end.format_map(self.format_dictionary))
        if self.document_closing:
            self.document_closing = False
            if self.document_end is not None:
                write_string_utf8(self.f, self.document_end.format_map(self.format_dictionary))

    def get_write_segment(self, cmd):
        # SEQUIN_MODE
        if cmd == SEQUIN_MODE and self.sequin_mode is not None:
            return self.sequin_mode

        # SEQUIN
        if cmd == SEQUIN_EJECT and self.sequin is not None:
            return self.sequin

        # STITCH
        if cmd == STITCH and self.stitch is not None:
            return self.stitch

        # TRIM
        if cmd == TRIM and self.trim is not None:
            return self.trim

        # JUMP
        if cmd == JUMP and self.jump is not None:
            return self.jump

        # COLOR CHANGE
        if cmd == COLOR_CHANGE and self.color_change is not None:
            return self.color_change

        # NEEDLE SET
        if cmd == NEEDLE_SET and self.needle_set is not None:
            return self.needle_set

        # STOP COMMAND
        if cmd == STOP and self.stop is not None:
            return self.stop

        # SLOW COMMAND
        if cmd == SLOW and self.slow is not None:
            return self.slow

        # FAST COMMAND
        if cmd == FAST and self.fast is not None:
            return self.fast

        # END COMMAND
        if cmd == END and self.end is not None:
            return self.end

        # GENERIC SEGMENT
        return self.segment

    def set_document_statistics(self):
        pattern = self.pattern
        self.format_dictionary.update(pattern.extras)

        bounds = pattern.bounds()  # convert to mm.
        width = bounds[2] - bounds[0]
        height = bounds[3] - bounds[1]

        stitch_counts = {}
        for s in pattern.stitches:
            command = s[2] & COMMAND_MASK
            if command in stitch_counts:
                stitch_counts[command] += 1
            else:
                stitch_counts[command] = 1

        names = get_common_name_dictionary()
        for name in names:
            value = names[name].lower()
            self.format_dictionary[value + "_count"] = stitch_counts.get(name,0)
        self.format_dictionary.update(
            {
                "stitch_total": pattern.count_stitches(),
                "thread_total": pattern.count_threads(),
                "extents_left": bounds[0],
                "extends_top": bounds[1],
                "extends_right": bounds[2],
                "extends_bottom": bounds[3],
                "extends_width": width,
                "extends_height": height,
                "extents_left_mm": bounds[0] / 10.0,
                "extends_top_mm": bounds[1] / 10.0,
                "extends_right_mm": bounds[2] / 10.0,
                "extends_bottom_mm": bounds[3] / 10.0,
                "extends_width_mm": width / 10.0,
                "extends_height_mm": height / 10.0,
            }
        )

    def update_positions(self, x, y, cmd):
        self.dx = x - self.last_x
        self.dy = y - self.last_y
        idx = int(round(x - self.xx))
        idy = int(round(y - self.yy))
        self.xx += idx
        self.yy += idy
        self.format_dictionary.update(
            {
                "x": x,
                "y": y,
                "z": self.z,
                "_x": -x,
                "_y": -y,
                "dx": self.dx,
                "dy": self.dy,
                "idx": idx,
                "idy": idy,
                "_idx": -idx,
                "_idy": -idy,
                "ix": self.xx,
                "iy": self.yy,
                "_ix": -self.xx,
                "_iy": -self.yy,
                "last_x": self.last_x,
                "last_y": self.last_y,
                "_last_x": -self.last_x,
                "_last_y": -self.last_y,
            }
        )
        if cmd == STITCH:
            self.z += self.z_increment
        self.last_x = x
        self.last_y = y

    def update_command(self):
        try:
            self.current_stitch = self.pattern.stitches[self.command_index]

            self.x, self.y, self.command = self.current_stitch
            self.cmd, self.thread, self.needle, self.order = decode_embroidery_command(
                self.command
            )
            self.cmd_str = pyembroidery.get_common_name_dictionary()[self.cmd]
        except IndexError:
            self.current_stitch = None
            self.x = None
            self.y = None
            self.command = None
            self.cmd = None
            self.thread = None
            self.needle = None
            self.order = None
            self.cmd_str = None
        self.format_dictionary.update(
            {
                "index": self.command_index,
                "command": self.command,
                "cmd_str": self.cmd_str,
                "cmd": self.cmd,
                "cmd_thread": self.thread,
                "cmd_needle": self.needle,
                "cmd_order": self.order,
            }
        )

    def open_pattern(self):
        if not self.pattern_established:
            self.pattern_established = True
            if self.pattern_start is not None:
                write_string_utf8(
                    self.f, self.pattern_start.format_map(self.format_dictionary)
                )

    def open_document(self):
        # DOCUMENT START
        if not self.document_established:
            self.document_established = True
            self.document_index += 1
            self.document_opening = True
            self.color_index = 0

            self.format_dictionary.update(
                {
                    "document_index": self.document_index,
                    "document_index1": self.document_index + 1,
                    "color_index": self.color_index,
                    "color_index1": self.color_index + 1,
                    "block_index": self.block_index,
                    "block_index1": self.block_index + 1,
                }
            )

    def open_color(self):
        # COLOR START
        if not self.color_established:
            self.color_established = True
            self.thread_index += 1
            self.color_opening = True

            self.thread = self.pattern.get_thread_or_filler(self.thread_index)
            self.block_index = 0
            self.color_index += 1
            self.format_dictionary.update(
                {
                    "document_index": self.document_index,
                    "document_index1": self.document_index + 1,
                    "color_index": self.color_index,
                    "color_index1": self.color_index + 1,
                    "block_index": self.block_index,
                    "block_index1": self.block_index + 1,
                }
            )

    def open_block(self):
        # BLOCK START
        if not self.block_established:
            self.block_established = True
            self.block_index += 1
            self.block_opening = True
            self.format_dictionary.update(
                {
                    "document_index": self.document_index,
                    "document_index1": self.document_index + 1,
                    "color_index": self.color_index,
                    "color_index1": self.color_index + 1,
                    "block_index": self.block_index,
                    "block_index1": self.block_index + 1,
                }
            )

    def write_segment(self, segment):
        # SEGMENT
        if self.segment_start is not None:
            write_string_utf8(self.f, self.segment_start.format_map(self.format_dictionary))

        write_string_utf8(self.f, segment.format_map(self.format_dictionary))

        # SEGMENT JOIN
        if self.segment_join is not None:
            write_string_utf8(self.f, self.segment_join.format_map(self.format_dictionary))

        # SEGMENT_END
        if self.segment_end is not None:
            write_string_utf8(self.f, self.segment_end.format_map(self.format_dictionary))

    def close_pattern(self):
        if self.pattern_established:
            self.pattern_established = False
            if self.pattern_end is not None:
                write_string_utf8(
                    self.f, self.pattern_end.format_map(self.format_dictionary)
                )

    def close_document(self):
        # DOCUMENT END
        if self.document_established:
            self.document_established = False
            self.document_closing = True

    def close_color(self):
        # COLOR END
        if self.color_established:
            self.color_established = False
            self.color_closing = True

    def close_block(self):
        # BLOCK END
        if self.block_established:
            self.block_established = False
            self.block_closing = True

    def write(self):
        # DOCUMENT STATISTICS
        self.set_document_statistics()

        self.open_pattern()
        if self.metadata_entry is not None:
            for i, key in enumerate(self.pattern.extras):
                value = self.pattern.extras[key]
                self.format_dictionary.update({
                    "metadata_index": i,
                    "metadata_key": str(key),
                    "metadata_value": str(value),
                })
                write_string_utf8(
                    self.f, self.metadata_entry.format_map(self.format_dictionary)
                )

        if self.thread_entry is not None:
            for i, thread in enumerate(self.pattern.threadlist):
                self.format_dictionary.update({
                    "thread_index": i,
                    "thread_color": thread.hex_color(),
                    "thread_description": thread.description,
                    "thread_brand": thread.brand,
                    "thread_catalog_number": thread.catalog_number,
                    "thread_chart": thread.chart,
                    "thread_details": thread.details,
                    "thread_weight": thread.weight,
                    "thread_red": thread.get_red(),
                    "thread_green": thread.get_green(),
                    "thread_blue": thread.get_blue(),
                })
                write_string_utf8(
                    self.f, self.thread_entry.format_map(self.format_dictionary)
                )
        for self.command_index in range(0, len(self.pattern.stitches)):
            self.update_command()
            write_segment = self.get_write_segment(self.cmd)

            # MAIN CODE, there is something to write.
            if write_segment is not None:
                if isinstance(write_segment, dict):
                    key, default = write_segment[None]
                    key = key.format_map(self.format_dictionary)
                    write_segment = write_segment.get(key, default)
                self.update_positions(self.x, self.y, self.cmd)
                if self.cmd == SEQUIN_MODE:
                    self.open_document()
                    self.open_color()
                    self.open_block()

                # SEQUIN
                if self.cmd == SEQUIN_EJECT:
                    self.open_document()
                    self.open_color()
                    self.open_block()

                # STITCH
                if self.cmd == STITCH:
                    self.open_document()
                    self.open_color()
                    self.open_block()

                # TRIM
                if self.cmd == TRIM:
                    self.open_document()
                    self.open_color()

                # JUMP
                if self.cmd == JUMP:
                    self.open_document()
                    self.open_color()
                    self.open_block()

                # COLOR CHANGE
                if self.cmd == COLOR_CHANGE:
                    self.open_document()

                # NEEDLE SET
                if self.cmd == NEEDLE_SET:
                    self.open_document()
                    self.open_color()

                # STOP COMMAND
                if self.cmd == STOP:
                    self.open_document()
                    self.open_color()
                    self.open_block()

                # SLOW COMMAND
                if self.cmd == SLOW:
                    self.open_document()
                    self.open_color()
                    self.open_block()

                # FAST COMMAND
                if self.cmd == FAST:
                    self.open_document()
                    self.open_color()
                    self.open_block()

                # END COMMAND
                if self.cmd == END:
                    self.open_document()
                    self.open_color()
                    self.open_block()

                self.write_opens()
                self.write_segment(write_segment)

                if self.cmd == SEQUIN_MODE:
                    pass

                # SEQUIN
                if self.cmd == SEQUIN_EJECT:
                    pass

                # STITCH
                if self.cmd == STITCH:
                    pass

                # TRIM
                if self.cmd == TRIM:
                    self.close_block()

                # JUMP
                if self.cmd == JUMP:
                    pass

                # COLOR CHANGE
                if self.cmd == COLOR_CHANGE:
                    self.close_block()
                    self.close_color()

                # NEEDLE SET
                if self.cmd == NEEDLE_SET:
                    pass

                # STOP COMMAND
                if self.cmd == STOP:
                    pass

                # SLOW COMMAND
                if self.cmd == SLOW:
                    pass

                # FAST COMMAND
                if self.cmd == FAST:
                    pass

                # END COMMAND
                if self.cmd == END:
                    self.close_block()
                    self.close_color()
                    self.close_document()
                self.write_closes()

        self.close_block()
        self.close_color()
        self.close_document()
        self.close_pattern()
