
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

class SingleByte:
    buffer_size = 128
    var_name_prefix = 'lxb_encoding_single_index_'
    hash_name_prefix = 'lxb_encoding_single_hash_'
    flat_index_typename = 'lxb_encoding_single_index_t'
    const_prefix = "LXB_ENCODING_"

    def __init__(self, dir_path, temp_file_c, save_to, silent = False):
        if not os.path.isdir(dir_path):
            raise Exception('Directory "{}" not exit'.format(dir_path))

        self.dir_path = dir_path
        self.silent = silent
        self.temp_file_c = temp_file_c
        self.save_to = save_to

    def make(self):
        buf = []
        externs = []
        hash_externs = []
        hash_buf = []
        hash_sizes = []
        dir_path = self.dir_path

        for f in sorted(os.listdir(dir_path)):
            f_path = os.path.join(dir_path, f)
            if not os.path.isfile(f_path) or f[0] == '.':
                continue

            print('File: {}'.format(f_path))

            values = {}
            fo = open(f_path, "rb")

            for lineno, line in enumerate(fo):
                line = line.rstrip()
                if not line or line[:1] == b'#':
                    continue

                entries = line.split(b'\t')
                captions = entries[-1].split(b' ', maxsplit=1)

                if not captions[0]:
                    raise Exception('Failed to get chars variant on line {}'.format(lineno))

                entries.append(captions[0])
                entries.append(captions[1] if captions[1] else b'')

                entries[2] = entries[3]
                entries[3] = len(entries[2])

                values[ int(entries[0].decode('utf-8')) ] = entries

            flat_name = self.make_name(f)
            res = self.make_flat_index(flat_name, values)
            hash_index = self.make_hash_index(flat_name, values)

            buf.append(''.join(res))
            externs.append('{};'.format(self.make_extern_name(flat_name)))
            hash_buf.append(''.join(hash_index[0]))
            hash_sizes.append(hash_index[1])
            hash_externs.append(hash_index[2])

            const_name = "{}{}".format(self.const_prefix, flat_name.upper())
            self.save_test(self.make_var_name(flat_name), const_name, flat_name)

            fo.close()

        externs.append('')
        externs += hash_externs

        return [buf, externs, hash_buf, hash_sizes]

    def make_name(self, filename):
        name = re.sub("[^a-zA-Z0-9]", "_", filename)
        name = re.sub("^index_", "", name)
        name = re.sub("_txt$", "", name)

        return name

    def make_var_name(self, name):
        return '{}{}'.format(self.var_name_prefix, name)

    def make_hash_name(self, name):
        return '{}{}'.format(self.hash_name_prefix, name)

    def make_extern_name(self, name):
        var_name = self.make_var_name(name) + '[{}]'.format(self.buffer_size)

        return 'extern const {} {}'.format(self.flat_index_typename, var_name)

    def make_flat_index(self, name, values):
        res = LXB.Res(self.flat_index_typename, 
                self.make_var_name(name) + '[{}]'.format(self.buffer_size), False, None, '')

        for idx in range(0, self.buffer_size):
            if idx in values:
                entries = values[idx]

                res.append('{{(lxb_char_t *) "{}", {}, {}}}'.format(toHex(entries[2].decode('utf-8')), entries[3], entries[1].decode('utf-8')))
                res.append('/* {} */'.format(entries[4].decode('utf-8')), is_comment = True)
            else:
                res.append('{NULL, 0, LXB_ENCODING_DECODE_ERROR}')
                res.append('/* Not defined */', is_comment = True)

        buf = res.create()

        return buf

    def make_hash_index(self, name, values):
        name = self.make_hash_name(name)

        hash_key = LXB.HashKey(412, name)

        for idx in range(0, self.buffer_size):
            if idx in values:
                entries = values[idx]
                key_id = entries[1].decode('utf-8')

                hash_key.append(key_id, '(void *) {}'.format(idx + 0x80))

        return hash_key.create(rate = 1)

    def save_res(self, temp_file_h, save_to_h, temp_file_c, save_to_c):
        res = self.make()

        lxb_temp = LXB.Temp(temp_file_h, save_to_h)
        lxb_temp.pattern_append("%%EXTERNS%%", '\n'.join(res[1]))
        lxb_temp.pattern_append("%%SIZES%%", '\n'.join(res[3]))
        lxb_temp.build()
        lxb_temp.save()

        lxb_temp = LXB.Temp(temp_file_c, save_to_c)
        lxb_temp.pattern_append("%%INDEX%%", '\n\n'.join(res[0]))
        lxb_temp.pattern_append("%%HASH%%", '\n\n'.join(res[2]))
        lxb_temp.build()
        lxb_temp.save()

    def save_test(self, data, const_name, name):
        save_to_c = os.path.join(self.save_to, name + ".c")

        lxb_temp = LXB.Temp(self.temp_file_c, save_to_c)
        lxb_temp.pattern_append("%%DATA%%", data)
        lxb_temp.pattern_append("%%CONST_NAME%%", const_name)
        lxb_temp.pattern_append("%%NAME%%", name)
        lxb_temp.build()
        lxb_temp.save()

def toHex(s):
    lst = []

    for ch in bytes(s, 'utf-8'):
        hv = hex(ch).replace('0x', '\\\\x')
        lst.append(hv)

    return ''.join(lst)

if __name__ == "__main__":

    sb = SingleByte("single-byte", "tmp/single_byte_test.c",
                    "../../../test/lexbor/encoding/single")

    sb.save_res("tmp/single.h", "../../../source/lexbor/encoding/single.h",
                "tmp/single.c", "../../../source/lexbor/encoding/single.c")

    # yn = input("Print encode declarations? (y - yes, n - no): ")
    # if yn.lower() == 'y':
    #     print('asd')
