
import sys, re, os

# Find and append run script run dir to module search path
ABS_PATH = os.path.dirname(os.path.abspath(__file__))
sys.path.append("{}/../../lexbor/".format(ABS_PATH))

import LXB

lxb_css_selectors_state_function_end = "lxb_css_selectors_state_function_end"

pseudo_classes = [
    # https://drafts.csswg.org/selectors-4/#overview
    "warning", "any-link", "link", "visited", "local-link", "target",
    "target-within", "scope", "current", "past", "future", "active",
    "hover", "focus", "focus-within", "focus-visible", "enabled", 
    "disabled", "read-write", "read-only", "placeholder-shown",
    "default", "checked", "indeterminate", "valid", "invalid",
    "in-range", "out-of-range", "required", "optional", "blank",
    "user-invalid", "root", "empty", "first-child", "last-child",
    "only-child", "first-of-type", "last-of-type", "only-of-type",

    # https://fullscreen.spec.whatwg.org/#index
    "fullscreen"
]

pseudo_class_functions_dict = {
    # [callback end function, can empty, combinator, forgiving, comma]
    #
    # https://drafts.csswg.org/selectors-4/#overview
    "not": [lxb_css_selectors_state_function_end, False, "CLOSE", False, True],
    "is": ["lxb_css_selectors_state_function_forgiving", False, "CLOSE", True, True],
    "where": ["lxb_css_selectors_state_function_forgiving", False, "CLOSE", True, True],
    "has": ["lxb_css_selectors_state_function_forgiving_relative", False, "DESCENDANT", True, True],
    "dir": [lxb_css_selectors_state_function_end, False, "CLOSE", False, False],
    "lang": [lxb_css_selectors_state_function_end, False, "CLOSE", False, False],
    "current": [lxb_css_selectors_state_function_end, False, "CLOSE", False, True],
    "nth-child": [lxb_css_selectors_state_function_end, False, "CLOSE", False, True],
    "nth-last-child": [lxb_css_selectors_state_function_end, False, "CLOSE", False, True],
    "nth-of-type": [lxb_css_selectors_state_function_end, False, "CLOSE", False, False],
    "nth-last-of-type": [lxb_css_selectors_state_function_end, False, "CLOSE", False, False],
    "nth-col": [lxb_css_selectors_state_function_end, False, "CLOSE", False, False],
    "nth-last-col": [lxb_css_selectors_state_function_end, False, "CLOSE", False, False],
    "lexbor-contains": [lxb_css_selectors_state_function_end, False, "CLOSE", False, False]
}

pseudo_class_functions = list(pseudo_class_functions_dict.keys())

pseudo_elements = [
    # https://drafts.csswg.org/css-pseudo-4/#index
    "after", "before", "first-letter", "first-line", "grammar-error",
    "inactive-selection", "marker", "placeholder", "selection",
    "spelling-error", "target-text",

    # https://fullscreen.spec.whatwg.org/#index
    "backdrop"
]

pseudo_element_functions_dict = {
}

pseudo_element_functions = list(pseudo_element_functions_dict.keys())

pseudo_classes.sort()
pseudo_classes.insert(0, "_undef")

pseudo_class_functions.sort()
pseudo_class_functions.insert(0, "_undef")

pseudo_elements.sort()
pseudo_elements.insert(0, "_undef")

pseudo_element_functions.sort()
pseudo_element_functions.insert(0, "_undef")

class Pseudo:
    prefix = "LXB_CSS_SELECTOR_"

    def make(self, pseude_name, arr, names = None):
        idx = 0
        state = ""
        entries = []
        type_data_name = ""
        state_declaration = []
        state_bodies = []
        data_name = "lxb_css_selectors_pseudo_data_{}".format(pseude_name)

        make_func = names != None

        if make_func == True:
            type_data_name = "lxb_css_selectors_pseudo_data_func_t"
        else:
            type_data_name = "lxb_css_selectors_pseudo_data_t"

        res = LXB.Res(type_data_name, data_name, False,
                      self.make_enum_name(pseude_name, "_LAST_ENTRY"))

        frmt_enum = LXB.FormatEnum("{}{}_id_t".format(self.prefix.lower(),
                                                      self.make_name(pseude_name)))

        for name in arr:
            enum_name = self.make_enum_name(pseude_name, name)

            frmt_enum.append(enum_name, "0x{0:04x}".format(idx))
            idx += 1

            if make_func == True:
                state = "lxb_css_selectors_state_{}_{}".format(self.make_name(pseude_name),
                                                               self.make_name(name))
                state_declaration.append(self.make_state_declaration(state))
                state_bodies.append(self.make_state_body(state))

                success = lxb_css_selectors_state_function_end
                can_empty = False
                combinator = "CLOSE"

                if name in names:
                    success = names[name][0]
                    can_empty = names[name][1]
                    combinator = names[name][2]
                    forgiving = str(names[name][3]).lower()
                    comma = str(names[name][4]).lower()

                if name == "_undef":
                    res.append("{{(lxb_char_t *) \"#undef\", 6, {},\n     {}, {},\n     "
                               "{{.value = {},\n      .cb.failed = {}, .cb.end = {}}}, {}, {}}}".format(enum_name, str(can_empty).lower(),
                                        self.make_combinator_name(combinator),
                                        state, "lxb_css_state_failed", success, "false", "false"))
                    continue
                else:
                    res.append("{{(lxb_char_t *) \"{}\", {}, {},\n     {}, {},\n     "
                               "{{.value = {},\n      .cb.failed = {}, .cb.end = {}}}, {}, {}}}".format(name, len(name),
                                        enum_name, str(can_empty).lower(), self.make_combinator_name(combinator),
                                        state, "lxb_css_state_failed", success, forgiving, comma))
            else:
                if name == "_undef":
                    res.append("{{(lxb_char_t *) \"#undef\", 6, {}}}".format(enum_name))
                    continue
                else:
                    res.append("{{(lxb_char_t *) \"{}\", {}, {}}}".format(name, len(name),
                                                                          enum_name))

            entries.append({"key": name,
                            "value": "(void *) &{}[{}]".format(data_name,  enum_name)})

        frmt_enum.append(self.make_enum_name(pseude_name, "_LAST_ENTRY"),
                                             "0x{0:04x}".format(idx))

        shs = LXB.SHS(entries, 0, False)
        test = shs.make_test(5, 256)
        shs.table_size_set(test[0][2])

        result_enum = frmt_enum.build()
        result_res = res.create(rate = 1, is_const = True)
        result_sh = shs.create("lxb_css_selectors_{}_shs".format(pseude_name), rate = 1)

        print(self.shs_stat(shs))

        return [result_enum, result_res, result_sh, state_declaration, state_bodies]

    def make_combinator_name(self, name):
        name = re.sub(r"[^a-zA-Z0-9_]", "_", name)
        return "{}COMBINATOR_{}".format(self.prefix, name.upper())

    def make_enum_name(self, pseudo_name, name):
        name = re.sub(r"[^a-zA-Z0-9_]", "_", name)
        pseudo_name = re.sub(r"[^a-zA-Z0-9_]", "_", pseudo_name)
        return "{}{}_{}".format(self.prefix, pseudo_name.upper(), name.upper())

    def make_name(self, name):
        return re.sub("[^a-zA-Z0-9]", "_", name)

    def shs_stat(self, shs):
       return "Max deep {}; Used {} of {}".format(shs.max, shs.used, shs.table_size)

    def make_state_declaration(self, name):
        return ("bool\n{}(lxb_css_parser_t *parser,\n"
                "    lxb_css_syntax_token_t *token, void *ctx)").format(name)

    def make_state_body(self, name):
        return "{}\n{{\n    return true;\n}}".format(self.make_state_declaration(name))

    def save(self, const_temp_file, const_save_to, res_temp_file, res_save_to):
        res_class = ps.make("pseudo_class", pseudo_classes)
        res_class_function = ps.make("pseudo_class_function", pseudo_class_functions, pseudo_class_functions_dict)
        res_element = ps.make("pseudo_element", pseudo_elements)
        res_element_function = ps.make("pseudo_element_function", pseudo_element_functions, pseudo_element_functions_dict)

        # const
        lxb_temp = LXB.Temp(const_temp_file, const_save_to)

        lxb_temp.pattern_append("%%BODY%%",
                                "{}\n\n{}\n\n{}\n\n{}".format('\n'.join(res_class[0]),
                                                        '\n'.join(res_class_function[0]),
                                                        '\n'.join(res_element[0]),
                                                        '\n'.join(res_element_function[0])))

        lxb_temp.build()
        lxb_temp.save()

        print("Const saved to {}".format(const_save_to))

        # res
        lxb_temp = LXB.Temp(res_temp_file, res_save_to)

        lxb_temp.pattern_append("%%SHS_DATA%%",
                                "{}\n\n{}\n\n{}\n\n{}".format(''.join(res_class[1]),
                                                        ''.join(res_class_function[1]),
                                                        ''.join(res_element[1]),
                                                        ''.join(res_element_function[1])))
        lxb_temp.pattern_append("%%SHS%%",
                                "{}\n\n{}\n\n{}\n\n{}".format(''.join(res_class[2]),
                                                        ''.join(res_class_function[2]),
                                                        ''.join(res_element[2]),
                                                        ''.join(res_element_function[2])))

        lxb_temp.build()
        lxb_temp.save()

        print("Res saved to {}".format(res_save_to))
        print("Done")

        yn = input("Print declarations? (y - yes, n - no): ")
        if yn.lower() == 'y':
            print('LXB_API ', ';\n\nLXB_API '.join(res_class_function[3]), ';', sep='')
            print('LXB_API ', ';\n\nLXB_API '.join(res_element_function[3]), ';', sep='')
            print('\n\n'.join(res_class_function[4]))
            print('\n\n'.join(res_element_function[4]))

if __name__ == "__main__":
    ps = Pseudo()

    ps.save("tmp/const.h", "../../../../source/lexbor/css/selectors/pseudo_const.h",
            "tmp/res.h", "../../../../source/lexbor/css/selectors/pseudo_res.h")
