// Module for binding the u8g2 library
// Note: This file is intended to be shared between esp8266 and esp32 platform

// Do not use the code from u8g2 submodule and skip the complete source here
// if the u8g2 module is not selected.
// Reason: The whole u8g2 submodule code tree might not even exist in this case.
#include "user_modules.h"
#if defined(LUA_USE_MODULES_U8G2) || defined(ESP_PLATFORM)

#include "module.h"
#include "lauxlib.h"

#define U8X8_USE_PINS
#define U8X8_WITH_USER_PTR
#include "u8g2.h"
#include "u8x8_nodemcu_hal.h"

#include "u8g2_displays.h"
#include "u8g2_fonts.h"

#ifdef ESP_PLATFORM
// ESP32
#include "spi_common.h"

#include "sdkconfig.h"
#endif

#ifndef CONFIG_LUA_MODULE_U8G2
// ignore unused functions if u8g2 module will be skipped anyhow
#pragma GCC diagnostic ignored "-Wunused-function"
#endif


typedef struct {
  int font_ref;
  int host_ref;
  u8g2_nodemcu_t u8g2;
} u8g2_ud_t;

#define GET_U8G2() \
  u8g2_ud_t *ud = (u8g2_ud_t *)luaL_checkudata( L, 1, "u8g2.display" ); \
  u8g2_t *u8g2 = (u8g2_t *)(&(ud->u8g2));

static int lu8g2_clearBuffer( lua_State *L )
{
  GET_U8G2();

  u8g2_ClearBuffer( u8g2 );

  return 0;
}

static int lu8g2_drawBox( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int x = luaL_checkint( L, ++stack );
  int y = luaL_checkint( L, ++stack );
  int w = luaL_checkint( L, ++stack );
  int h = luaL_checkint( L, ++stack );

  u8g2_DrawBox( u8g2, x, y, w, h );

  return 0;
}

static int lu8g2_drawCircle( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int x0  = luaL_checkint( L, ++stack );
  int y0  = luaL_checkint( L, ++stack );
  int rad = luaL_checkint( L, ++stack );
  int opt = luaL_optint( L, ++stack, U8G2_DRAW_ALL );

  u8g2_DrawCircle( u8g2, x0, y0, rad, opt );

  return 0;
}

static int lu8g2_drawDisc( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int x0  = luaL_checkint( L, ++stack );
  int y0  = luaL_checkint( L, ++stack );
  int rad = luaL_checkint( L, ++stack );
  int opt = luaL_optint( L, ++stack, U8G2_DRAW_ALL );

  u8g2_DrawDisc( u8g2, x0, y0, rad, opt );

  return 0;
}

static int lu8g2_drawEllipse( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int x0  = luaL_checkint( L, ++stack );
  int y0  = luaL_checkint( L, ++stack );
  int rx  = luaL_checkint( L, ++stack );
  int ry  = luaL_checkint( L, ++stack );
  int opt = luaL_optint( L, ++stack, U8G2_DRAW_ALL );

  u8g2_DrawEllipse( u8g2, x0, y0, rx, ry, opt );

  return 0;
}

static int lu8g2_drawFilledEllipse( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int x0  = luaL_checkint( L, ++stack );
  int y0  = luaL_checkint( L, ++stack );
  int rx  = luaL_checkint( L, ++stack );
  int ry  = luaL_checkint( L, ++stack );
  int opt = luaL_optint( L, ++stack, U8G2_DRAW_ALL );

  u8g2_DrawFilledEllipse( u8g2, x0, y0, rx, ry, opt );

  return 0;
}

static int lu8g2_drawFrame( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int x = luaL_checkint( L, ++stack );
  int y = luaL_checkint( L, ++stack );
  int w = luaL_checkint( L, ++stack );
  int h = luaL_checkint( L, ++stack );

  u8g2_DrawFrame( u8g2, x, y, w, h );

  return 0;
}

static int lu8g2_drawGlyph( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int x   = luaL_checkint( L, ++stack );
  int y   = luaL_checkint( L, ++stack );
  int enc = luaL_checkint( L, ++stack );

  u8g2_DrawGlyph( u8g2, x, y, enc );

  return 0;
}

static int lu8g2_drawHLine( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int x = luaL_checkint( L, ++stack );
  int y = luaL_checkint( L, ++stack );
  int w = luaL_checkint( L, ++stack );

  u8g2_DrawHLine( u8g2, x, y, w );

  return 0;
}

static int lu8g2_drawLine( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int x0 = luaL_checkint( L, ++stack );
  int y0 = luaL_checkint( L, ++stack );
  int x1 = luaL_checkint( L, ++stack );
  int y1 = luaL_checkint( L, ++stack );

  u8g2_DrawLine( u8g2, x0, y0, x1, y1 );

  return 0;
}

static int lu8g2_drawPixel( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int x = luaL_checkint( L, ++stack );
  int y = luaL_checkint( L, ++stack );

  u8g2_DrawPixel( u8g2, x, y );

  return 0;
}

static int lu8g2_drawRBox( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int x = luaL_checkint( L, ++stack );
  int y = luaL_checkint( L, ++stack );
  int w = luaL_checkint( L, ++stack );
  int h = luaL_checkint( L, ++stack );
  int r = luaL_checkint( L, ++stack );

  u8g2_DrawRBox( u8g2, x, y, w, h, r );

  return 0;
}

static int lu8g2_drawRFrame( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int x = luaL_checkint( L, ++stack );
  int y = luaL_checkint( L, ++stack );
  int w = luaL_checkint( L, ++stack );
  int h = luaL_checkint( L, ++stack );
  int r = luaL_checkint( L, ++stack );

  u8g2_DrawRFrame( u8g2, x, y, w, h, r );

  return 0;
}

static int lu8g2_drawStr( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int x = luaL_checkint( L, ++stack );
  int y = luaL_checkint( L, ++stack );
  const char *str = luaL_checkstring( L, ++stack );

  u8g2_DrawStr( u8g2, x, y, str );

  return 0;
}

static int lu8g2_drawTriangle( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int x0 = luaL_checkint( L, ++stack );
  int y0 = luaL_checkint( L, ++stack );
  int x1 = luaL_checkint( L, ++stack );
  int y1 = luaL_checkint( L, ++stack );
  int x2 = luaL_checkint( L, ++stack );
  int y2 = luaL_checkint( L, ++stack );

  u8g2_DrawTriangle( u8g2, x0, y0, x1, y1, x2, y2 );

  return 0;
}

static int lu8g2_drawUTF8( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int x = luaL_checkint( L, ++stack );
  int y = luaL_checkint( L, ++stack );
  const char *str = luaL_checkstring( L, ++stack );

  u8g2_DrawUTF8( u8g2, x, y, str );

  return 0;
}

static int lu8g2_drawVLine( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int x = luaL_checkint( L, ++stack );
  int y = luaL_checkint( L, ++stack );
  int h = luaL_checkint( L, ++stack );

  u8g2_DrawVLine( u8g2, x, y, h );

  return 0;
}

static int lu8g2_drawXBM( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int x = luaL_checkint( L, ++stack );
  int y = luaL_checkint( L, ++stack );
  int w = luaL_checkint( L, ++stack );
  int h = luaL_checkint( L, ++stack );
  size_t len;
  const char *bitmap = luaL_checklstring( L, ++stack, &len );

  u8g2_DrawXBM( u8g2, x, y, w, h, (uint8_t *)bitmap );

  return 0;
}

static int lu8g2_getAscent( lua_State *L )
{
  GET_U8G2();

  lua_pushinteger( L, u8g2_GetAscent( u8g2 ) );
  return 1;
}

static int lu8g2_getDescent( lua_State *L )
{
  GET_U8G2();

  lua_pushinteger( L, u8g2_GetDescent( u8g2 ) );
  return 1;
}

static int lu8g2_getStrWidth( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  const char *s = luaL_checkstring( L, ++stack );

  lua_pushinteger( L, u8g2_GetStrWidth( u8g2, s ) );
  return 1;
}

static int lu8g2_getUTF8Width( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  const char *s = luaL_checkstring( L, ++stack );

  lua_pushinteger( L, u8g2_GetUTF8Width( u8g2, s ) );
  return 1;
}

static int lu8g2_sendBuffer( lua_State *L )
{
  GET_U8G2();

  u8g2_SendBuffer( u8g2 );

  return 0;
}

static int lu8g2_setBitmapMode( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int is_transparent = luaL_checkint( L, ++stack );

  u8g2_SetBitmapMode( u8g2, is_transparent );

  return 0;
}

static int lu8g2_setContrast( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int value = luaL_checkint( L, ++stack );

  u8g2_SetContrast( u8g2, value );

  return 0;
}

static int lu8g2_setDisplayRotation( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  const u8g2_cb_t *u8g2_cb = (u8g2_cb_t *)lua_touserdata( L, ++stack );

  u8g2_SetDisplayRotation( u8g2, u8g2_cb );

  return 0;
}

static int lu8g2_setDrawColor( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int col = luaL_checkint( L, ++stack );

  u8g2_SetDrawColor( u8g2, col );

  return 0;
}

static int lu8g2_setFlipMode( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int is_enable = luaL_checkint( L, ++stack );

  u8g2_SetFlipMode( u8g2, is_enable );

  return 0;
}

static int lu8g2_setFont( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  const uint8_t *font = NULL;

  luaL_unref( L, LUA_REGISTRYINDEX, ud->font_ref );
  ud->font_ref = LUA_NOREF;

  if (lua_islightuserdata( L, ++stack )) {
    font = (const uint8_t *)lua_touserdata( L, stack );

  } else if (lua_isstring( L, stack )) {
    // ref the font string to safe it in case the string variable gets gc'ed
    lua_pushvalue( L, stack );
    ud->font_ref = luaL_ref( L, LUA_REGISTRYINDEX );

    size_t len;
    font = (const uint8_t *)luaL_checklstring( L, stack, &len );

  }
  luaL_argcheck( L, font != NULL, stack, "invalid font" );

  u8g2_SetFont( u8g2, font );

  return 0;
}

static int lu8g2_setFontDirection( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int dir = luaL_checkint( L, ++stack );

  u8g2_SetFontDirection( u8g2, dir );

  return 0;
}

static int lu8g2_setFontMode( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int is_transparent = luaL_checkint( L, ++stack );

  u8g2_SetFontMode( u8g2, is_transparent );

  return 0;
}

static int lu8g2_setFontPosBaseline( lua_State *L )
{
  GET_U8G2();

  u8g2_SetFontPosBaseline( u8g2 );

  return 0;
}

static int lu8g2_setFontPosBottom( lua_State *L )
{
  GET_U8G2();

  u8g2_SetFontPosBottom( u8g2 );

  return 0;
}

static int lu8g2_setFontPosTop( lua_State *L )
{
  GET_U8G2();

  u8g2_SetFontPosTop( u8g2 );

  return 0;
}

static int lu8g2_setFontPosCenter( lua_State *L )
{
  GET_U8G2();

  u8g2_SetFontPosCenter( u8g2 );

  return 0;
}

static int lu8g2_setFontRefHeightAll( lua_State *L )
{
  GET_U8G2();

  u8g2_SetFontRefHeightAll( u8g2 );

  return 0;
}

static int lu8g2_setFontRefHeightExtendedText( lua_State *L )
{
  GET_U8G2();

  u8g2_SetFontRefHeightExtendedText( u8g2 );

  return 0;
}

static int lu8g2_setFontRefHeightText( lua_State *L )
{
  GET_U8G2();

  u8g2_SetFontRefHeightText( u8g2 );

  return 0;
}

static int lu8g2_setPowerSave( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int is_enable = luaL_checkint( L, ++stack );

  u8g2_SetPowerSave( u8g2, is_enable );

  return 0;
}

static int lu8g2_updateDisplay( lua_State *L )
{
  GET_U8G2();

  u8g2_UpdateDisplay( u8g2 );

  return 0;
}

static int lu8g2_updateDisplayArea( lua_State *L )
{
  GET_U8G2();
  int stack = 1;

  int x = luaL_checkint( L, ++stack );
  int y = luaL_checkint( L, ++stack );
  int w = luaL_checkint( L, ++stack );
  int h = luaL_checkint( L, ++stack );

  u8g2_UpdateDisplayArea( u8g2, x, y, w, h );

  return 0;
}


LROT_BEGIN(lu8g2_display)
  LROT_FUNCENTRY( clearBuffer, lu8g2_clearBuffer )
  LROT_FUNCENTRY( drawBox, lu8g2_drawBox )
  LROT_FUNCENTRY( drawCircle, lu8g2_drawCircle )
  LROT_FUNCENTRY( drawDisc, lu8g2_drawDisc )
  LROT_FUNCENTRY( drawEllipse, lu8g2_drawEllipse )
  LROT_FUNCENTRY( drawFilledEllipse, lu8g2_drawFilledEllipse )
  LROT_FUNCENTRY( drawFrame, lu8g2_drawFrame )
  LROT_FUNCENTRY( drawGlyph, lu8g2_drawGlyph )
  LROT_FUNCENTRY( drawHLine, lu8g2_drawHLine )
  LROT_FUNCENTRY( drawLine, lu8g2_drawLine )
  LROT_FUNCENTRY( drawPixel, lu8g2_drawPixel )
  LROT_FUNCENTRY( drawRBox, lu8g2_drawRBox )
  LROT_FUNCENTRY( drawRFrame, lu8g2_drawRFrame )
  LROT_FUNCENTRY( drawStr, lu8g2_drawStr )
  LROT_FUNCENTRY( drawTriangle, lu8g2_drawTriangle )
  LROT_FUNCENTRY( drawUTF8, lu8g2_drawUTF8 )
  LROT_FUNCENTRY( drawVLine, lu8g2_drawVLine )
  LROT_FUNCENTRY( drawXBM, lu8g2_drawXBM )
  LROT_FUNCENTRY( getAscent, lu8g2_getAscent )
  LROT_FUNCENTRY( getDescent, lu8g2_getDescent )
  LROT_FUNCENTRY( getStrWidth, lu8g2_getStrWidth )
  LROT_FUNCENTRY( getUTF8Width, lu8g2_getUTF8Width )
  LROT_FUNCENTRY( sendBuffer, lu8g2_sendBuffer )
  LROT_FUNCENTRY( setBitmapMode, lu8g2_setBitmapMode )
  LROT_FUNCENTRY( setContrast, lu8g2_setContrast )
  LROT_FUNCENTRY( setDisplayRotation, lu8g2_setDisplayRotation )
  LROT_FUNCENTRY( setDrawColor, lu8g2_setDrawColor )
  LROT_FUNCENTRY( setFlipMode, lu8g2_setFlipMode )
  LROT_FUNCENTRY( setFont, lu8g2_setFont )
  LROT_FUNCENTRY( setFontDirection, lu8g2_setFontDirection )
  LROT_FUNCENTRY( setFontMode, lu8g2_setFontMode )
  LROT_FUNCENTRY( setFontPosBaseline, lu8g2_setFontPosBaseline )
  LROT_FUNCENTRY( setFontPosBottom, lu8g2_setFontPosBottom )
  LROT_FUNCENTRY( setFontPosTop, lu8g2_setFontPosTop )
  LROT_FUNCENTRY( setFontPosCenter, lu8g2_setFontPosCenter )
  LROT_FUNCENTRY( setFontRefHeightAll, lu8g2_setFontRefHeightAll )
  LROT_FUNCENTRY( setFontRefHeightExtendedText, lu8g2_setFontRefHeightExtendedText )
  LROT_FUNCENTRY( setFontRefHeightText, lu8g2_setFontRefHeightText )
  LROT_FUNCENTRY( setPowerSave, lu8g2_setPowerSave )
  LROT_FUNCENTRY( updateDispla, lu8g2_updateDisplay )
  LROT_FUNCENTRY( updateDisplayArea, lu8g2_updateDisplayArea )
  //  LROT_FUNCENTRY( __gc, lu8g2_display_free )
  LROT_TABENTRY( __index, lu8g2_display )
LROT_END( lu8g2_display, lu8g2_display, LROT_MASK_GC_INDEX )


uint8_t u8x8_d_overlay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);

typedef void (*display_setup_fn_t)(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb);

// ***************************************************************************
// Device constructors
//
// I2C based devices will use this function template to implement the Lua binding.
//
static int ldisplay_i2c( lua_State *L, display_setup_fn_t setup_fn )
{
  int stack = 0;

  int id = -1;
  int i2c_addr = -1;
  int rfb_cb_ref = LUA_NOREF;

  if (lua_type( L, ++stack) == LUA_TNUMBER) {
    /* hardware display connected */
    id = luaL_checkint( L, stack );
    i2c_addr = luaL_checkint( L, ++stack );
    luaL_argcheck( L, i2c_addr >= 0 && i2c_addr <= 0x7f, stack, "invalid i2c address" );
  } else
    stack--;
  if (lua_isfunction( L, ++stack )) {
    lua_pushvalue( L, stack );
    rfb_cb_ref = luaL_ref( L, LUA_REGISTRYINDEX );
  }
  if (id < 0 && rfb_cb_ref == LUA_NOREF)
    return luaL_error( L, "wrong args" );

  u8g2_ud_t *ud = (u8g2_ud_t *)lua_newuserdata( L, sizeof( u8g2_ud_t ) );
  u8g2_nodemcu_t *ext_u8g2 = &(ud->u8g2);
  ud->font_ref = LUA_NOREF;
  ud->host_ref = LUA_NOREF;

  u8g2_t *u8g2 = (u8g2_t *)ext_u8g2;
  u8x8_t *u8x8 = (u8x8_t *)u8g2;
  /* the i2c driver id is forwarded in the user pointer */
  u8x8->user_ptr = id >= 0 ? (void *)id : NULL;

  setup_fn( u8g2, U8G2_R0, u8x8_byte_nodemcu_i2c, u8x8_gpio_and_delay_nodemcu );

  /* prepare overlay data */
  if (rfb_cb_ref != LUA_NOREF) {
    ext_u8g2->overlay.template_display_cb = u8x8->display_cb;
    ext_u8g2->overlay.hardware_display_cb = NULL;
    ext_u8g2->overlay.rfb_cb_ref = LUA_NOREF;
    u8x8->display_cb = u8x8_d_overlay;
  }
  if (id >= 0) {
    /* hardware device specific initialization */
    u8x8_SetI2CAddress( u8x8, i2c_addr );
    ext_u8g2->overlay.hardware_display_cb = ext_u8g2->overlay.template_display_cb;
  }

  u8g2_InitDisplay( (u8g2_t *)u8g2 );
  u8g2_ClearDisplay( (u8g2_t *)u8g2 );
  u8g2_SetPowerSave( (u8g2_t *)u8g2, 0 );

  if (rfb_cb_ref != LUA_NOREF) {
    /* finally enable rfb display driver */
    ext_u8g2->overlay.rfb_cb_ref = rfb_cb_ref;
  }

  /* set its metatable */
  luaL_getmetatable(L, "u8g2.display");
  lua_setmetatable(L, -2);

  return 1;
}
//
// SPI based devices will use this function template to implement the Lua binding.
//
static int ldisplay_spi( lua_State *L, display_setup_fn_t setup_fn )
{
  int stack = 0;

#ifndef ESP_PLATFORM
  // ESP8266
  typedef struct {
    int host;
  } lspi_host_t;
  lspi_host_t host_elem;
  lspi_host_t *host = &host_elem;
#else
  // ESP32
  lspi_host_t *host = NULL;
#endif
  int host_ref = LUA_NOREF;
  int cs = -1;
  int dc = -1;
  int res = -1;
  int rfb_cb_ref = LUA_NOREF;
  int get_spi_pins;


  if (lua_type( L, ++stack ) == LUA_TUSERDATA) {
    host = (lspi_host_t *)luaL_checkudata( L, stack, "spi.master" );
    /* reference host object to avoid automatic gc */
    lua_pushvalue( L, stack );
    host_ref = luaL_ref( L, LUA_REGISTRYINDEX );
    get_spi_pins = 1;
  } else if (lua_type( L, stack ) == LUA_TNUMBER) {
    host->host = luaL_checkint( L, stack );
    get_spi_pins = 1;
  } else {
    get_spi_pins = 0;
    stack--;
  }

  if (get_spi_pins) {
    cs = luaL_checkint( L, ++stack );
    dc = luaL_checkint( L, ++stack );
    res = luaL_optint( L, ++stack, -1 );
  }

  if (lua_isfunction( L, ++stack )) {
    lua_pushvalue( L, stack );
    rfb_cb_ref = luaL_ref( L, LUA_REGISTRYINDEX );
  }
  if (!host && rfb_cb_ref == LUA_NOREF)
    return luaL_error( L, "wrong args" );

  u8g2_ud_t *ud = (u8g2_ud_t *)lua_newuserdata( L, sizeof( u8g2_ud_t ) );
  u8g2_nodemcu_t *ext_u8g2 = &(ud->u8g2);
  ud->font_ref = LUA_NOREF;
  ud->host_ref = host_ref;

  u8g2_t *u8g2 = (u8g2_t *)ext_u8g2;
  u8x8_t *u8x8 = (u8x8_t *)u8g2;
  /* the spi host id is forwarded in the user pointer */
  u8x8->user_ptr = host ? (void *)(host->host) : NULL;

  setup_fn( u8g2, U8G2_R0, u8x8_byte_nodemcu_spi, u8x8_gpio_and_delay_nodemcu );

  /* prepare overlay data */
  if (rfb_cb_ref != LUA_NOREF) {
    ext_u8g2->overlay.template_display_cb = u8x8->display_cb;
    ext_u8g2->overlay.hardware_display_cb = NULL;
    ext_u8g2->overlay.rfb_cb_ref = LUA_NOREF;
    u8x8->display_cb = u8x8_d_overlay;
  }
  if (host) {
    /* hardware specific device initialization */
    u8x8_SetPin( u8x8, U8X8_PIN_CS, cs );
    u8x8_SetPin( u8x8, U8X8_PIN_DC, dc );
    if (res >= 0)
      u8x8_SetPin( u8x8, U8X8_PIN_RESET, res );
    ext_u8g2->overlay.hardware_display_cb = ext_u8g2->overlay.template_display_cb;
  }

  u8g2_InitDisplay( (u8g2_t *)u8g2 );
  u8g2_ClearDisplay( (u8g2_t *)u8g2 );
  u8g2_SetPowerSave( (u8g2_t *)u8g2, 0 );

  if (rfb_cb_ref != LUA_NOREF) {
    /* finally enable rfb display driver */
    ext_u8g2->overlay.rfb_cb_ref = rfb_cb_ref;
  }

  /* set its metatable */
  luaL_getmetatable(L, "u8g2.display");
  lua_setmetatable(L, -2);

  return 1;
}
//
//
#undef U8G2_DISPLAY_TABLE_ENTRY
#define U8G2_DISPLAY_TABLE_ENTRY(function, binding) \
  static int l ## binding( lua_State *L )           \
  {                                                 \
    return ldisplay_i2c( L, function );             \
  }
//
// Unroll the display table and insert binding functions for I2C based displays.
U8G2_DISPLAY_TABLE_I2C
//
//
#undef U8G2_DISPLAY_TABLE_ENTRY
#define U8G2_DISPLAY_TABLE_ENTRY(function, binding) \
  static int l ## binding( lua_State *L )           \
  {                                                 \
    return ldisplay_spi( L, function );             \
  }
//
// Unroll the display table and insert binding functions for SPI based displays.
U8G2_DISPLAY_TABLE_SPI
//


#undef U8G2_FONT_TABLE_ENTRY
#undef U8G2_DISPLAY_TABLE_ENTRY
#define U8G2_DISPLAY_TABLE_ENTRY(function, binding) LROT_FUNCENTRY(binding,l ## binding)
LROT_BEGIN(lu8g2)
  U8G2_DISPLAY_TABLE_I2C
  U8G2_DISPLAY_TABLE_SPI
  //
  // Register fonts
#define U8G2_FONT_TABLE_ENTRY(font) LROT_LUDENTRY(font, u8g2_ ## font)
  U8G2_FONT_TABLE
  //
  LROT_NUMENTRY( DRAW_UPPER_RIGHT, U8G2_DRAW_UPPER_RIGHT )
  LROT_NUMENTRY( DRAW_UPPER_LEFT, U8G2_DRAW_UPPER_LEFT )
  LROT_NUMENTRY( DRAW_LOWER_RIGHT, U8G2_DRAW_LOWER_RIGHT )
  LROT_NUMENTRY( DRAW_LOWER_LEFT, U8G2_DRAW_LOWER_LEFT )
  LROT_NUMENTRY( DRAW_ALL, U8G2_DRAW_ALL )
  LROT_LUDENTRY( R0, U8G2_R0 )
  LROT_LUDENTRY( R1, U8G2_R1 )
  LROT_LUDENTRY( R2, U8G2_R2 )
  LROT_LUDENTRY( R3, U8G2_R3 )
  LROT_LUDENTRY( MIRROR, U8G2_MIRROR )
LROT_END( lu8g2, NULL, 0 )


int luaopen_u8g2( lua_State *L ) {
  luaL_rometatable(L, "u8g2.display", LROT_TABLEREF(lu8g2_display));
  return 0;
}

NODEMCU_MODULE(U8G2, "u8g2", lu8g2, luaopen_u8g2);

#endif /* defined(LUA_USE_MODULES_U8G2) || defined(ESP_PLATFORM) */
