from textwrap import dedent
from textwrap import indent
from textwrap import TextWrapper

import numpy as np

LV_HEADER = '''\
/* Generated by bdf2lvgl (https://github.com/summivox/bdf2lvgl) */

#ifdef LV_LVGL_H_INCLUDE_SIMPLE
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif
'''

#
# Glyph bitmaps (glyph_bitmap)
#

LV_GLYPH_HEADER = '''\
static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
'''


def lv_glyph(ch, data: list[int]):
    return indent(
        dedent(f'''\
        /* U+{ord(ch):04X} {repr(ch)} */
        {' '.join(f'0x{x:02x},' for x in data)}

        '''), '    ')


LV_GLYPH_FOOTER = '};\n\n'

#
# Glyph descriptors (glyph_dsc)
#

LV_DSC_HEADER = '''\
static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
'''


def lv_dsc(index, adv_w, box_w, box_h, ofs_x, ofs_y, ch):
    return f'    {{.bitmap_index = {index}, .adv_w = {adv_w}, .box_w = {box_w}, .box_h = {box_h}, .ofs_x = {ofs_x}, .ofs_y = {ofs_y}}},  /* U+{ord(ch):04X} {repr(ch)} */\n'


LV_DSC_FOOTER = '};\n\n'

#
# Character range mappings (cmaps)
#

_wrapper = TextWrapper(width=100,
                       initial_indent='    ',
                       subsequent_indent='    ')


def lv_sparse_list(sparse_i, offset, data):
    data_str = '\n'.join(
        _wrapper.wrap(', '.join(str(x - offset) for x in data)))
    return f'static const uint16_t unicode_list_{sparse_i}[] = {{\n' + data_str + ',\n};\n\n'


LV_CMAPS_HEADER = 'static const lv_font_fmt_txt_cmap_t cmaps[] = {\n'


def lv_cmaps_dense(start, length, glyph_start):
    return indent(
        dedent(f'''\
        {{
            .range_start = {start}, .range_length = {length}, .glyph_id_start = {glyph_start},
            .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY
        }},
        '''), '    ')


def lv_cmaps_sparse(start, length, glyph_start, sparse_i, sparse_length):
    return indent(
        dedent(f'''\
        {{
            .range_start = {start}, .range_length = {length}, .glyph_id_start = {glyph_start},
            .unicode_list = unicode_list_{sparse_i}, .glyph_id_ofs_list = NULL, .list_length = {sparse_length}, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
        }},
        '''), '    ')


LV_CMAPS_FOOTER = '};\n\n'

#
# Putting everything together
#


def lv_font_dsc(name, cmap_num, line_height, base_line):
    return dedent(f'''\
        #if LVGL_VERSION_MAJOR == 8
        /*Store all the custom data of the font*/
        static  lv_font_fmt_txt_glyph_cache_t cache;
        #endif

        #if LVGL_VERSION_MAJOR >= 8
        static const lv_font_fmt_txt_dsc_t font_dsc = {{
        #else
        static lv_font_fmt_txt_dsc_t font_dsc = {{
        #endif
            .glyph_bitmap = glyph_bitmap,
            .glyph_dsc = glyph_dsc,
            .cmaps = cmaps,
            .kern_dsc = NULL,
            .kern_scale = 0,
            .cmap_num = {cmap_num},
            .bpp = 1,
            .kern_classes = 0,
            .bitmap_format = 0,
        #if LVGL_VERSION_MAJOR == 8
            .cache = &cache
        #endif
        }};

        /*Initialize a public general font descriptor*/
        #if LVGL_VERSION_MAJOR >= 8
        const lv_font_t {name} = {{
        #else
        lv_font_t {name} = {{
        #endif
            .get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt,    /*Function pointer to get glyph's data*/
            .get_glyph_bitmap = lv_font_get_bitmap_fmt_txt,    /*Function pointer to get glyph's bitmap*/
            .line_height = {line_height},          /*The maximum line height required by the font*/
            .base_line = {base_line},             /*Baseline measured from the bottom of the line*/
        #if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0)
            .subpx = LV_FONT_SUBPX_NONE,
        #endif
        #if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8
            .underline_position = 0,
            .underline_thickness = 0,
        #endif
            .dsc = &font_dsc,          /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */
        #if LV_VERSION_CHECK(8, 2, 0) || LVGL_VERSION_MAJOR >= 9
            .fallback = NULL,
        #endif
            .user_data = NULL
        }};
        ''')


def print_lv_bitmap(a: list[int], w: int):
    ''' Prints LVGL compact bitmap with bounding box width `w` '''
    bits = ''.join(list(map(lambda x: ''.join(list((f'{x:08b}'))),
                            a))).replace('0', '.').replace('1', '#')
    print('\n'.join(bits[i:i + w] for i in range(0, len(bits), w)))


def gen_lv_font(parsed):
    font_name = parsed['name']
    result = LV_HEADER

    result += dedent(f'''\
    #ifndef {font_name.upper()}
    #define {font_name.upper()} 1
    #endif

    #if {font_name.upper()}\n
    ''')

    result += LV_GLYPH_HEADER
    for bitmap, codepoint in zip(parsed['bitmaps'], parsed['codepoints']):
        result += lv_glyph(chr(codepoint), bitmap)
    result += LV_GLYPH_FOOTER

    bitmap_index = np.cumsum([0, *map(len, parsed['bitmaps'])])

    result += LV_DSC_HEADER
    result += lv_dsc(index=0,
                     adv_w=0,
                     box_w=0,
                     box_h=0,
                     ofs_x=0,
                     ofs_y=0,
                     ch='\0')
    for index, (adv, bbW, bbH, bbX,
                bbY), codepoint in zip(bitmap_index, parsed['bbs'],
                                       parsed['codepoints']):
        result += lv_dsc(
            index=index,
            adv_w=adv * 16,  # NOTE: watch out!
            box_w=bbW,
            box_h=bbH,
            ofs_x=bbX,
            ofs_y=bbY,
            ch=chr(codepoint))
    result += LV_DSC_FOOTER

    for i, (start, length, glyph_start,
            sparse_list) in enumerate(parsed['cp_parts']):
        if not sparse_list:
            continue
        result += lv_sparse_list(i, start, sparse_list)

    result += LV_CMAPS_HEADER
    for i, (start, length, glyph_start,
            sparse_list) in enumerate(parsed['cp_parts']):
        if sparse_list:
            result += lv_cmaps_sparse(
                start=start,
                length=length,
                glyph_start=glyph_start,
                sparse_i=i,
                sparse_length=len(sparse_list),
            )
        else:
            result += lv_cmaps_dense(
                start=start,
                length=length,
                glyph_start=glyph_start,
            )
    result += LV_CMAPS_FOOTER

    result += lv_font_dsc(
        name=font_name,
        cmap_num=len(parsed['cp_parts']),
        line_height=parsed['height'],
        base_line=parsed['descent'],
    )

    result += f'#endif /* {font_name.upper()} */\n'

    return result
