// Module for Ucglib

#include "lualib.h"
#include "lauxlib.h"
#include "platform.h"
#include "auxmods.h"
#include "lrotable.h"

#include "c_stdlib.h"

#include "ucg.h"

//#include "u8g_config.h"

struct _lucg_userdata_t
{
    ucg_t ucg;

    ucg_dev_fnptr dev_cb;
    ucg_dev_fnptr ext_cb;  

    // For Print() function
    ucg_int_t tx, ty;
    uint8_t tdir;
};

typedef struct _lucg_userdata_t lucg_userdata_t;


#define delayMicroseconds os_delay_us

static int16_t ucg_com_esp8266_hw_spi(ucg_t *ucg, int16_t msg, uint16_t arg, uint8_t *data);




// shorthand macro for the ucg structure inside the userdata
#define LUCG (&(lud->ucg))


// helper function: retrieve and check userdata argument
static lucg_userdata_t *get_lud( lua_State *L )
{
    lucg_userdata_t *lud = (lucg_userdata_t *)luaL_checkudata(L, 1, "ucg.display");
    luaL_argcheck(L, lud, 1, "ucg.display expected");
    return lud;
}

// helper function: retrieve given number of integer arguments
static void lucg_get_int_args( lua_State *L, uint8_t stack, uint8_t num, ucg_int_t *args)
{
    while (num-- > 0)
    {
        *args++ = luaL_checkinteger( L, stack++ );
    }
}

// Lua: ucg.begin( self, fontmode )
static int lucg_begin( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_Init( LUCG, lud->dev_cb, lud->ext_cb, ucg_com_esp8266_hw_spi );

    ucg_int_t fontmode = luaL_checkinteger( L, 2 );

    ucg_SetFontMode( LUCG, fontmode );

    return 0;
}

// Lua: ucg.clearScreen( self )
static int lucg_clearScreen( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_ClearScreen( LUCG );

    return 0;
}

// Lua: ucg.draw90Line( self, x, y, len, dir, col_idx )
static int lucg_draw90Line( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_int_t args[5];
    lucg_get_int_args( L, 2, 5, args );

    ucg_Draw90Line( LUCG, args[0], args[1], args[2], args[3], args[4] );

    return 0;
}

// Lua: ucg.drawBox( self, x, y, w, h )
static int lucg_drawBox( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_int_t args[4];
    lucg_get_int_args( L, 2, 4, args );

    ucg_DrawBox( LUCG, args[0], args[1], args[2], args[3] );

    return 0;
}

// Lua: ucg.drawFrame( self, x, y, w, h )
static int lucg_drawFrame( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_int_t args[4];
    lucg_get_int_args( L, 2, 4, args );

    ucg_DrawFrame( LUCG, args[0], args[1], args[2], args[3] );

    return 0;
}

// Lua: ucg.drawGradientBox( self, x, y, w, h )
static int lucg_drawGradientBox( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_int_t args[4];
    lucg_get_int_args( L, 2, 4, args );

    ucg_DrawGradientBox( LUCG, args[0], args[1], args[2], args[3] );

    return 0;
}

// Lua: ucg.drawHLine( self, x, y, len )
static int lucg_drawHLine( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_int_t args[3];
    lucg_get_int_args( L, 2, 3, args );

    ucg_DrawHLine( LUCG, args[0], args[1], args[2] );

    return 0;
}

// Lua: ucg.drawLine( self, x1, y1, x2, y2 )
static int lucg_drawLine( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_int_t args[4];
    lucg_get_int_args( L, 2, 4, args );

    ucg_DrawLine( LUCG, args[0], args[1], args[2], args[3] );

    return 0;
}

// Lua: ucg.drawPixel( self, x, y )
static int lucg_drawPixel( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_int_t args[2];
    lucg_get_int_args( L, 2, 2, args );

    ucg_DrawPixel( LUCG, args[0], args[1] );

    return 0;
}

// Lua: ucg.drawString( self, x, y, dir, str )
static int lucg_drawString( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_int_t args[3];
    lucg_get_int_args( L, 2, 3, args );

    const char *s = luaL_checkstring( L, 2 );
    if (s == NULL)
        return 0;

    ucg_DrawString( LUCG, args[0], args[1], args[2], s );

    return 0;
}

// Lua: ucg.drawTriangle( self, x0, y0, x1, y1, x2, y2 )
static int lucg_drawTriangle( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_int_t args[6];
    lucg_get_int_args( L, 2, 6, args );

    ucg_DrawTriangle( LUCG, args[0], args[1], args[2], args[3], args[4], args[5] );

    return 0;
}

// Lua: ucg.drawVLine( self, x, y, len )
static int lucg_drawVLine( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_int_t args[3];
    lucg_get_int_args( L, 2, 3, args );

    ucg_DrawVLine( LUCG, args[0], args[1], args[2] );

    return 0;
}

// Lua: ucg.getHeight( self )
static int lucg_getHeight( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    lua_pushinteger( L, ucg_GetHeight( LUCG ) );

    return 1;
}

// Lua: ucg.getWidth( self )
static int lucg_getWidth( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    lua_pushinteger( L, ucg_GetWidth( LUCG ) );

    return 1;
}

// Lua: ucg.setClipRange( self, x, y, w, h )
static int lucg_setClipRange( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_int_t args[4];
    lucg_get_int_args( L, 2, 4, args );

    ucg_SetClipRange( LUCG, args[0], args[1], args[2], args[3] );

    return 0;
}

// Lua: ucg.setColor( self, [idx], r, g, b )
static int lucg_setColor( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_int_t args[3];
    lucg_get_int_args( L, 2, 3, args );

    ucg_int_t opt = luaL_optint( L, (1+3) + 1, -1 );

    if (opt < 0)
        ucg_SetColor( LUCG, 0, args[0], args[1], args[2] );
    else
        ucg_SetColor( LUCG, args[0], args[1], args[2], opt );

    return 0;
}

// Lua: ucg.setFont( self, font )
static int lucg_setFont( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_fntpgm_uint8_t *font = (ucg_fntpgm_uint8_t *)lua_touserdata( L, 2 );
    if (font != NULL)
        ucg_SetFont( LUCG, font );
    else
        luaL_argerror(L, 2, "font data expected");

    return 0;
}

// Lua: ucg.print( self, str )
static int lucg_print( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    const char *s = luaL_checkstring( L, 2 );
    if (s == NULL)
        return 0;

    while (*s)
    {
        ucg_int_t delta;
        delta = ucg_DrawGlyph(LUCG, lud->tx, lud->ty, lud->tdir, *(s++));
        switch(lud->tdir)
        {
        case 0: lud->tx += delta; break;
        case 1: lud->ty += delta; break;
        case 2: lud->tx -= delta; break;
        default: case 3: lud->ty -= delta; break;
        }
    }

    return 0;
}

// Lua: ucg.setFontMode( self, fontmode )
static int lucg_setFontMode( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_int_t fontmode = luaL_checkinteger( L, 2 );

    ucg_SetFontMode( LUCG, fontmode );

    return 0;
}

// Lua: ucg.setMaxClipRange( self )
static int lucg_setMaxClipRange( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_SetMaxClipRange( LUCG );

    return 0;
}

// Lua: ucg.setPrintPos( self, x, y )
static int lucg_setPrintPos( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_int_t args[2];
    lucg_get_int_args( L, 2, 2, args );

    lud->tx = args[0];
    lud->ty = args[1];

    return 0;
}

// Lua: ucg.setPrintDir( self, dir )
static int lucg_setPrintDir( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    lud->tdir = luaL_checkinteger( L, 2 );

    return 0;
}

// Lua: ucg.setRotate90( self )
static int lucg_setRotate90( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_SetRotate90( LUCG );

    return 0;
}

// Lua: ucg.setRotate180( self )
static int lucg_setRotate180( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_SetRotate180( LUCG );

    return 0;
}

// Lua: ucg.setRotate270( self )
static int lucg_setRotate270( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_SetRotate270( LUCG );

    return 0;
}

// Lua: ucg.undoRotate( self )
static int lucg_undoRotate( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    ucg_UndoRotate( LUCG );

    return 0;
}



static int16_t ucg_com_esp8266_hw_spi(ucg_t *ucg, int16_t msg, uint16_t arg, uint8_t *data)
{
  switch(msg)
  {
    case UCG_COM_MSG_POWER_UP:
        /* "data" is a pointer to ucg_com_info_t structure with the following information: */
        /*	((ucg_com_info_t *)data)->serial_clk_speed value in nanoseconds */
        /*	((ucg_com_info_t *)data)->parallel_clk_speed value in nanoseconds */
      
        /* setup pins */
    
        // we assume that the SPI interface was already initialized
        // just care for the /CS and D/C pins
        //platform_gpio_write( ucg->pin_list[0], value );

        if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
            platform_gpio_mode( ucg->pin_list[UCG_PIN_RST], PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT );
        platform_gpio_mode( ucg->pin_list[UCG_PIN_CD], PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT );
      
        if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
            platform_gpio_mode( ucg->pin_list[UCG_PIN_CS], PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT );
      
        break;

    case UCG_COM_MSG_POWER_DOWN:
        break;

    case UCG_COM_MSG_DELAY:
        delayMicroseconds(arg);
        break;

    case UCG_COM_MSG_CHANGE_RESET_LINE:
        if ( ucg->pin_list[UCG_PIN_RST] != UCG_PIN_VAL_NONE )
            platform_gpio_write( ucg->pin_list[UCG_PIN_RST], arg );
        break;

    case UCG_COM_MSG_CHANGE_CS_LINE:
        if ( ucg->pin_list[UCG_PIN_CS] != UCG_PIN_VAL_NONE )
            platform_gpio_write( ucg->pin_list[UCG_PIN_CS], arg );
        break;

    case UCG_COM_MSG_CHANGE_CD_LINE:
        platform_gpio_write( ucg->pin_list[UCG_PIN_CD], arg );
        break;

    case UCG_COM_MSG_SEND_BYTE:
        platform_spi_send_recv( 1, arg );
        break;

    case UCG_COM_MSG_REPEAT_1_BYTE:
        while( arg > 0 ) {
            platform_spi_send_recv( 1, data[0] );
            arg--;
        }
        break;

    case UCG_COM_MSG_REPEAT_2_BYTES:
        while( arg > 0 ) {
            platform_spi_send_recv( 1, data[0] );
            platform_spi_send_recv( 1, data[1] );
            arg--;
        }
        break;

    case UCG_COM_MSG_REPEAT_3_BYTES:
        while( arg > 0 ) {
            platform_spi_send_recv( 1, data[0] );
            platform_spi_send_recv( 1, data[1] );
            platform_spi_send_recv( 1, data[2] );
            arg--;
        }
        break;

    case UCG_COM_MSG_SEND_STR:
        while( arg > 0 ) {
            platform_spi_send_recv( 1, *data++ );
            arg--;
        }
        break;

    case UCG_COM_MSG_SEND_CD_DATA_SEQUENCE:
        while(arg > 0)
        {
            if ( *data != 0 )
            {
                /* set the data line directly, ignore the setting from UCG_CFG_CD */
                if ( *data == 1 )
                {
                    platform_gpio_write( ucg->pin_list[UCG_PIN_CD], 0 );
                }
                else
                {
                    platform_gpio_write( ucg->pin_list[UCG_PIN_CD], 1 );
                }
            }
            data++;
            platform_spi_send_recv( 1, *data );
            data++;
            arg--;
        }
        break;
  }
  return 1;
}



// device destructor
static int lucg_close_display( lua_State *L )
{
    lucg_userdata_t *lud;

    if ((lud = get_lud( L )) == NULL)
        return 0;

    return 0;
}


static int lucg_ili9341_18x240x320_hw_spi( lua_State *L )
{
    unsigned cs = luaL_checkinteger( L, 1 );
    if (cs == 0)
        return luaL_error( L, "CS pin required" );
    unsigned dc = luaL_checkinteger( L, 2 );
    if (dc == 0)
        return luaL_error( L, "D/C pin required" );
    unsigned res = luaL_optinteger( L, 3, UCG_PIN_VAL_NONE );

    lucg_userdata_t *lud = (lucg_userdata_t *) lua_newuserdata( L, sizeof( lucg_userdata_t ) );

    // do a dummy init so that something usefull is part of the ucg structure
    ucg_Init( LUCG, ucg_dev_default_cb, ucg_ext_none, (ucg_com_fnptr)0 );

    // reset cursor position
    lud->tx   = 0;
    lud->ty   = 0;
    lud->tdir = 0;  // default direction

    uint8_t i;
    for( i = 0; i < UCG_PIN_COUNT; i++ )
        lud->ucg.pin_list[i] = UCG_PIN_VAL_NONE;


    lud->dev_cb = ucg_dev_ili9341_18x240x320;
    lud->ext_cb = ucg_ext_ili9341_18;
    lud->ucg.pin_list[UCG_PIN_RST] = res;
    lud->ucg.pin_list[UCG_PIN_CD]  = dc;
    lud->ucg.pin_list[UCG_PIN_CS]  = cs;


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

    return 1;
}



// Module function map
#define MIN_OPT_LEVEL 2
#include "lrodefs.h"

static const LUA_REG_TYPE lucg_display_map[] =
{
    { LSTRKEY( "begin" ),           LFUNCVAL( lucg_begin ) },
    { LSTRKEY( "clearScreen" ),     LFUNCVAL( lucg_clearScreen ) },
    { LSTRKEY( "draw90Line" ),      LFUNCVAL( lucg_draw90Line ) },
    { LSTRKEY( "drawBox" ),         LFUNCVAL( lucg_drawBox ) },
    { LSTRKEY( "drawFrame" ),       LFUNCVAL( lucg_drawFrame ) },
    { LSTRKEY( "drawGradientBox" ), LFUNCVAL( lucg_drawGradientBox ) },
    { LSTRKEY( "drawHLine" ),       LFUNCVAL( lucg_drawHLine ) },
    { LSTRKEY( "drawLine" ),        LFUNCVAL( lucg_drawLine ) },
    { LSTRKEY( "drawPixel" ),       LFUNCVAL( lucg_drawPixel ) },
    { LSTRKEY( "drawString" ),      LFUNCVAL( lucg_drawString ) },
    { LSTRKEY( "drawTriangle" ),    LFUNCVAL( lucg_drawTriangle ) },
    { LSTRKEY( "drawVLine" ),       LFUNCVAL( lucg_drawVLine ) },
    { LSTRKEY( "getHeight" ),       LFUNCVAL( lucg_getHeight ) },
    { LSTRKEY( "getWidth" ),        LFUNCVAL( lucg_getWidth ) },
    { LSTRKEY( "print" ),           LFUNCVAL( lucg_print ) },
    { LSTRKEY( "setClipRange" ),    LFUNCVAL( lucg_setClipRange ) },
    { LSTRKEY( "setColor" ),        LFUNCVAL( lucg_setColor ) },
    { LSTRKEY( "setFont" ),         LFUNCVAL( lucg_setFont ) },
    { LSTRKEY( "setFontMode" ),     LFUNCVAL( lucg_setFontMode ) },
    { LSTRKEY( "setMaxClipRange" ), LFUNCVAL( lucg_setMaxClipRange ) },
    { LSTRKEY( "setPrintDir" ),     LFUNCVAL( lucg_setPrintDir ) },
    { LSTRKEY( "setPrintPos" ),     LFUNCVAL( lucg_setPrintPos ) },
    { LSTRKEY( "setRotate90" ),     LFUNCVAL( lucg_setRotate90 ) },
    { LSTRKEY( "setRotate180" ),    LFUNCVAL( lucg_setRotate180 ) },
    { LSTRKEY( "setRotate270" ),    LFUNCVAL( lucg_setRotate270 ) },
    { LSTRKEY( "undoRotate" ),      LFUNCVAL( lucg_undoRotate ) },

    { LSTRKEY( "__gc" ),  LFUNCVAL( lucg_close_display ) },
#if LUA_OPTIMIZE_MEMORY > 0
    { LSTRKEY( "__index" ), LROVAL ( lucg_display_map ) },
#endif
    { LNILKEY, LNILVAL }
};

const LUA_REG_TYPE lucg_map[] = 
{
    { LSTRKEY( "ili9341_18x240x320_hw_spi" ), LFUNCVAL( lucg_ili9341_18x240x320_hw_spi ) },

#if LUA_OPTIMIZE_MEMORY > 0

    // Register fonts
    { LSTRKEY( "font_helvB08_hr" ), LUDATA( (void *)(ucg_font_helvB08_hr) ) },
    { LSTRKEY( "font_helvB10_hr" ), LUDATA( (void *)(ucg_font_helvB10_hr) ) },
    { LSTRKEY( "font_helvB12_hr" ), LUDATA( (void *)(ucg_font_helvB12_hr) ) },
    { LSTRKEY( "font_helvB18_hr" ), LUDATA( (void *)(ucg_font_helvB18_hr) ) },
    { LSTRKEY( "font_ncenR12_tr" ), LUDATA( (void *)(ucg_font_ncenR12_tr) ) },
    { LSTRKEY( "font_ncenR14_hr" ), LUDATA( (void *)(ucg_font_ncenR14_hr) ) },

    // Font modes
    { LSTRKEY( "FONT_MODE_TRANSPARENT" ), LNUMVAL( UCG_FONT_MODE_TRANSPARENT ) },
    { LSTRKEY( "FONT_MODE_SOLID" ),       LNUMVAL( UCG_FONT_MODE_SOLID ) },

    { LSTRKEY( "__metatable" ), LROVAL( lucg_map ) },
#endif
    { LNILKEY, LNILVAL }
};

LUALIB_API int luaopen_ucg( lua_State *L )
{
#if LUA_OPTIMIZE_MEMORY > 0
    luaL_rometatable(L, "ucg.display", (void *)lucg_display_map);  // create metatable
    return 0;
#else // #if LUA_OPTIMIZE_MEMORY > 0
    int n;
    luaL_register( L, AUXLIB_UCG, lucg_map );

    // Set it as its own metatable
    lua_pushvalue( L, -1 );
    lua_setmetatable( L, -2 );

    // Module constants  

    // Register fonts
    MOD_REG_LUDATA( L, "font_helvB08_hr", (void *)(ucg_font_helvB08_hr) );
    MOD_REG_LUDATA( L, "font_helvB10_hr", (void *)(ucg_font_helvB10_hr) );
    MOD_REG_LUDATA( L, "font_helvB12_hr", (void *)(ucg_font_helvB12_hr) );
    MOD_REG_LUDATA( L, "font_helvB18_hr", (void *)(ucg_font_helvB18_hr) );
    MOD_REG_LUDATA( L, "font_ncenR12_tr", (void *)(ucg_font_ncenR12_tr) );
    MOD_REG_LUDATA( L, "font_ncenR14_hr", (void *)(ucg_font_ncenR14_hr) );

    // Font modes
    MOD_REG_NUMBER( L, "FONT_MODE_TRANSPARENT", UCG_FONT_MODE_TRANSPARENT );
    MOD_REG_NUMBER( L, "FONT_MODE_SOLID",       UCG_FONT_MODE_SOLID );

    // create metatable
    luaL_newmetatable(L, "ucg.display");
    // metatable.__index = metatable
    lua_pushliteral(L, "__index");
    lua_pushvalue(L,-2);
    lua_rawset(L,-3);
    // Setup the methods inside metatable
    luaL_register( L, NULL, lucg_display_map );

    return 1;
#endif // #if LUA_OPTIMIZE_MEMORY > 0  
}
