"""
Name: template_generator
Title: Template Generator
Author: Cooper
Date: 24/04/2019
Modified: 26/06/2024

Desc: This is based off of the isi_msg_handler, a lot of these ethernet based networks only give you text and if you are
lucky colour information.  This module should be presented with this raw data and put it in a series of templates for
our signs to display.

!!!! This module no longer uses databaseParser. !!!!

The main issue is that the despite the key(s) of the data being inconsistent amongst the protocols.  In order for this
module to work the data given to it must be consistent.

Example data input to this module:

display_data = {
    "$bcol": "0,0,0",
    "$fcol": "0,0,0
    "$rn": "12A",
    "$dest": ["Language1_TopLine/Language1_BottomLine", "Language2"]
    }

Where:
RouteColour is an RGB value
RouteNumber is a string
Destination is a list of destinations, if a given index has a delimiter in it then it will assume two line destination

There exists a mapping routine in the original ISI implementation, which maps key elements to configurable names which
is stored in the config file.  But given how the data structure for ISI was known it was possible to do this.
It probably isn't possible with mapping in the general case.  But keep this in mind!

There are three scenarios in which data is presented:
1) Manual control via front panel
    The data here will be from an eric.bin stored on the console
2) Remote control via dest code:  The remote device only sends destination/route codes
    The data here will be from an eric.bin stored on the console
3) Remote control via display text: The remote device sends destination/route text
    The data will be used to generate templates to be funneled into Renderbox

This class will not do any data grabbing via a given protocol.  It should be expected that some form of remote priority
should be implemented and to have it so that it behaves the same as our controllers:
RP=0  No remote priority, remote codes will only be executed if manual destcode is 0
RP=1  Remote always has priority
RP=2  Temporary manual priority, that is manual changes will persist until new remote code is sent

Templates are defined in the config.cfg

"""
import os
import logging
import textwrap

class TemplateGenerator(object):
    def __init__(self, config_dict, data_dir):
        self.config_dict = config_dict

        self.console_display_text = None
        self.console_display_code = None

        # At the very least the basic templates are used when nothing else is supplied to prevent things breaking
        self.mode_zero = self.config_dict.get("TEMPLATES_mode0", r"{\mode0$col{$rn}}")
        self.mode_one = self.config_dict.get("TEMPLATES_mode1", r"{\mode1\pt30$al$col$rw{$rn}\fs{$dest}}")
        self.mode_two = self.config_dict.get("TEMPLATES_mode2", r"{\mode2\pt30{$dest_1}\fs{$dest_2}}")
        self.mode_three = self.config_dict.get("TEMPLATES_mode3", r"{\mode3\pt30$al$col$rw{$rn}\fs{$dest_1}\fs{$dest_2}}")

        self.enable_font_replacement = self.config_dict.get("TEMPLATES_enable_font_replacement", False)

        self.rn_font = self.config_dict.get("TEMPLATES_rn_font", None)
        self.rn_font_no_des = self.config_dict.get("TEMPLATES_rn_font_no_des", None)
        self.single_line_font = self.config_dict.get("TEMPLATES_single_line_font", None)
        self.single_line_font_no_des = self.config_dict.get("TEMPLATES_single_line_font_no_des", None)
        self.double_line_font = self.config_dict.get("TEMPLATES_double_line_font", None)
        self.double_line_font_no_des = self.config_dict.get("TEMPLATES_double_line_font_no_des", None)

    """
    ###################################################################################################################
    Colour handling stuff
    """
    def convert_colour(self, colour):
        """
        Converts an RGB value into Hanover colour value
        :param colour: Colour value as an comma separated RGB value
        :return: Hanover equivalent colour BGR colour value
        """
        if colour == "":
            colour = "0,0,0"
        if colour == None or colour == "None":
            colour = "0,0,0"

        r, g, b = colour.split(",")

        r = int(r)
        g = int(g) << 8
        b = int(b) << 16

        col_val = b + g + r     #Trust me this works, dont question it.

        return str(col_val)

    def convert_colour_back(self, hanover_bgr_value):
        """
        Converts a Hanover bgr value back into RGB.  This colour value can be typically found in a superX string.
        Not currently used for anything but preserved here for reference.
        """
        binary_val = "{0:b}".format(hanover_bgr_value).zfill(24)
        _b, _g, _r = [binary_val[i:i + 8] for i in range(0, 24, 8)]

        r = int(_r, 2)
        g = int(_g, 2)
        b = int(_b, 2)

        print("%s,%s,%s" % (r, g, b), "%02X%02X%02X" % (r, g, b))

    def generate_colour_command(self, fc, bc):
        """
        Takes the colour template in the config file and inserts the values of the colours
        :param fc: foreground colour value as an comma separated RGB value
        :param bc: background colour value as an comma separated RGB value
        :return: SuperX colour command
        """
        fore = self.convert_colour(fc)
        back = self.convert_colour(bc)

        colour_template = self.config_dict.get("TEMPLATES_colour", r"{\mrn\fc$fore\bc$back\it0\ic0\oc0\ot1\osp1}")
        colour_command = colour_template.replace("$fore", fore).replace("$back", back)

        return colour_command

    """
    ###################################################################################################################
    Text handling stuff
    """
    def wrap_text(self, text, settings=None):
        """
        This will attempt to wrap the text in situations where a delimiter is not provided by the data provider.

        If settings is None, then use the values set in config.cfg, else use
        """
        delimiter = self.config_dict.get("TEMPLATES_delimiter", r"/")

        if settings is None:
            enable_wrap = self.config_dict.get("TEMPLATES_auto_wrap", False)

            if enable_wrap:
                wrap_width = int(self.config_dict.get("TEMPLATES_wrap_width", "16"))
                max_lines = int(self.config_dict.get("TEMPLATES_max_lines", "2"))
                break_long_words = self.config_dict.get("TEMPLATES_break_long_words", False)
                placeholder = self.config_dict.get("TEMPLATES_placeholder", " .")
            else:
                return text
        else:
            wrap_width = settings.get("wrap_width", 16)
            max_lines = settings.get("TEMPLATE_max_lines", 2)
            break_long_words = self.config_dict.get("TEMPLATE_break_long_words", False)
            placeholder = self.config_dict.get("TEMPLATE_placeholder", " .")

        wrapped_text = textwrap.fill(text,
                                     width=wrap_width,
                                     max_lines=max_lines,
                                     break_long_words=break_long_words,
                                     placeholder=placeholder)

        return wrapped_text.replace("\n", delimiter)

    def split_text(self, text):
        """
        This function splits the text that ends up in the different superX fields.  This was originally in
        self.generate_templates but it was getting a bit phat.  With a ph cos we don't body shame here.
        """
        delimiter = self.config_dict.get("TEMPLATES_delimiter", r"/")

        if delimiter not in text:
            text = self.wrap_text(text)

        if delimiter in text:
            two_line = True
            split_dests = text.split(delimiter)
            dest_0 = text.replace(delimiter, " ")
            dest_1 = split_dests[0]
            dest_2 = split_dests[1]
        else:
            two_line = False
            dest_0 = text
            dest_1 = dest_2 = ""

        return two_line, dest_0, dest_1, dest_2

    """
    ###################################################################################################################
    Font selection
    """
    def substitute_descender_fonts_explicit(self, enabled, descender_present):
        """
        This substitutes the font depending on whether the incoming text has descenders or not.

        #TODO need to deal with single line text messages separately from RN only

        """
        example_command = r"xt --sign=160x19 -Oh /usr/share/renderbox/fontlib-bino.bin  -c {\mode0{\*\font hn24n}{Hello}}"
        font_template = r"{\*\font $fnt}"

        #The templates contain all the appropriate modes 0-3
        templates = [""]*5
        rn_font = ""
        sl_font = ""
        dl_font = ""

        if enabled:
            if descender_present:
                if self.rn_font is not None:
                    rn_font = font_template.replace("$fnt", self.rn_font)
                if self.single_line_font is not None:
                    sl_font = font_template.replace("$fnt", self.single_line_font)
                if self.double_line_font is not None:
                    dl_font = font_template.replace("$fnt", self.double_line_font)
            else:
                if self.single_line_font_no_des is not None:
                    rn_font = font_template.replace("$fnt", self.rn_font_no_des)
                if self.single_line_font_no_des is not None:
                    sl_font = font_template.replace("$fnt", self.single_line_font_no_des)
                if self.double_line_font_no_des is not None:
                    dl_font = font_template.replace("$fnt", self.double_line_font_no_des)

            templates[0] =  self.mode_zero.replace("$fnt_rn", sl_font)
            templates[1] =  self.substituter(self.mode_one, [("$fnt_rn", rn_font), ("$fnt_dest", sl_font)])
            templates[2] =  self.substituter(self.mode_two, [("$fnt_dest_1", dl_font),("$fnt_dest_2", dl_font)])
            templates[3] =  self.substituter(self.mode_three, [("$fnt_rn", rn_font), ("$fnt_dest_1", dl_font),("$fnt_dest_2", dl_font)])
            templates[4] =  self.mode_zero.replace("$fnt_rn", "")   #This is for RN only no text
        else:
            templates[0] =  self.mode_zero.replace("$fnt_rn", "")
            templates[1] =  self.substituter(self.mode_one, [("$fnt_rn", ""), ("$fnt_dest", "")])
            templates[2] =  self.substituter(self.mode_two, [("$fnt_dest_1", ""),("$fnt_dest_2", "")])
            templates[3] =  self.substituter(self.mode_three, [("$fnt_rn", ""), ("$fnt_dest_1", ""),("$fnt_dest_2", "")])
            templates[4] =  self.mode_zero.replace("$fnt_rn", "")   #This is for RN only no text

        return templates

    def substitute_descender_fonts_auto(self, enabled, descender_present):
        """
        From the font oracle Phil there appears to be an alternative way for Renderbox to select the fonts with no
        descenders and that is to instruct it to use RN fonts via the \route command.

        This has advantages in the fact no one has to care what fonts are included just set and forget.

        This relies on the template having $sx_route as part of its string.

        """
        templates = [""] * 5

        if enabled:
            if descender_present:
                templates[0] = self.mode_zero.replace("$sx_route", "")
                templates[1] = self.substituter(self.mode_one, [("$sx_route", "")])
                templates[2] = self.substituter(self.mode_two, [("$sx_route", "")])
                templates[3] = self.substituter(self.mode_three, [("$sx_route", "")])
                templates[4] = self.mode_zero.replace("$sx_route", "")  # This is for RN only no text
            else:
                templates[0] = self.mode_zero.replace("$sx_route", r"\route")
                templates[1] = self.substituter(self.mode_one, [("$sx_route", r"\route")])
                templates[2] = self.substituter(self.mode_two, [("$sx_route", r"\route")])
                templates[3] = self.substituter(self.mode_three, [("$sx_route", r"\route")])
                templates[4] = self.mode_zero.replace("$sx_route", "")  # This is for RN only no text
        else:
            templates[0] = self.mode_zero.replace("$sx_route", "")
            templates[1] = self.substituter(self.mode_one, [("$sx_route", "")])
            templates[2] = self.substituter(self.mode_two, [("$sx_route", "")])
            templates[3] = self.substituter(self.mode_three, [("$sx_route", "")])
            templates[4] = self.mode_zero.replace("$sx_route", "")  # This is for RN only no text

        return templates


    def check_for_descenders(self, text):
        """
        This checks whether the text contains descenders or not
        """

        characters_with_descenders = ["g", "j", "p", "q", "y"]

        for character in characters_with_descenders:
            index = text.find(character)

            if index != -1:
                # character exists in the string if a value other than -1 is returned
                logging.debug("TG: Descender: %s found at position %s" % (character, index))
                return True

        return False


    """
    ###################################################################################################################
    Template generation
    """
    def substituter(self, template, substitutions):
        """
        This is intended to make substitutions easier as well as adding additional substitutions should they be needed
        down the line

        Substitutions are a list of key, value pairs e.g. [("$rn", "1"), ("$dest", "Lewes")]
        """
        _template = template

        for substitution in substitutions:
            placeholder = substitution[0]
            value = substitution[1]

            _template = _template.replace(placeholder, value)

        logging.debug(_template)
        return _template

    def generate_templates(self, template_items):
        """
        Takes in the dictionary of display items and generates a series of messages using templates in the config file:
        Mode0: Generally RN signs
        Mode1: RN + Single line for side displays
        Mode3: RN + Two line for full size displays
        Generates messages for both monochrome and colour RN signs.  Can handle left or right justifications

        :param template_items: dictionary of display data (see top of module)
        :return: A tuple containing two lists, both lists contain Mode0, Mode1 and Mode3 messages in that order.  The
        difference being is one set is for monochrome signs the other set is for colour signs.  Example:
        ([mode0_mono, mode1_mono, mode3_mono], [mode0_col, mode1_col, mode3_col])
        """
        generated_templates_col = [""]*3
        generated_templates_mono = [""]*3
        #Where generated_templates[0] are mode3 capable signs, [1] are mode 1 capable and [2] are mode0 only

        self.console_display_text = "RM: %s %s" % (template_items["$rn"], template_items["$dest"][0])

        # Set RN alignment, this defaults to Left handed if not configured
        alignment = self.config_dict.get("TEMPLATES_rn_just", "left")
        if alignment == "right":
            align = r"\rh"
        else:
            align = ""

        # Obtain the RN and set the RN flag
        route_number = str(template_items["$rn"])
        if len(route_number) > 0:
            rn_flag = True
        else:
            rn_flag = False

        # Generate the appropriate colour commands
        colour = self.generate_colour_command(template_items["$fcol"], template_items["$bcol"])

        # Go through each destination in the list in turn
        for destination_text in template_items["$dest"]:
            #Set the font if enabled:
            if self.enable_font_replacement:
                descenders_present = self.check_for_descenders(destination_text)
            else:
                descenders_present = False

            # The index of the templates returns corresponds to a mode e.g. index0 == mode0
            # There are two template substiution modes, one is explict and the other is auto
            method = self.config_dict.get("TEMPLATES_replacement_method", "auto")
            if method == "auto":
                templates = self.substitute_descender_fonts_auto(self.enable_font_replacement, descenders_present)
            else:
                templates = self.substitute_descender_fonts_explicit(self.enable_font_replacement, descenders_present)
            # print(templates)

            split_text_stuff = self.split_text(destination_text)
            # print(split_text_stuff)
            two_line_flag = split_text_stuff[0]      #This is the two_line flag
            dest_0 = split_text_stuff[1]        #This is the destination with delimiter removed, so single line
            dest_1 = split_text_stuff[2]      #This is the top line of a two line destination
            dest_2 = split_text_stuff[3]      #This is the bottom line of a two line destination

            # Substitute template values, as this generates all pages, lists are appended to hence some are +=
            if rn_flag:
                generated_templates_col[0] = self.substituter(templates[4], [("$rn", route_number)])
                generated_templates_col[1] += self.substituter(templates[1], [("$rn", route_number), ("$dest", dest_0)])

                if two_line_flag:
                    generated_templates_col[2] += self.substituter(templates[3],
                                                   [("$rn", route_number), ("$dest_1", dest_1), ("$dest_2", dest_2)])
                else:
                    generated_templates_col[2] += self.substituter(templates[1], [("$rn", route_number), ("$dest", dest_0)])
            else:
                generated_templates_col[0] = self.substituter(templates[0], [("$rn", " ")])
                generated_templates_col[1] += self.substituter(templates[0], [("$rn", dest_0)])
                if two_line_flag:
                    generated_templates_col[2] += self.substituter(templates[2], [("$dest_1", dest_1), ("$dest_2", dest_2)])
                else:
                    generated_templates_col[2] += self.substituter(templates[0], [("$rn", dest_0)])


        # Sort out colour and alignment and fill the appropriate lists.
        for idx, gen_temp in enumerate(generated_templates_col):
            generated_templates_mono[idx] = generated_templates_col[idx] \
                .replace("$al", align) \
                .replace("$col", "")
            generated_templates_col[idx] = generated_templates_col[idx]\
                .replace("$al", align)\
                .replace("$col", colour)

        return generated_templates_mono, generated_templates_col

    """
    ###################################################################################################################
    Main
    """

    def process_display_text(self, data_dict, sign_dict):
        """
        This invokes the routine to generate all the templates for a given data_dict with the addition that it also
        selects the appropriate bit of data for a given sign according to its resolution.  A bit rudimentary but has
        done the trick so far.

        This is for external signs only.

        :param data_dict: dictionary of display data (see top of module)
        :param sign_dict: dictionary of sign information where each key (signX) contains another dict which has the
        sign resolution and colour panel resolution
        :return: a list containing data where each index is equivalent to the sign address, contains data appropriate
        for that particular sign

        Should there be no data then it returns a list of ".", likewise when there is no info for a given sign in the
        sign_dict, then it will be given "."
        
        There is now the possiblity of overriding the delimiter in cases there the same message needs to be shown on
        normal signs and RN signs.  The message should contain a delimiter, and the flag to override set to true.
        For RN signs, the delimiter will be respected and the message will be shown on two lines.
        For normal signs, the delimiter will be ignored and the message will be shown on one line
        Note that this only applies to very specific cases where the application higher up decides to include this flag
        """
        if data_dict == None:
            return ["."] * 14

        sign_data = []
        filled_templates_mono, filled_templates_col = self.generate_templates(data_dict)
        override_delimiter = data_dict.get("override_delimiter", False)

        # console_display_text = "%s %s" % (template_items["$rn"], "_temp_")

        for sign in sign_dict:
            if sign_dict[sign] != None:
                # print(sign_dict)

                sign_resolution = sign_dict[sign]["resolution"]
                col_resolution = sign_dict[sign]["colour_panel"]
                sign_w, sign_h = sign_resolution.split("x")
                # print(sign, sign_w, sign_h)

                if col_resolution == None:
                    if int(sign_h) < 11:
                        # Single line signs: Mode 0,1
                        temp_sign_data = filled_templates_mono[1]

                    elif int(sign_w) < 41:
                        # Route number only: Mode 0
                        if override_delimiter:
                            temp_sign_data = filled_templates_mono[2]
                        else:
                            temp_sign_data = filled_templates_mono[0]
                    else:
                        # RN two line: Mode 0,1,2,3
                        if override_delimiter:
                            temp_sign_data = filled_templates_mono[1]
                        else:
                            temp_sign_data = filled_templates_mono[2]

                    temp_sign_data = temp_sign_data.replace("$rw", "")
                else:
                    if int(sign_h) < 11:
                        # Single line signs: Mode 0,1
                        temp_sign_data = filled_templates_col[1]

                    elif int(sign_w) < 41:
                        # Route number only: Mode 0
                        if override_delimiter:
                            temp_sign_data = filled_templates_col[2]
                        else:
                            temp_sign_data = filled_templates_col[0]
                    else:
                        # RN two line: Mode 0,1,2,3
                        if override_delimiter:
                            temp_sign_data = filled_templates_col[1]
                        else:
                            temp_sign_data = filled_templates_col[2]

                    rn_width = col_resolution.split("x")[0]
                    temp_sign_data = temp_sign_data.replace("$rw", r"\rw%s" % rn_width)

                sign_data.append(temp_sign_data)

            else:
                sign_data.append(".")

        # print(sign_data)
        return sign_data

    def process_internal_display_text(self, data_dict):
        """
        This routine was created assuming that the sign will be driven by the Onion it is hosting and not via a DG3
        Onion.  Whether this will be expanded to support the latter is not known at this point in time.

        This routine takes the display dictionary and converts it into a basicX template.  There is limited options for
        the internal sign in terms of displaying text, there is only the equivalent of a Mode0 or Mode2.  Either way
        it takes the basicX template in the config and replaces the elements.

        Note that internal displays do not support multipage.

        Currently only a basic grab that and return it.  Until i have a better idea of internal led requirements then
        just showing the stop is good enough for me  <- famous last words there

        Any preambles like "Next Stop:", "Volgende Halte:" etc should be added in the templates, although if they want
        paging between next stop and destination will need to think of a mechanism.

        :param data_dict: dictionary of display data (see top of module)
        :return: stopname
        """
        template_items = data_dict

        delimiter = self.config_dict.get("TEMPLATES_delimiter", r"/")
        route_number = str(template_items["$rn"])
        dest_data = data_dict["$dest"][0]  #Only grab the first element for "multipage" we can concatenate the messages

        split_text_stuff = self.split_text(dest_data)
        two_line = split_text_stuff[0]
        dest = dest_data
        dest_0 = split_text_stuff[1]
        dest_1 = split_text_stuff[2]
        dest_2 = split_text_stuff[3]

        if two_line:
            basicx_template = self.config_dict.get("TEMPLATES_int_dual", r"\=\0\s$dest_1\;\1\s$dest_2\;")

            generated_template = basicx_template.replace("$rn", route_number)\
                                                .replace("$dest_1", dest_1)\
                                                .replace("$dest_2", dest_2)
        else:
            basicx_template = self.config_dict.get("TEMPLATES_int_single", r"\-\s$dest\;")

            generated_template = basicx_template.replace("$rn", route_number)\
                                                .replace("$dest", dest_0)\

        return generated_template

if __name__ == "__main__":
    logging.basicConfig(level=logging.DEBUG)

    test_config_dict_explicit = {
        "TEMPLATES_delimiter": r"/",
        "TEMPLATES_rn_just": "left",
        "TEMPLATES_colour": r"{\mrn\fc$fore\bc$back\it0\ic0\oc0\ot1\osp1}",
        "TEMPLATES_mode0": r"{\mode0$col$fnt_rn{$rn}}",
        "TEMPLATES_mode1": r"{\mode1\pt30$al$col$rw$fnt_rn{$rn}\fs$fnt_dest{$dest}}",
        "TEMPLATES_mode2": r"{\mode2\pt30$fnt_dest_1{$dest_1}\fs$fnt_dest_1{$dest_2}}",
        "TEMPLATES_mode3": r"{\mode3\pt30$al$col$rw$fnt_rn{$rn}\fs$fnt_dest_1{$dest_1}\fs$fnt_dest_2{$dest_2}}",

        "TEMPLATES_enable_font_replacement": True,
        "TEMPLATES_replacement_method": "explicit",
        "TEMPLATES_font_template": r"{\*\font $font}",
        "TEMPLATES_rn_font": "slf_des",
        "TEMPLATES_single_line_font": "slf_des",
        "TEMPLATES_single_line_font_no_des": "slf_nodes",
        "TEMPLATES_double_line_font": "dlf_des",
        "TEMPLATES_double_line_font_no_des": "dlf_nodes",
    }

    test_config_dict_auto = {
        "TEMPLATES_delimiter": r"/",
        "TEMPLATES_rn_just": "left",
        "TEMPLATES_colour": r"{\mrn\fc$fore\bc$back\it0\ic0\oc0\ot1\osp1}",
        "TEMPLATES_mode0": r"{\mode0$col$sx_route{$rn}}",
        "TEMPLATES_mode1": r"{\mode1\pt30$al$col$rw{$rn}\fs$sx_route{$dest}}",
        "TEMPLATES_mode2": r"{\mode2\pt30$sx_route{$dest_1}\fs$sx_route{$dest_2}}",
        "TEMPLATES_mode3": r"{\mode3\pt30$al$col$rw{$rn}\fs$sx_route{$dest_1}\fs$sx_route{$dest_2}}",

        "TEMPLATES_enable_font_replacement": False,
        "TEMPLATES_replacement_method": "auto",
    }

    # test_data_dict = {
    #     "$bcol": "1,1,1",
    #     "$fcol": "2,2,2",
    #     "$rn": "12a",
    #     "$dest": ["Eastbourne/via Seaford", "Brighton"]
    # }

    test_data_dict = {
        "$bcol": "1,1,1",
        "$fcol": "2,2,2",
        "$rn": "",
        "$dest": ["Geen/Dienst"],
        "override_delimiter": True
    }
    
    dummy_sign = {
        "sign0": {
            "resolution": "160x19",
            "colour_panel": None
        }
    }

    tg = TemplateGenerator(test_config_dict_auto, ".")

    # templates = tg.generate_templates(test_data_dict)
    # for template in templates:
    #     print(template)

    template = tg.process_display_text(test_data_dict, dummy_sign)
    print(template)



