// Module for interfacing with the OneWire interface

#include "module.h"
#include "lauxlib.h"
#include <stdint.h>
#include "platform.h"


static int ow_bus_table_ref;

// Lua: ow.setup( pin )
static int ow_setup( lua_State *L )
{
  int pin = luaL_checkint( L, 1 );
  luaL_argcheck( L, pin >= 0, 1, "invalid pin" );

  if (platform_onewire_init( pin ) != PLATFORM_OK)
    luaL_error( L, "failed" );

  return 0;
}

// Lua: r = ow.reset( pin )
static int ow_reset( lua_State *L )
{
  int pin = luaL_checkint( L, 1 );
  luaL_argcheck( L, pin >= 0, 1, "invalid pin" );

  uint8_t presence;
  if (platform_onewire_reset( pin, &presence ) != PLATFORM_OK)
    luaL_error( L, "failed" );

  lua_pushinteger( L, presence );
  return 1;
}

// Lua: ow.write( pin, v, power)
static int ow_write( lua_State *L )
{
  int stack = 0;

  int pin = luaL_checkint( L, ++stack );
  luaL_argcheck( L, pin >= 0, stack, "invalid pin" );

  int data = luaL_checkint( L, ++stack );
  luaL_argcheck( L, data >= 0 && data < 256, stack, "invalid data" );

  int power = luaL_optint( L, ++stack, 0 ) != 0 ? 1 : 0;

  uint8_t d = data;
  if (platform_onewire_write_bytes( pin, &d, 1, power ) != PLATFORM_OK)
    luaL_error( L, "failed" );

  return 0;
}

// Lua: ow.write_bytes( pin, buf, power)
static int ow_write_bytes( lua_State *L )
{
  int stack = 0;
  size_t datalen;

  int pin = luaL_checkint( L, ++stack );
  luaL_argcheck( L, pin >= 0, stack, "invalid pin" );

  const char *pdata = luaL_checklstring( L, ++stack, &datalen );

  int power = luaL_optint( L, ++stack, 0 ) != 0 ? 1 : 0;

  platform_onewire_write_bytes( pin, (uint8_t *)pdata, datalen, power);

  return 0;
}

// Lua: r = ow.read( pin )
static int ow_read( lua_State *L )
{
  int pin = luaL_checkint( L, 1 );
  luaL_argcheck( L, pin >= 0, 1, "invalid pin" );

  uint8_t data;
  if (platform_onewire_read_bytes( pin, &data, 1 ) != PLATFORM_OK)
    luaL_error( L, "failed" );

  lua_pushinteger( L, data );
  return 1;
}

// Lua: r = ow.read_bytes( pin, size )
static int ow_read_bytes( lua_State *L )
{
  int stack = 0;

  int pin = luaL_checkint( L, ++stack );
  luaL_argcheck( L, pin >= 0, stack, "invalid pin" );

  int size = luaL_checkint( L, ++stack );
  if (size == 0)
    return 0;
  luaL_argcheck( L, size <= LUAL_BUFFERSIZE, stack, "Attempt to read too many characters" );

  luaL_Buffer b;
  luaL_buffinit( L, &b );
  char *p = luaL_prepbuffer( &b );

  if (platform_onewire_read_bytes( pin, (uint8_t *)p, size ) != PLATFORM_OK)
    luaL_error( L, "failed" );

  luaL_addsize( &b, size );
  luaL_pushresult( &b );
  return 1;
}

// Lua: ow.select( pin, buf[8] )
static int ow_select( lua_State *L )
{
  int stack = 0;
  uint8_t rom[1+8];
  size_t datalen;
  int numdata, i;
  const char *pdata;

  int pin = luaL_checkint( L, ++stack );
  luaL_argcheck( L, pin >= 0, stack, "invalid pin" );

  if( lua_istable( L, ++stack ) )
  {
    datalen = lua_objlen( L, stack );
    luaL_argcheck( L, datalen == 8, stack, "wrong arg range" );
    for (i = 0; i < datalen; i ++) {
      lua_rawgeti( L, stack, i + 1 );
      numdata = ( int )luaL_checkinteger( L, -1 );
      lua_pop( L, 1 );
      if (numdata > 255)
        return luaL_error( L, "wrong arg range" );
      rom[1+i] = (uint8_t)numdata;
    }
  } else {
    pdata = luaL_checklstring( L, stack, &datalen );
    luaL_argcheck( L, datalen == 8, stack, "wrong arg range" );
    for (i = 0; i < datalen; i++) {
      rom[1+i] = pdata[i];
    }
  }

  rom[0] = 0x55;  // select command
  if (platform_onewire_write_bytes( pin, rom, 1+8, 0 ) != PLATFORM_OK)
    luaL_error( L, "write failed" );

  return 0;
}

// Lua: ow.skip( pin )
static int ow_skip( lua_State *L )
{
  int pin = luaL_checkint( L, 1 );
  luaL_argcheck( L, pin >= 0, 1, "invalid pin" );

  const uint8_t cmd = 0xCC;  // skip command
  if (platform_onewire_write_bytes( pin, &cmd, 1, 0 ) != PLATFORM_OK)
    luaL_error( L, "write failed" );

  return 0;
}

// Lua: ow.depower( pin )
static int ow_depower( lua_State *L )
{
  int pin = luaL_checkint( L, 1 );
  luaL_argcheck( L, pin >= 0, 1, "invalid pin" );

  platform_onewire_depower( pin );

  return 0;
}

// Clear the search state so that if will start from the beginning again.
// Lua: ow.reset_search( pin )
static int ow_reset_search( lua_State *L )
{
  int pin = luaL_checkint( L, 1 );
  luaL_argcheck( L, pin >= 0, 1, "invalid pin" );

  // set up new userdata for this pin
  lua_rawgeti( L, LUA_REGISTRYINDEX, ow_bus_table_ref );
  platform_onewire_bus_t *bus = (platform_onewire_bus_t *)lua_newuserdata( L, sizeof( platform_onewire_bus_t ));
  lua_rawseti( L, -2, pin );
  // remove table from stack
  lua_pop( L, 1 );

  platform_onewire_reset_search( bus );
  return 0;
}

// Setup the search to find the device type 'family_code' on the next call
// to search(*newAddr) if it is present.
// Lua: ow.target_search( pin, family_code )
static int ow_target_search( lua_State *L )
{
  int stack = 0;

  int pin = luaL_checkint( L, ++stack );
  luaL_argcheck( L, pin >= 0, stack, "invalid pin" );

  int code = (int)luaL_checkinteger( L, ++stack );
  luaL_argcheck( L, code >= 0 && code < 256, stack, "wrong arg range" );

  // set up new userdata for this pin
  lua_rawgeti( L, LUA_REGISTRYINDEX, ow_bus_table_ref );
  platform_onewire_bus_t *bus = (platform_onewire_bus_t *)lua_newuserdata( L, sizeof( platform_onewire_bus_t ));
  lua_rawseti( L, -2, pin );
  // remove table from stack
  lua_pop( L, 1 );

  platform_onewire_target_search( (uint8_t)code, bus );

  return 0;
}

// Look for the next device. Returns 1 if a new address has been
// returned. A zero might mean that the bus is shorted, there are
// no devices, or you have already retrieved all of them.  It
// might be a good idea to check the CRC to make sure you didn't
// get garbage.  The order is deterministic. You will always get
// the same devices in the same order.

// Lua: r = ow.search( pin )
static int ow_search( lua_State *L )
{
  int pin = luaL_checkint( L, 1 );
  luaL_argcheck( L, pin >= 0, 1, "invalid pin" );

  // look-up bus userdata for this pin
  lua_rawgeti( L, LUA_REGISTRYINDEX, ow_bus_table_ref );
  lua_rawgeti( L, -1, pin );
  platform_onewire_bus_t *bus = (platform_onewire_bus_t *)lua_touserdata( L, -1 );
  if (!bus)
    return luaL_error( L, "search not initialized" );
  // removed temps from stack
  lua_pop( L, 2 );

  luaL_Buffer b;
  luaL_buffinit( L, &b );
  char *p = luaL_prepbuffer( &b );

  if (platform_onewire_search( pin, (uint8_t *)p, bus )){
    luaL_addsize( &b, 8 );
    luaL_pushresult( &b );
  } else {
    luaL_pushresult( &b );  /* close buffer */
    lua_pop( L, 1 );

    // remove bus userdata from table
    lua_rawgeti( L, LUA_REGISTRYINDEX, ow_bus_table_ref );
    lua_pushnil( L );
    lua_rawseti( L, -2, pin );
    // remove table from stack
    lua_pop( L, 1 );

    lua_pushnil( L );
  }
  return 1; 
}

// uint8_t onewire_crc8(const uint8_t *addr, uint8_t len);
// Lua: r = ow.crc8( buf )
static int ow_crc8( lua_State *L )
{
  size_t datalen;
  const char *pdata = luaL_checklstring( L, 1, &datalen );
  if(datalen > 255)
    return luaL_error( L, "wrong arg range" );
  lua_pushinteger( L, platform_onewire_crc8((uint8_t *)pdata, datalen) );
  return 1;
}

// bool onewire_check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc);
// Lua: b = ow.check_crc16( buf, inverted_crc0, inverted_crc1, crc )
static int ow_check_crc16( lua_State *L )
{
  size_t datalen;
  uint8_t inverted_crc[2];
  const char *pdata = luaL_checklstring( L, 1, &datalen );
  if(datalen > 65535)
    return luaL_error( L, "wrong arg range" );

  int crc = 0;
  crc = luaL_checkinteger( L, 2 );
  if(datalen > 255)
    return luaL_error( L, "wrong arg range" );
  inverted_crc[0] = (uint8_t)crc;

  crc = luaL_checkinteger( L, 3 );
  if(datalen > 255)
    return luaL_error( L, "wrong arg range" );
  inverted_crc[1] = (uint8_t)crc;

  crc = 0;
  if(lua_isnumber(L, 4))
    crc = lua_tointeger(L, 4);
  if(crc > 65535)
    return luaL_error( L, "wrong arg range" );

  lua_pushboolean( L, platform_onewire_check_crc16((uint8_t *)pdata, datalen, inverted_crc, crc) );

  return 1;
}

// uint16_t onewire_crc16(const uint8_t* input, uint16_t len, uint16_t crc);
// Lua: r = ow.crc16( buf, crc )
static int ow_crc16( lua_State *L )
{
  int stack = 0;
  size_t datalen;

  const char *pdata = luaL_checklstring( L, ++stack, &datalen );
  luaL_argcheck( L, datalen <= 65535, stack, "wrong arg range" );

  int crc = 0;
  if(lua_isnumber(L, ++stack))
    crc = lua_tointeger(L, stack);
  luaL_argcheck( L, crc >= 0 && crc <= 65535, stack, "wrong arg range" );

  lua_pushinteger( L, platform_onewire_crc16((uint8_t *)pdata, datalen, crc) );

  return 1;
}

static const LUA_REG_TYPE ow_map[] = {
  { LSTRKEY( "setup" ),         LFUNCVAL( ow_setup ) },
  { LSTRKEY( "reset" ),         LFUNCVAL( ow_reset ) },
  { LSTRKEY( "skip" ),          LFUNCVAL( ow_skip ) },
  { LSTRKEY( "select" ),        LFUNCVAL( ow_select ) },
  { LSTRKEY( "write" ),         LFUNCVAL( ow_write ) },
  { LSTRKEY( "write_bytes" ),   LFUNCVAL( ow_write_bytes ) },
  { LSTRKEY( "read" ),          LFUNCVAL( ow_read ) },
  { LSTRKEY( "read_bytes" ),    LFUNCVAL( ow_read_bytes ) },
  { LSTRKEY( "depower" ),       LFUNCVAL( ow_depower ) },
  { LSTRKEY( "reset_search" ),  LFUNCVAL( ow_reset_search ) },
  { LSTRKEY( "target_search" ), LFUNCVAL( ow_target_search ) },
  { LSTRKEY( "search" ),        LFUNCVAL( ow_search ) },
  { LSTRKEY( "crc8" ),          LFUNCVAL( ow_crc8 ) },
  { LSTRKEY( "check_crc16" ),   LFUNCVAL( ow_check_crc16 ) },
  { LSTRKEY( "crc16" ),         LFUNCVAL( ow_crc16 ) },
  { LNILKEY, LNILVAL }
};

int luaopen_ow( lua_State *L )
{
  // create the table to store bus userdata for search operations
  lua_newtable( L );
  ow_bus_table_ref = luaL_ref( L, LUA_REGISTRYINDEX );

  return 0;
}

NODEMCU_MODULE(OW, "ow", ow_map, luaopen_ow);
