import re
import sys

lv_widgets_file = "../mapping/lv_funcs.h"
lv_module_file = "../mapping/lv_enum.h"

out_prefix = "../generate/"
lvgl_prefix = "../generate/"
doc_prefix = "../generate/"

be_lv_defines = "be_lv_defines.h"
be_lv_c_mapping = "be_lv_c_mapping.h"
be_lv_widgets_libs = "be_lvgl_widgets_lib.c"
be_lv_lvgl_module = "be_lvgl_module.c"
be_lv_lvgl_doc = "LVGL_API_Reference.md"


lv = {}
lv0 = []        # function in lvlg module
lv_module = []
lv_cb_types = ['lv_group_focus_cb', 'lv_event_cb', 'lv_timer_cb',
               'lv_constructor_cb',                 # 'constructor_cb', addition to LVGL8, also works for 'destructor_cb'
               ]
# list of callback types that will need each a separate C callback

# For LVGL8, need to add synthetic lv_style, lv_font, lv_color, lv_theme
lv['style'] = []
lv['font'] = []
lv['color'] = []
lv['theme'] = []

# standard widgets
lv_widgets = ['obj',
              'arc', 'bar', 'button', 'buttonmatrix', 'canvas', 'checkbox',
              'dropdown', 'image', 'label', 'line', 'roller', 'slider',
              'switch', 'table', 'textarea',
              # added in LVGL 9
              'spangroup', 'span',
              'scale_section', 'scale',   # 'scale_section' needs to be before 'scale' to capture more selective first
              ]
lv_widgets_no_class = ['span', 'scale_section']      # widgets that don't have a lv_obj class
# extra widgets
lv_widgets = lv_widgets + [ 'chart', 'imagebutton', 'led', 'msgbox', 'spinbox', 'spinner', 'keyboard', 'tabview', 'tileview' , 'list',
                            'animimg', 'calendar', 'menu']

# add qrcode
lv_widgets = lv_widgets + [ 'qrcode' ]

lv_prefix = ['group', 'style', 'indev', 'display', 'timer', 'anim', 'event', 'span'] + lv_widgets

# define here widget inheritance because it's hard to deduce from source
lv_widget_inheritance = {
  "animimage": "image",
  "calendar": "buttonmatrix",
  "keyboard": "buttonmatrix",
  "list_button": "button",
  "list_text": "label",
  "spinbox": "textarea",
  "spinner": "arc",             # lv_spinner is a subclass of lv_arc
  "canvas": "image",
  "roller_label": "label",
  "animimg": "image",
  "span": None,
}

# contains any custom attribute we need to add to a widget
lv_widget_custom_ptr = {
  # "spinner": {
  #   "_arc_anim_start_angle": "comptr(&arc_anim_start_angle)",
  #   "_arc_anim_end_angle": "comptr(&arc_anim_end_angle)",
  # }
}


compress_lv_type = True     # if True, convert `lv.lv_<type>` to `lv.<type>` to reduce the size of mapping

class lvgl_function:
  def __init__(self, c_func_name, c_ret_type, c_argc, c_args_doc_list, orig_func_name, be_name):
    # change "lv.lv_" into "lv."
    c_args_doc = ', '.join(c_args_doc_list)
    if compress_lv_type:
      c_argc = re.sub(r"lv\.lv_", "lv.", c_argc)
      c_args_doc = re.sub(r"lv\.lv_", "lv.", c_args_doc)
    self.c_args_doc = c_args_doc

    self.c_func_name = c_func_name
    self.orig_func_name = orig_func_name
    self.be_name = be_name
    if c_ret_type == '.':   c_ret_type = 'c'        # if return type is '.', return a comptr
    self.c_argc = c_argc
    if len(c_ret_type) > 1:
      if compress_lv_type:
        c_ret_type = "lv." + re.sub(r"^lv_", "", c_ret_type)
      else:
        c_ret_type = "lv." + c_ret_type
    self.c_ret_type = c_ret_type
    self.c_ret_type_doc = type_mapper.return_type_to_doc(c_ret_type)

  # return a key/value pair: be_name, C string for this method or function
  def add_C_line_to_map(self, map):
    # if c_ret_type is an object, prefix with `lv.`
    be_ret_type = self.c_ret_type

    map[self.be_name] = f"  {{ \"{self.be_name}\", {{ (const void*) &{self.orig_func_name}, \"{be_ret_type}\", \"{self.c_argc}\" }} }},"

# enter some synonyms so that names in LVGL 8 are mapped to their LVGL 9 equivalent
synonym_functions = {
  # functions
  "scr_act": "screen_active",
  "set_zoom": "set_scale",
  "get_zoom": "get_scale",
  "set_angle": "set_rotation",
  "get_angle": "get_rotation",
  "clear_flag": "remove_flag",
  "clear_state": "remove_state",
  "set_style_img_opa": "set_style_image_opa",
  "set_style_img_recolor": "set_style_image_recolor",
  "set_style_img_recolor_opa": "set_style_image_recolor_opa",
  "set_bg_img_opa": "set_bg_image_opa",
  "set_bg_img_recolor": "set_bg_image_recolor",
  "set_bg_img_recolor_opa": "set_bg_image_recolor_opa",
  "set_bg_img_src": "set_bg_image_src",
  "set_bg_img_tiled": "set_bg_image_tiled",

  "del": "delete",
  "del_async": "delete_async",
  "clear_flag": "remove_flag",
  "clear_state": "remove_state",

  "set_disp": "set_display",
  "get_act": "active",
  "scr_act": "screen_active",
  "scr_load": "screen_load",
  "remove": "delete",
  "set_default": "set_default",
  "get_default": "get_default",
  "get_next": "get_next",
  "set_rotation": "set_rotation",
  "get_hor_res": "get_horizontal_resolution",
  "get_ver_res": "get_vertical_resolution",
  "get_physical_hor_res": "get_physical_horizontal_resolution",
  "get_physical_ver_res": "get_physical_vertical_resolution",
  "get_offset_x": "get_offset_x",
  "get_offset_y": "get_offset_y",
  "get_rotation": "get_rotation",
  "get_dpi": "get_dpi",
  "get_antialiasing": "get_antialiasing",
  "flush_ready": "flush_ready",
  "flush_is_last": "flush_is_last",
  "get_scr_act": "get_screen_active",
  "get_scr_prev": "get_screen_prev",
  "load_scr": "lv_screen_load",
  "get_layer_top": "get_layer_top",
  "get_layer_sys": "get_layer_sys",
  "send_event": "send_event",
  "set_theme": "set_theme",
  "get_theme": "get_theme",
  "get_inactive_time": "get_inactive_time",
  "trig_activity": "trigger_activity",
  "enable_invalidation": "enable_invalidation",
  "is_invalidation_enabled": "is_invalidation_enabled",

  "del_all": "delete_all",
  "set_ready_cb": "set_completed_cb",

  "get_size": "get_size",
  "get_width": "get_width",

  "set_btn_text": "set_button_text",
  "_btn_text": "get_button_text",
  "add_btn": "add_button",

  "get_tab_btns": "get_tab_bar",
  "get_tab_act": "get_tab_active",
  "set_act": "set_active",

  "get_tile_act": "get_tile_active",
  "set_tile_id": "set_tile_by_index",

  "set_visible_row_cnt": "set_visible_row_count",
  "get_option_cnt": "get_option_count",

  "set_col_cnt": "set_column_count",
  "set_row_cnt": "set_row_count",
  "get_col_cnt": "get_column_count",
  "get_row_cnt": "get_row_count",
  "set_col_width": "set_column_width",
  "get_col_width": "get_column_width",

  "get_option_cnt": "get_option_count",

  "get_child_cnt": "get_child_count",
  "get_disp": "get_display",
  "delete_anim_ready_cb": "delete_anim_completed_cb",

  "get_style_anim_time": "get_style_anim_duration",
  "get_style_img_opa": "get_style_image_opa",
  "get_style_img_recolor": "get_style_image_recolor",
  "get_style_img_recolor_filtered": "get_style_image_recolor_filtered",
  "get_style_img_recolor_opa": "get_style_image_recolor_opa",
  "get_style_shadow_ofs_x": "get_style_shadow_offset_x",
  "get_style_shadow_ofs_y": "get_style_shadow_offset_y",
  "get_style_transform_angle": "get_style_transform_rotation",

  "set_style_anim_time": "set_style_anim_duration",
  "set_style_img_opa": "set_style_image_opa",
  "set_style_img_recolor": "set_style_image_recolor",
  "set_style_img_recolor_opa": "set_style_image_recolor_opa",
  "set_style_shadow_ofs_x": "set_style_shadow_offset_x",
  "set_style_shadow_ofs_y": "set_style_shadow_offset_y",
  "set_style_transform_zoom": "set_style_transform_scale",
  "set_style_transform_angle": "set_style_transform_rotation",

  "set_anim_time": "set_anim_duration",
  "set_img_opa": "set_image_opa",
  "set_img_recolor": "set_image_recolor",
  "set_img_recolor_opa": "set_image_recolor_opa",
  "set_shadow_ofs_x": "set_shadow_offset_x",
  "set_shadow_ofs_y": "set_shadow_offset_y",
  "set_transform_angle": "set_transform_rotation",
  "set_transform_zoom": "set_transform_scale",

  "scr_load_anim": "screen_load_anim",
}

def get_synonyms(name):
  return [k for k,v in synonym_functions.items() if v == name]

# The following class takes a C++ type and returns all information about Berry type
class type_mapper_class:
  # detect a function definition all
  # Ex: 'void lv_obj_set_parent(lv_obj_t * obj, lv_obj_t * parent);'
  # Group 1: 'void'
  # Group 2: 'lv_obj_set_parent'
  # Group 3: 'lv_obj_t * obj, lv_obj_t * parent'
  parse_func_regex = re.compile(r"(.*?)\s(\w+)\((.*?)\)")
  
  # parse call argument type
  # Ex: 'const lv_obj_t * parent' -> 'const ', 'lv_obj_t', ' * ', 'parent'
  # Ex: 'bool auto_fit' -> '', 'bool', ' ', 'auto_fit'
  parse_arg_regex = re.compile(r"(\w+\s+)?(\w+)([\*\s]+)(\w+)(\[\])?")

  # the following types are skipped without warning, because it would be too complex to adapt (so we don't map any function using or returning these types)
  skipping_type = [
    "bvm *",                      # Berry
    "lv_global_t *",              # reading globals is not useful in Berry
    # "lv_event_dsc_t *",           # internal implementation, use functions instead
    "lv_draw_task_t *",           # skip low-level tasks for now
    "lv_draw_buf_t *",            # low-level
    "lv_calendar_date_t *",       # skip calendar for now
    "lv_vector_dsc_t",            # see later if we need this
    "lv_point_precise_t",         # see later if we need this
    "void **",                    # edge case of lv_animimg_get_src()
    "va_list",
    "lv_matrix_t *",
    "lv_event_list_t *",
    "lv_style_value_t *",
    "lv_fpoint_t *",
    "lv_draw_fill_dsc_t *",
    "lv_draw_mask_rect_dsc_t *",
    "lv_draw_triangle_dsc_t *",
    "lv_font_glyph_dsc_t *",
    "lv_buttonmatrix_ctrl_t []",
    "lv_group_edge_cb_t",
    "lv_obj_tree_walk_cb_t",
    "lv_display_flush_cb_t",
    "lv_display_flush_wait_cb_t",
    "lv_draw_glyph_dsc_t *",
    "lv_draw_unit_t *",
    "lv_draw_border_dsc_t *",
    "lv_draw_box_shadow_dsc_t *",
    "lv_anim_deleted_cb_t",
    "lv_timer_handler_resume_cb_t",
    "lv_theme_apply_cb_t",
    "lv_color32_t *",
    "lv_color16_t *",
    "lv_color_filter_cb_t",
    "lv_style_prop_t []",
    "lv_calendar_date_t []",
    "lv_indev_read_cb_t",
    "lv_vector_path_t *",
    "lv_vector_path_quality_t",
  ]

  return_types = {
    # "void": "",           # treated in code directly, it doesn't work well with regex since there is no variable name
    "bool": "b",
    "int": "i",
    "uint8_t": "i",
    "uint16_t": "i",
    "int16_t": "i",
    "uint32_t": "i",
    "int32_t": "i",
    "void *": ".",
    # "const void *": ".",
    "char *": "c",
    # "uint8_t *": "c",
    "size_t": "i",
    # "const char *": "s",
    "retchar *": "s",
    "constchar *": "s",       # special construct
    # "lv_obj_user_data_t": "i",
    "lv_result_t": "i",
    # "float": "f",

    "lv_coord_t": "i",
    "lv_opa_t": "i",
    "lv_state_t": "i",
    "lv_chart_axis_t":"i",
    "lv_point_t":"i",
    "lv_chart_type_t":"i",
    # "lv_layout_t":"i",
    "lv_color_hsv_t":"i",
    "lv_label_long_mode_t": "i",
    "lv_scrollbar_mode_t": "i",
    "lv_blend_mode_t": "i",
    "lv_grad_dir_t": "i",
    "lv_border_side_t": "i",
    "lv_align_t": "i",
    "lv_keyboard_mode_t": "i",
    "lv_indev_type_t": "i",
    "lv_indev_mode_t": "i",
    "lv_indev_state_t": "i",
    # "lv_event_t": "i",
    "lv_dir_t": "i",
    "lv_part_t": "i",
    "lv_base_dir_t": "i",
    "lv_text_decor_t": "i",
    "lv_text_align_t": "i",
    "lv_arc_mode_t": "i",
    "lv_bar_mode_t": "i",
    "lv_event_code_t": "i",
    "lv_obj_flag_t": "i",
    "lv_slider_mode_t": "i",
    "lv_scroll_snap_t": "i",
    "lv_style_value_t": "i",
    # "lv_image_src_t": "i",
    "lv_style_selector_t": "i",
    # "lv_palette_t": "i",
    "lv_style_prop_t": "i",
    "lv_chart_update_mode_t": "i",
    "lv_style_res_t": "i",
    # LVGL 9
    "lv_image_align_t": "i",
    "lv_text_flag_t": "i",
    "lv_display_rotation_t": "i",
    "lv_color_format_t": "i",
    "lv_value_precise_t": "i",
    "lv_color32_t": "i",
    "lv_scale_mode_t": "i",
    "lv_span_overflow_t": "i",
    "lv_span_mode_t": "i",
    # "lv_vector_path_t *": "c",    # treat as opaque pointer
    # "lv_vector_dsc_t *": "c",     # treat as opaque pointer
    "lv_point_t *": "c",          # treat as opaque pointer
    "lv_hit_test_info_t *": "c",  # treat as opaque pointer
    "lv_screen_load_anim_t": "i",
    "lv_display_render_mode_t": "i",
    # "lv_vector_gradient_spread_t": "i",
    "lv_cover_res_t": "i",
    # "lv_vector_path_quality_t": "i",
    # "lv_vector_blend_t": "i",
    # "lv_vector_fill_t": "i",
    # "lv_vector_stroke_cap_t": "i",
    # "lv_vector_stroke_join_t": "i",
    "lv_font_kerning_t": "i",
    "lv_menu_mode_header_t": "i",
    "lv_menu_mode_root_back_button_t": "i",
    "lv_point_precise_t []": "lv_point_arr",
    "lv_obj_point_transform_flag_t": "i",
    "lv_palette_t": "i",

    "int32_t *": "lv_int_arr",
    "int32_t []": "lv_int_arr",
    "uint32_t *": "lv_int_arr",
    # "float *": "lv_float_arr",
    # layouts
    "lv_flex_align_t": "i",
    "lv_flex_flow_t": "i",
    "lv_grid_align_t": "i",

    # lv_anim
    "lv_anim_t *": "lv_anim",
    "lv_anim_enable_t": "i",
    "lv_anim_exec_xcb_t": "c",
    "lv_anim_custom_exec_cb_t": "c",
    "lv_anim_get_value_cb_t": "c",
    "lv_anim_path_cb_t": "c",
    "lv_anim_completed_cb_t": "c",
    "lv_anim_start_cb_t": "c",
    # lv_imagebutton
    "lv_imagebutton_state_t": "i",
    # lv_buttonmatrix
    "lv_buttonmatrix_ctrl_t": "i",

    "lv_group_refocus_policy_t": "i",
    "lv_roller_mode_t": "i",
    "lv_table_cell_ctrl_t": "i",

    # arrays
    "constchar * []": "str_arr",
    # "char * []": "str_arr",
    # "lv_coord_t []": "lv_coord_arr",
    # "lv_point_t []": "lv_point_arr",

    # ctypes objects
    "lv_area_t *": "lv_area",
    "lv_obj_class_t *": "lv_obj_class",
    "lv_chart_series_t *": "lv_chart_series",
    "lv_chart_cursor_t *": "lv_chart_cursor",
    "lv_draw_label_dsc_t *": "lv_draw_label_dsc",
    "lv_draw_rect_dsc_t *": "lv_draw_rect_dsc",
    "lv_draw_line_dsc_t *": "lv_draw_line_dsc",
    "lv_draw_arc_dsc_t *": "lv_draw_arc_dsc",
    "lv_point_precise_t *": "lv_point_precise",
    "lv_draw_image_dsc_t *": "lv_draw_image_dsc",
    "lv_event_dsc_t *": "lv_event_dsc",

    "_lv_obj_t *": "lv_obj",
    "lv_obj_t *": "lv_obj",
    "lv_event_t *": "lv_event",
    "lv_color_t": "lv_color",
    "lv_style_t *": "lv_style",
    "lv_group_t *": "lv_group",
    "lv_font_t *": "lv_font",
    "lv_theme_t *": "lv_theme",
    "lv_display_t *": "lv_display",
    '_lv_display_t *': "lv_display",
    "lv_indev_t *": "lv_indev",
    "lv_point_t []": "lv_point_arr",
    "lv_span_t *": "lv_span",
    "lv_scale_section_t *": "lv_scale_section",  # treat as opaque pointer
    # "lv_image_header_t *": "lv_image_header",
    "lv_image_dsc_t *": "lv_image_dsc",
    "lv_ts_calibration_t *": "lv_ts_calibration",
    "lv_style_transition_dsc_t *": "lv_style_transition_dsc",
    "lv_layer_t *": "lv_layer",               # LVGL9
    # "_lv_draw_layer_ctx_t *": "lv_draw_layer_ctx",
    "lv_grad_dsc_t *": "lv_grad_dsc",
    "lv_color_filter_dsc_t *": "lv_color_filter_dsc",
    "lv_timer_t *": "lv_timer",
    # "_lv_timer_t *": "lv_timer",
    # "lv_coord_t *": "lv_coord_arr",
    # "char **": "lv_str_arr",           # treat as a simple pointer, decoding needs to be done at Berry level
    "constchar **": "c",      # treat as a simple pointer, decoding needs to be done at Berry level
    "void * []": "c",         # treat as a simple pointer, decoding needs to be done at Berry level

    # callbacks
    "lv_group_focus_cb_t": "lv_group_focus_cb",
    "lv_event_cb_t": "lv_event_cb",
    "lv_timer_cb_t": "lv_timer_cb",
  }

  lv_cb_types = ['lv_group_focus_cb', 'lv_event_cb', 'lv_timer_cb',
                'lv_constructor_cb',                # 'constructor_cb', addition to LVGL8, also works for 'destructor_cb'
                'lv_group_edge_cb',                 # new in LVGL9
                ]

  # how to transform a type used for Berry mapping into user-explicit
  return_types_for_doc = {
    "i": "int",
    "b": "bool",
    "s": "string",
    "c": "comptr",
    "C": "callback",
    ".": "\\<any\\>",
  }

  def return_type_to_doc(self, type):
    return type_mapper_class.return_types_for_doc.get(type, type)

  def __init__(self) -> None:
    # create a map to count each time a C type is converted to Berry, and catch those that are never used
    # also provide some stats
    self.return_types_count = { k:0 for k in type_mapper_class.return_types.keys() }
    self.unhandled_types = {}

  # convert a C return type to the string representation for the Berry parser
  # using return_types
  def c_convert_type(self, c_ctype, try_without_t):
    # try name first
    be_ret = type_mapper_class.return_types.get(c_ctype)
    if be_ret:
      self.return_types_count[c_ctype] += 1
      return be_ret
  
    if try_without_t:
      c_ctype_without_t = re.sub(r"^(\w+?)_t", "\\1", c_ctype)
      # print(f">>> ctype '{c_ctype}' '{c_ctype_without_t}'")
      be_ret = type_mapper_class.return_types.get(c_ctype_without_t)
      if be_ret:
        self.return_types_count[c_ctype_without_t] += 1
        return be_ret
    
    return None     # fallback
  
  def type_is_unhandled(self, t):
    self.unhandled_types[t] = self.unhandled_types.get(t, 0) + 1

  def dump_return_types_stats(self, dump_all):
    for k in sorted(self.unhandled_types, key=self.unhandled_types.get, reverse=True):
      print(f"> Unhandled type '{k}' ({self.unhandled_types[k]})", file=sys.stderr)
    if dump_all:
      for k in sorted(self.return_types_count, key=self.return_types_count.get, reverse=True):
        print(f"# '{k}': {self.return_types_count[k]}", file=sys.stderr)

    else:
      # dump only unused
      return_types_count_filtered = [k for k,v in self.return_types_count.items() if v == 0]
      for k in return_types_count_filtered:
        print(f"# mapping not used '{k}'", file=sys.stderr)

  def clean_c_line(self, l_raw):
    l_raw = re.sub(r'//.*$', '', l_raw)                  # remove trailing comments
    l_raw = re.sub(r'LV_ATTRIBUTE_FAST_MEM ', '', l_raw) # remove LV_ATTRIBUTE_FAST_MEM marker
    l_raw = re.sub(r'\s+', ' ', l_raw)                   # replace any multi-space with a single space
    l_raw = l_raw.strip(" \t\n\r")                      # remove leading or trailing spaces
    l_raw = re.sub(r'static ', '', l_raw)                # remove `static` qualifier
    l_raw = re.sub(r'inline ', '', l_raw)                # remove `inline` qualifier
    l_raw = re.sub(r'const\s+char\s*\*', 'constchar *', l_raw)
    l_raw = re.sub(r'^char\s*\*', 'retchar *', l_raw)    # special case for returning a char*
    l_raw = re.sub(r'const ', '', l_raw)
    l_raw = re.sub(r'struct ', '', l_raw)
    return l_raw

  def parse_c_line(self, l_raw):
    g = type_mapper_class.parse_func_regex.search(l_raw)
    if g:
      c_return_type = g.group(1)
      c_func_name = g.group(2)
      c_arg_types = g.group(3)
      return (c_return_type, c_func_name, c_arg_types)
    else:
      return (None, None, None)

  def get_widget_return_type(self, c_type):
    c_ret = self.c_convert_type(c_type, False)
    c_ctype_without_t = re.sub(r"_t$", "", c_type)
    if c_ret:
      pass      # done
    elif c_type == "void":
      c_ret = ""
    elif c_ctype_without_t in type_mapper_class.lv_cb_types:
      c_ret = "C"   # general callback, if not already captured by explicit type
    elif c_type not in type_mapper_class.skipping_type:
      print(f"  // Skipping unsupported return type '{c_type}'", file=sys.stderr)
      self.type_is_unhandled(c_type)
      c_ret = None

    # print(f">>> get_widget_return_type '{c_type}' -> '{c_ret}'", file=sys.stderr)
    return c_ret
  
  def get_widget_arg_type(self, c_return_type, c_func_name, c_arg_types):
    # convert arguments
    c_args = ""
    c_args_doc = []
    args_raw = [ x.strip(" \t\n\r") for x in c_arg_types.split(",") ]  # split by comma and strip
    # print(args_raw)
    for arg_raw in args_raw:

      # check if `void`
      if arg_raw == 'void':      # Special case for 'void' which can't be captured easily in regex
        break

      if arg_raw == '...':          # Special case for '...' which can't be captured easily in regex
        # '...'
        c_args += "[......]"  # allow 6 additional parameters by default
        c_args_doc.append("[\\<any\\>]")
        break
      
      # Ex: 'const lv_obj_t * parent' -> 'const ', 'lv_obj_t', ' * ', 'parent', ''
      # Ex: 'bool auto_fit' -> '', 'bool', ' ', 'auto_fit', ''
      # Ex: 'const lv_coord_t value[]' -> 'const', 'lv_coord_t', '', 'value', '[]'
      ga = type_mapper_class.parse_arg_regex.search(arg_raw)
      if ga:                      # parsing ok?
        # ga.group(1) is first modifier like `const`, we don't use them
        ga_type = ga.group(2)     # main type
        ga_ptr = ( ga.group(3).strip(" \t\n\r") == "*" )      # (bool) is it a pointer
        ga_2ptr = ( ga.group(3).strip(" \t\n\r") == "**" )    # (bool) is it a pointer to a pointer
        ga_name = ga.group(4)     # main type name
        ga_array = ga.group(5)    # is it an array `[]`
        # print(f"raw='{arg_raw}' {ga_type=} {ga_ptr=} {ga_name=} {ga_array=}")

        ga_full_type = ga_type    # rebuild a complete type with modifier
        if ga_ptr:    ga_full_type += " *"
        if ga_2ptr:   ga_full_type += " **"
        if ga_array:  ga_full_type += " []"

        be_type = self.c_convert_type(ga_full_type, True)

        if be_type == None:
          # no match, unsupported type
          if ga_full_type not in type_mapper_class.skipping_type:
            self.type_is_unhandled(ga_full_type)
          # print(f"  // Unsupported argument type '{arg_raw}'", file=sys.stderr)
          return (None, None)

        # if the type is a single letter, we just add it
        if len(be_type) == 1 and be_type != 'C':  # callbacks are different
          c_args += be_type
          c_args_doc.append(type_mapper.return_type_to_doc(be_type))
        else:
          if be_type.endswith("_cb"):
            if 'remove_' in c_func_name:    # if the call is to remove the cb, just treat as an 'anything' parameter
              c_args += "."
              c_args_doc.append("\\<any\\>")
            else:
              # it's a callback type, we encode it differently
              if be_type not in lv_cb_types:
                lv_cb_types.append(be_type)
              c_args += "^" + be_type + "^"
              c_args_doc.append('\\<closure\\>')
          else:
            # we have a high-level type that we treat as a class name, enclose in parenthesis
            c_args += "(" + "lv." + be_type + ")"
            c_args_doc.append("lv." + be_type)
      else:
        print(f"  // unable to parse arguments '{arg_raw}'", file=sys.stderr)
    
    # print(f">>> get_widget_arg_type '{c_arg_types}' -> '{c_args}'", file=sys.stderr)
    return (c_args, c_args_doc)

type_mapper = type_mapper_class()

def try_int(s):
  try:
    v = int(s)
    return v
  except ValueError:
    return None

# parse widgets files containing most function calls
with open(lv_widgets_file) as f:
  for l_raw in f:
    l_raw = type_mapper.clean_c_line(l_raw)
    if (len(l_raw) == 0): continue

    (c_return_type, c_func_name, c_arg_types) = type_mapper.parse_c_line(l_raw)
    if (c_return_type == None): continue

    c_ret = type_mapper.get_widget_return_type(c_return_type)
    if c_ret == None:   continue      # skip if nothing to return

    # convert arguments
    (c_args, c_args_doc_list) = type_mapper.get_widget_arg_type(c_return_type, c_func_name, c_arg_types)
    if c_args == None: continue      # skip if nothing to return

    # analyze function name and determine if it needs to be assigned to a specific class
    # Ex: c_func_name -> 'lv_obj_set_parent'
    if c_func_name.startswith("_"): continue            # skip low-level
    if c_func_name.startswith("lv_debug_"): continue    # skip debug

    be_func_name = ""

    if c_func_name == "lv_style_init":
      continue   # no need for init as it would collied with native init (and called behind the scene anyways)

    found = False
    for subtype in lv_prefix:
      if c_func_name.startswith("lv_" + subtype + "_"):
        be_name = re.sub("^lv_" + subtype + "_", '', c_func_name)
        be_func_name = "lvbe_" + subtype + "_" + be_name
        if subtype not in lv: lv[subtype] = []    # add entry
        is_right_type = c_args.startswith(f"(lv.lv_{subtype})")   # check if first arg matches class
        is_obj_arg1 = c_args.startswith("(lv.lv_obj)")            # or first arg is lv_obj
        is_group_create = (subtype == 'group') and (c_func_name == 'lv_group_create')
        if is_right_type or is_obj_arg1 or is_group_create:
          # remove first argument which is implicit
          c_args_doc_list = c_args_doc_list[1:]
          lv[subtype].append(lvgl_function(be_func_name, c_ret, c_args, c_args_doc_list, c_func_name, be_name))
          # add synonyms
          syn = get_synonyms(be_name)
          for n in syn: lv[subtype].append(lvgl_function(be_func_name, c_ret, c_args, c_args_doc_list, c_func_name, n))

          found = True
          break

    if found: continue
    # not found, we treat it as lv top level function
    be_name = re.sub("^lv_", '', c_func_name)
    lv0.append(lvgl_function(be_func_name, c_ret, c_args, c_args_doc_list, c_func_name, be_name))
    # add synonyms
    syn = get_synonyms(be_name)
    for n in syn: lv0.append(lvgl_function(be_func_name, c_ret, c_args, c_args_doc_list, c_func_name, n))

print("| callback types"+str(lv_cb_types), file=sys.stderr)

# parse module file containing numerical constants
with open(lv_module_file) as f:
  for l_raw in f:
    l_raw = l_raw.strip(" \t\n\r")    # remove leading or trailing spaces
    if l_raw.startswith("//"):
      lv_module.append( [ None, l_raw ] )   # if key in None then add comment line
    l_raw = re.sub(r'//.*$', '', l_raw) # remove trailing comments
    l_raw = re.sub(r'\s+', '', l_raw) # remove all spaces
    l_raw = re.sub(r',.*$', '', l_raw) # remove comma and anything after it
    if (len(l_raw) == 0): continue

    k_v = l_raw.split("=")
    if len(k_v) > 2:
      print(f"Error: cannot match {l_raw}")
      continue
    # extract the key name
    k = k_v[0]
    if k.startswith("_"):
      continue      # skip any label starting with '_'
    k = re.sub('^LV_', '', k) # remove remove any LV_ prefix
    v = None
    if len(k_v) == 2:   # value is included
      v = k_v[1]
      if k is None or v is None: continue   # we skip if key is void or value is void

    if not k.isidentifier():
      print(f"Error: {k} is not an identifier")
      continue

    lv_module.append( [k, v] )    # keep as string or None


# documentation in Markdown for `lv` module
sys.stdout = open(doc_prefix + be_lv_lvgl_doc, 'w')
print("""# LVGL Berry API Reference

[//]: # (**********************************************************************)
[//]: # (* Generated code, don't edit                                         *)
[//]: # (**********************************************************************)

""")

# Start with `lv` module
print("## module `lv`") 
print()
print("Method|Arguments|Return type|LVGL equivalent")
print(":---|:---|:---|:---")

for f in sorted(lv0, key=lambda x: x.be_name):
  print(f"{f.be_name}|{f.c_args_doc}|{f.c_ret_type_doc}|[{f.orig_func_name}](https://docs.lvgl.io/9.0/search.html?q={f.orig_func_name})")

print()

# Continue with core classes
print("## Core classes") 
print()

for subtype, flv in sorted(lv.items()):
  if subtype not in lv_widgets and len(flv) > 0:
    print(f"### class `lv.{subtype}`")
    print()
    print("Method|Arguments|Return type|LVGL equivalent")
    print(":---|:---|:---|:---")
    for f in sorted(flv, key=lambda x: x.be_name):
      if f.c_func_name.endswith("_create"):
        pass
        # skip create function that are handled in object constructor (no `create` method)
      else:
        print(f"{f.be_name}|{f.c_args_doc}|{f.c_ret_type_doc}|[{f.orig_func_name}](https://docs.lvgl.io/9.0/search.html?q={f.orig_func_name})")
    print()

# Continue with Widgets
print("## Widgets") 
print()
for subtype in lv_widgets:
  flv = lv.get(subtype)
  if flv:
    print(f"### widget `lv.{subtype}`")
    print()
    print("Method|Arguments|Return type|LVGL equivalent")
    print(":---|:---|:---|:---")
    for f in sorted(flv, key=lambda x: x.be_name):
      if f.c_func_name.endswith("_create"):
        pass
        # skip create function that are handled in object constructor (no `create` method)
      else:
        print(f"{f.be_name}|{f.c_args_doc}|{f.c_ret_type_doc}|[{f.orig_func_name}](https://docs.lvgl.io/9.0/search.html?q={f.orig_func_name})")
    print()


sys.stdout.close()
      
sys.stdout = open(out_prefix + be_lv_c_mapping, 'w')
print("""
/********************************************************************
 * Generated code, don't edit
  *******************************************************************/

#ifdef __cplusplus
extern "C" {
#endif

#include "be_ctypes.h"
#include "be_mapping.h"
""")

for subtype, flv in lv.items():
  print(f"/* `lv_{subtype}` methods */")
  if subtype in lv_widgets:
    print(f"#ifdef BE_LV_WIDGET_{subtype.upper()}")
  print(f"const be_ntv_func_def_t lv_{subtype}_func[] = {{")

  func_out = {} # used to sort output
  for f in sorted(flv, key=lambda x: x.be_name):

    if f.c_func_name.endswith("_create"):
      pass
      # skip create function that are handled in object constructor (no `create` method)
    else:
      f.add_C_line_to_map(func_out)

  for be_name in sorted(func_out):
    print(func_out[be_name])

  print(f"}};")
  if subtype in lv_widgets:
    print(f"#endif // BE_LV_WIDGET_{subtype.upper()}")
  print(f"")

# extern classes
for subtype in sorted(lv):
  print(f"extern const bclass be_class_lv_{subtype};");
print()

# print the global map of classes
print(f"""
// map of clases
const be_ntv_class_def_t lv_classes[] = {{""")

for subtype in sorted(lv):
  if subtype in lv_widgets:
    print(f"#ifdef BE_LV_WIDGET_{subtype.upper()}")
  print(f"  {{ \"lv_{subtype}\", &be_class_lv_{subtype}, lv_{subtype}_func, sizeof(lv_{subtype}_func) / sizeof(lv_{subtype}_func[0]) }},")
  if subtype in lv_widgets:
    print(f"#endif // BE_LV_WIDGET_{subtype.upper()}")

print(f"""}};
const size_t lv_classes_size = sizeof(lv_classes) / sizeof(lv_classes[0]);
""")

# previous generation calls

# keep only create
for subtype, flv in lv.items():
  print(f"  /* `lv_{subtype}` methods */")
  create_found = False        # does the method contains an explicit `_create()` method
  for f in sorted(flv, key=lambda x: x.be_name):
    if f.c_func_name.endswith("_create"):
      create_found = True
      c_ret_type = "+_p"  # constructor, init method does not return any value
      if subtype in lv_widgets:
        print(f"#ifdef BE_LV_WIDGET_{subtype.upper()}")
        print(f"  int be_ntv_lv_{subtype}_init(bvm *vm)       {{ return be_call_c_func(vm, (void*) &{f.orig_func_name}, \"+_p\", \"{f.c_argc}\"); }}")
        print(f"#endif // BE_LV_WIDGET_{subtype.upper()}")
      else:
        print(f"  int be_ntv_lv_{subtype}_init(bvm *vm)       {{ return be_call_c_func(vm, (void*) &{f.orig_func_name}, \"+_p\", \"{f.c_argc}\"); }}")

  if not create_found and subtype in lv_widgets:
    # there is no explicit create, add one
    print(f"#ifdef BE_LV_WIDGET_{subtype.upper()}")
    print(f"  int be_ntv_lv_{subtype}_init(bvm *vm)       {{ return be_call_c_func(vm, (void*) &{f.orig_func_name}, \"+_p\", \"{f.c_argc}\"); }}")
    print(f"#endif // BE_LV_WIDGET_{subtype.upper()}")


print("""
// create font either empty or from parameter on stack
int lvbe_font_create(bvm *vm)       { return be_call_c_func(vm, NULL, "+_p", ""); }
int lvbe_theme_create(bvm *vm)       { return be_call_c_func(vm, NULL, "+_p", ""); }
""")

print()
print("#ifdef __cplusplus")
print("} /* extern \"C\" */")
print("#endif")
print("/********************************************************************/")
sys.stdout.close()


sys.stdout = open(lvgl_prefix + be_lv_widgets_libs, 'w')
print("""
/********************************************************************
 * Generated code, don't edit
 *******************************************************************/
 
 /********************************************************************
 * Tasmota LVGL classes for widgets
 *******************************************************************/
#include "be_constobj.h"

#include "lvgl.h"

extern int lv0_init(bvm *vm);

extern int lco_init(bvm *vm);           // generic function
extern int lco_tostring(bvm *vm);       // generic function
extern int lco_toint(bvm *vm);          // generic function

extern int lv_x_member(bvm *vm);
extern int lv_x_tostring(bvm *vm);       // generic function

extern int lv_be_style_init(bvm *vm);
extern int lv_be_style_del(bvm *vm);
extern int lv_be_anim_init(bvm *vm);
extern int lv_x_tostring(bvm *vm);

BE_EXPORT_VARIABLE extern const bclass be_class_lv_obj;

extern int lvbe_font_create(bvm *vm);
extern int lvbe_theme_create(bvm *vm);

""")

# expose all extern definitions:
for subtype, flv in lv.items():
  print(f"""extern int be_ntv_lv_{subtype}_init(bvm *vm);""")

print()

# extern classes
for subtype in sorted(lv):
  print(f"extern const bclass be_class_lv_{subtype};");
print()

# Define specific classes for lv_obj
#
print("""
/********************************************************************
** Solidified class: lv_style
********************************************************************/
#include "be_fixed_be_class_lv_style.h"
/* @const_object_info_begin
class be_class_lv_style (scope: global, name: lv_style, strings: weak) {
    _p, var
    init, func(lv_be_style_init)
    del, func(lv_be_style_del)
    tostring, func(lv_x_tostring)
    member, func(lv_x_member)
}
@const_object_info_end */

/********************************************************************
** Solidified class: lv_obj
********************************************************************/
#include "be_fixed_be_class_lv_obj.h"
/* @const_object_info_begin
class be_class_lv_obj (scope: global, name: lv_obj, strings: weak) {
    _p, var
    _class, comptr(&lv_obj_class)
    init, func(be_ntv_lv_obj_init)
    tostring, func(lv_x_tostring)
    member, func(lv_x_member)
}
@const_object_info_end */

/********************************************************************
** Solidified class: lv_group
********************************************************************/
#include "be_fixed_be_class_lv_group.h"
/* @const_object_info_begin
class be_class_lv_group (scope: global, name: lv_group, strings: weak) {
    _p, var
    init, func(be_ntv_lv_group_init)
    tostring, func(lv_x_tostring)
    member, func(lv_x_member)
}
@const_object_info_end */

/********************************************************************
** Solidified class: lv_indev
********************************************************************/
#include "be_fixed_be_class_lv_indev.h"
/* @const_object_info_begin
class be_class_lv_indev (scope: global, name: lv_indev, strings: weak) {
    _p, var
    init, func(lv0_init)
    tostring, func(lv_x_tostring)
    member, func(lv_x_member)
}
@const_object_info_end */

/********************************************************************
** Solidified class: lv_display
********************************************************************/
#include "be_fixed_be_class_lv_display.h"
/* @const_object_info_begin
class be_class_lv_display (scope: global, name: lv_display, strings: weak) {
    _p, var
    init, func(lv0_init)
    tostring, func(lv_x_tostring)
    member, func(lv_x_member)
}
@const_object_info_end */

/********************************************************************
** Solidified class: lv_timer
********************************************************************/
#include "be_fixed_be_class_lv_timer.h"
/* @const_object_info_begin
class be_class_lv_timer (scope: global, name: lv_timer, strings: weak) {
    _p, var
    init, func(lv0_init)
    tostring, func(lv_x_tostring)
    member, func(lv_x_member)
}
@const_object_info_end */

/********************************************************************
** Solidified class: lv_anim
********************************************************************/
#include "be_fixed_be_class_lv_anim.h"
/* @const_object_info_begin
class be_class_lv_anim (scope: global, name: lv_anim, strings: weak) {
    _p, var
    init, func(lv_be_anim_init)
    tostring, func(lv_x_tostring)
    member, func(lv_x_member)
}
@const_object_info_end */

/********************************************************************
** Solidified class: lv_event
********************************************************************/
#include "be_fixed_be_class_lv_event.h"
/* @const_object_info_begin
class be_class_lv_event (scope: global, name: lv_event, strings: weak) {
    _p, var
    init, func(lv0_init)
    tostring, func(lv_x_tostring)
    member, func(lv_x_member)
}
@const_object_info_end */

/********************************************************************
** Solidified class: lv_font
********************************************************************/
#include "be_fixed_be_class_lv_font.h"
/* @const_object_info_begin
class be_class_lv_font (scope: global, name: lv_font, strings: weak) {
    _p, var
    init, func(lvbe_font_create)
    tostring, func(lv_x_tostring)
}
@const_object_info_end */

/********************************************************************
** Solidified class: lv_theme
********************************************************************/
#include "be_fixed_be_class_lv_theme.h"
/* @const_object_info_begin
class be_class_lv_theme (scope: global, name: lv_theme, strings: weak) {
    _p, var
    init, func(lvbe_theme_create)
    tostring, func(lv_x_tostring)
}
@const_object_info_end */

/********************************************************************
** Solidified class: lv_color
********************************************************************/
#include "be_fixed_be_class_lv_color.h"
/* @const_object_info_begin
class be_class_lv_color (scope: global, name: lv_color, strings: weak) {
    _p, var
    init, func(lco_init)
    tostring, func(lco_tostring)
    toint, func(lco_toint)
}
@const_object_info_end */

/* define extern function for arc_anim_start_angle and arc_anim_end_angle*/
extern void arc_anim_start_angle(void * obj, int32_t v);
extern void arc_anim_end_angle(void * obj, int32_t v);

""")


for subtype, flv in lv.items():
  if subtype == 'obj':  continue    # 'obj' has a special implementation
  # special version for widgets
  if subtype in lv_widgets:
    super_class = lv_widget_inheritance.get(subtype, "obj")    # get superclass, default to lv_obj
    print(f"""/********************************************************************
** Solidified class: lv_{subtype}
********************************************************************/
#include "be_fixed_be_class_lv_{subtype}.h"
/* @const_object_info_begin""")
    if super_class is not None:
      print(f"class be_class_lv_{subtype} (scope: global, name: lv_{subtype}, super: be_class_lv_{super_class}, strings: weak) {{")
    else:
      print(f"class be_class_lv_{subtype} (scope: global, name: lv_{subtype}, strings: weak) {{")
      print(f"    _p, var")
      print(f"    tostring, func(lv_x_tostring)")
      print(f"    member, func(lv_x_member)")
    print(f"    init, func(be_ntv_lv_{subtype}_init)")

    if subtype not in lv_widgets_no_class:
      print(f"    _class, comptr(&lv_{subtype}_class)")
    if subtype in lv_widget_custom_ptr:
      for k, v in lv_widget_custom_ptr[subtype].items():
        print(f"    {k}, {v}")

    print(f"""}}
@const_object_info_end */
""")

sys.stdout.close()


sys.stdout = open(lvgl_prefix + be_lv_lvgl_module, 'w')
print("/********************************************************************")
print(" * Generated code, don't edit")
print(" *******************************************************************/")

print("""/********************************************************************
 * LVGL Module
 *******************************************************************/
#include "be_constobj.h"

#include "lvgl.h"
#include "be_mapping.h"
#include "be_ctypes.h"
#include "lv_berry.h"
#include "lv_theme_haspmota.h"

// declare accessors for non-const ints
int32_t be_LV_LAYOUT_GRID(void) { return LV_LAYOUT_GRID; };              BE_VAR_CTYPE_DECLARE(be_LV_LAYOUT_GRID, "i");
int32_t be_LV_LAYOUT_FLEX(void) { return LV_LAYOUT_FLEX; };              BE_VAR_CTYPE_DECLARE(be_LV_LAYOUT_FLEX, "i");

extern int lv0_member(bvm *vm);     // resolve virtual members
extern int lv0_load_font(bvm *vm);

extern lv_ts_calibration_t * lv_get_ts_calibration(void);

static int lv_get_hor_res(void) {
  return lv_display_get_horizontal_resolution(NULL);
}
static int lv_get_ver_res(void) {
  return lv_display_get_vertical_resolution(NULL);
}

/* `lv` methods */
const be_ntv_func_def_t lv_func[] = {
""")

func_out = {} # used to sort output
for f in lv0:
  f.add_C_line_to_map(func_out)
  # c_func_name = f[0]
  # c_ret_type = f[1]
  # c_argc = f[2]
  # if c_argc is not None: c_argc = "\"" + c_argc + "\""
  # orig_func_name = f[3]
  # be_name = f[4]

  # # if c_ret_type is an object, prefix with `lv.`
  # # if len(c_ret_type) > 1: c_ret_type = "lv." + c_ret_type
  # if len(c_ret_type) > 1: c_ret_type = "lv." + c_ret_type

  # func_out[be_name] = f"  {{ \"{be_name}\", {{ (const void*) &{orig_func_name}, \"{c_ret_type}\", { c_argc if c_argc else 'nullptr'} }} }},"


  # # any synonyms?
  # f_synonyms = get_synonyms(be_name)
  # if f_synonyms:
  #   for new_be_name in f_synonyms:
  #       func_out[new_be_name] = f"  {{ \"{new_be_name}\", {{ (const void*) &{orig_func_name}, \"{c_ret_type}\", { c_argc if c_argc else 'nullptr'} }} }},"


for be_name in sorted(func_out):
  print(func_out[be_name])




  # // resolution
  # { "get_hor_res", (void*) &lv_get_hor_res, "i", "" },
  # { "get_ver_res", (void*) &lv_get_ver_res, "i", "" },

  # // layers
  # { "layer_sys", (void*) &lv_layer_sys, "lv_obj", "" },
  # { "layer_top", (void*) &lv_layer_top, "lv_obj", "" },

  # // screens
  # { "scr_act", (void*) &lv_scr_act, "lv_obj", "" },
  # { "scr_load", (void*) &lv_scr_load, "", "(lv_obj)" },
  # { "scr_load_anim", (void*) &lv_scr_load_anim, "", "(lv_obj)iiib" },
print("""
};
const size_t lv_func_size = sizeof(lv_func) / sizeof(lv_func[0]);

""")

print("""

const be_const_member_t lv0_constants[] = {
""")

lv_module2 = {}
for k_v in lv_module:
  (k,v) = k_v
  if k is not None:
    lv_module2[k] = v

# print the enums, symbols and functions
# Ex: { "ALIGN_BOTTOM_LEFT", LV_ALIGN_BOTTOM_LEFT },
#
# Encoding:
# 1. `COLOR_WHITE=0xFFFFFF` enum with explicit value
# 2. `LV_EVENT_ALL` enum with implicit value with same name resolved by C compiler
# 3. `$SYMBOL_OK="\xef\x80\x8c"` if starts with `$` then it's a string
# 4. `&seg7_font=lv0_load_seg7_font` if starts with `&` then it's a native function
#
# We need to sort ignoring the first char if it's not a letter
for k in sorted(lv_module2):
  v = lv_module2[k]
  # check the type of the value
  # if first char is '"' then it's a string and prefix with '$'
  # if first char is '&' then it's a function and prefix with '&'
  # if no value, then it's an enum, prefix with `LV_`
  # otherwise it's an int, leave if unchanged
  if v is not None:
    v_prefix = ""
    v_macro = "be_cconst_int"
    if v[0] == '"': v_prefix = "$"; v_macro = "be_cconst_string"
    if v[0] == '&': v_prefix = "&"; v_macro = "be_cconst_ptr"
    if v[0] == '@': v_prefix = "@"; v_macro = "be_cconst_ptr"; v = "&" + v[1:]
    if v[0] == '>': v_prefix = ">"; v_macro = "be_ctype"; v = v[1:]
    print(f"    {{ \"{v_prefix}{k}\", {v_macro}({v}) }},")
  else:
    print(f"    {{ \"{k}\", be_cconst_int(LV_{k}) }},")

print("""
};

const size_t lv0_constants_size = sizeof(lv0_constants)/sizeof(lv0_constants[0]);

#include "../src/solidify/solidified_lv.h"

/* @const_object_info_begin
module lv (scope: global, file: lv) {
    init, closure(lv_module_init_closure)
    member, func(lv0_member)
}
@const_object_info_end */
#include "be_fixed_lv.h"
""")

print("/********************************************************************/")
sys.stdout.close()

type_mapper.dump_return_types_stats(False)    # True would dump all stats, False only unused
