/*******************************************************************************
 * Copyright (c) 2015 - 2016 fortiss GmbH
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Martin Jobst, Monika Wenger
 *   - initial API and implementation and/or initial documentation
 *******************************************************************************/

#include "luaengine.h"

#include "../datatypes/forte_any.h"
#include "../datatypes/forte_bool.h"
#include "../datatypes/forte_sint.h"
#include "../datatypes/forte_int.h"
#include "../datatypes/forte_dint.h"
#include "../datatypes/forte_lint.h"
#include "../datatypes/forte_usint.h"
#include "../datatypes/forte_uint.h"
#include "../datatypes/forte_udint.h"
#include "../datatypes/forte_ulint.h"
#include "../datatypes/forte_byte.h"
#include "../datatypes/forte_word.h"
#include "../datatypes/forte_dword.h"
#include "../datatypes/forte_lword.h"
#include "../datatypes/forte_real.h"
#include "../datatypes/forte_lreal.h"
#include "../datatypes/forte_date.h"
#include "../datatypes/forte_time_of_day.h"
#include "../datatypes/forte_date_and_time.h"
#include "../datatypes/forte_time.h"
#include "../datatypes/forte_array.h"
#include "../../arch/devlog.h"

extern "C" {
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}

CLuaEngine::CLuaEngine() {
  luaState = luaL_newstate();
  luaL_openlibs(luaState);
}

CLuaEngine::~CLuaEngine() {
  lua_close(luaState);
}

bool CLuaEngine::loadString(const std::string& paStr) {
  int err = luaL_loadstring(luaState, paStr.c_str());
  if(err) {
    switch(err){
      case LUA_ERRSYNTAX:
        DEVLOG_ERROR("Syntax error loading Lua definitions\n");
        break;
      case LUA_ERRMEM:
        DEVLOG_ERROR("Allocation error loading Lua definitions\n");
        break;
      default:
        DEVLOG_ERROR("Unknown error loading Lua definitions\n");
    }
    return false;
  }
  if(!call(0, 1)) {
    DEVLOG_ERROR("Error loading definitions\n");
    return false;
  }
  return true;
}

bool CLuaEngine::call(int paArgs, int paResults) {
  int err = lua_pcall(luaState, paArgs, paResults, 0);
  if(err) {
    switch(err){
      case LUA_ERRRUN:
        DEVLOG_ERROR("Runtime error calling Lua function: %s\n", lua_tostring(luaState, -1));
        break;
      case LUA_ERRMEM:
        DEVLOG_ERROR("Memory allocation error calling Lua function\n");
        break;
      case LUA_ERRERR:
        DEVLOG_ERROR("Message handler error calling Lua function\n");
        break;
      default:
        DEVLOG_ERROR("Unknown error calling Lua function\n");
    }
    return false;
  }
  return true;
}

bool CLuaEngine::luaPushAny(lua_State *paLuaState, CIEC_ANY *paValue) {
  if(!paValue) {
    return false;
  }

  switch(paValue->getDataTypeID()){
    case CIEC_ANY::e_BOOL:
      lua_pushboolean(paLuaState, static_cast<bool>(*static_cast<CIEC_BOOL*>(paValue)));
      break;
    case CIEC_ANY::e_SINT:
      lua_pushinteger(paLuaState, static_cast<TForteInt8>(*static_cast<CIEC_SINT*>(paValue)));
      break;
    case CIEC_ANY::e_INT:
      lua_pushinteger(paLuaState, static_cast<TForteInt16>(*static_cast<CIEC_INT*>(paValue)));
      break;
    case CIEC_ANY::e_DINT:
      lua_pushinteger(paLuaState, static_cast<TForteInt32>(*static_cast<CIEC_DINT*>(paValue)));
      break;
    case CIEC_ANY::e_LINT:
      lua_pushinteger(paLuaState, static_cast<lua_Integer>(static_cast<TForteInt64>(*static_cast<CIEC_LINT*>(paValue))));
      break;
    case CIEC_ANY::e_USINT:
      lua_pushinteger(paLuaState, static_cast<TForteUInt8>(*static_cast<CIEC_USINT*>(paValue)));
      break;
    case CIEC_ANY::e_UINT:
      lua_pushinteger(paLuaState, static_cast<TForteUInt16>(*static_cast<CIEC_UINT*>(paValue)));
      break;
    case CIEC_ANY::e_UDINT:
      lua_pushinteger(paLuaState, static_cast<TForteUInt32>(*static_cast<CIEC_UDINT*>(paValue)));
      break;
    case CIEC_ANY::e_ULINT:
      lua_pushinteger(paLuaState, static_cast<lua_Integer>(static_cast<TForteUInt64>(*static_cast<CIEC_ULINT*>(paValue))));
      break;
    case CIEC_ANY::e_BYTE:
      lua_pushinteger(paLuaState, static_cast<TForteByte>(*static_cast<CIEC_BYTE*>(paValue)));
      break;
    case CIEC_ANY::e_WORD:
      lua_pushinteger(paLuaState, static_cast<TForteWord>(*static_cast<CIEC_WORD*>(paValue)));
      break;
    case CIEC_ANY::e_DWORD:
      lua_pushinteger(paLuaState, static_cast<TForteDWord>(*static_cast<CIEC_DWORD*>(paValue)));
      break;
    case CIEC_ANY::e_LWORD:
      lua_pushinteger(paLuaState, static_cast<lua_Integer>(static_cast<TForteLWord>(*static_cast<CIEC_LWORD*>(paValue))));
      break;
    case CIEC_ANY::e_DATE:
      lua_pushinteger(paLuaState, static_cast<lua_Integer>(static_cast<TForteUInt64>(*static_cast<CIEC_DATE*>(paValue))));
      break;
    case CIEC_ANY::e_TIME_OF_DAY:
      lua_pushinteger(paLuaState, static_cast<lua_Integer>(static_cast<TForteUInt64>(*static_cast<CIEC_TIME_OF_DAY*>(paValue))));
      break;
    case CIEC_ANY::e_DATE_AND_TIME:
      lua_pushinteger(paLuaState, static_cast<lua_Integer>(static_cast<TForteUInt64>(*static_cast<CIEC_DATE_AND_TIME*>(paValue))));
      break;
    case CIEC_ANY::e_TIME:
      lua_pushinteger(paLuaState, static_cast<lua_Integer>(static_cast<TForteInt64>(*static_cast<CIEC_TIME*>(paValue))));
      break;
    case CIEC_ANY::e_REAL:
      lua_pushnumber(paLuaState, static_cast<TForteFloat>(*static_cast<CIEC_REAL*>(paValue)));
      break;
    case CIEC_ANY::e_LREAL:
      lua_pushnumber(paLuaState, static_cast<TForteDFloat>(*static_cast<CIEC_LREAL*>(paValue)));
      break;
    case CIEC_ANY::e_STRING:
      lua_pushstring(paLuaState, static_cast<CIEC_STRING*>(paValue)->getValue());
      break;
    case CIEC_ANY::e_WSTRING:
      lua_pushstring(paLuaState, static_cast<CIEC_WSTRING*>(paValue)->getValue());
      break;
    case CIEC_ANY::e_ARRAY:
      return luaPushArray(paLuaState, static_cast<CIEC_ARRAY*>(paValue));
    default:
      DEVLOG_ERROR("Conversion to Lua datatype unsupported for type with id %s\n", paValue->getDataTypeID());
      lua_pushnil(paLuaState);
  }

  return true;
}

bool CLuaEngine::luaGetAny(lua_State *paLuaState, CIEC_ANY *paValue, int paIndex) {
  if(!paValue) {
    return false;
  }
  switch(paValue->getDataTypeID()){
    case CIEC_ANY::e_BOOL:
      *static_cast<CIEC_BOOL*>(paValue) = static_cast<bool>(lua_toboolean(paLuaState, paIndex));
      break;
    case CIEC_ANY::e_SINT:
      *static_cast<CIEC_SINT*>(paValue) = static_cast<TForteInt8>(lua_tointeger(paLuaState, paIndex));
      break;
    case CIEC_ANY::e_INT:
      *static_cast<CIEC_INT*>(paValue) = static_cast<TForteInt16>(lua_tointeger(paLuaState, paIndex));
      break;
    case CIEC_ANY::e_DINT:
      *static_cast<CIEC_DINT*>(paValue) = static_cast<TForteInt32>(lua_tointeger(paLuaState, paIndex));
      break;
    case CIEC_ANY::e_LINT:
      *static_cast<CIEC_LINT*>(paValue) = static_cast<TForteInt64>(lua_tointeger(paLuaState, paIndex));
      break;
    case CIEC_ANY::e_USINT:
      *static_cast<CIEC_USINT*>(paValue) = static_cast<TForteUInt8>(lua_tointeger(paLuaState, paIndex));
      break;
    case CIEC_ANY::e_UINT:
      *static_cast<CIEC_UINT*>(paValue) = static_cast<TForteUInt16>(lua_tointeger(paLuaState, paIndex));
      break;
    case CIEC_ANY::e_UDINT:
      *static_cast<CIEC_UDINT*>(paValue) = static_cast<TForteUInt32>(lua_tointeger(paLuaState, paIndex));
      break;
    case CIEC_ANY::e_ULINT:
      *static_cast<CIEC_ULINT*>(paValue) = static_cast<TForteUInt64>(lua_tointeger(paLuaState, paIndex));
      break;
    case CIEC_ANY::e_BYTE:
      *static_cast<CIEC_BYTE*>(paValue) = static_cast<TForteByte>(lua_tointeger(paLuaState, paIndex));
      break;
    case CIEC_ANY::e_WORD:
      *static_cast<CIEC_WORD*>(paValue) = static_cast<TForteWord>(lua_tointeger(paLuaState, paIndex));
      break;
    case CIEC_ANY::e_DWORD:
      *static_cast<CIEC_DWORD*>(paValue) = static_cast<TForteDWord>(lua_tointeger(paLuaState, paIndex));
      break;
    case CIEC_ANY::e_LWORD:
      *static_cast<CIEC_LWORD*>(paValue) = static_cast<TForteLWord>(lua_tointeger(paLuaState, paIndex));
      break;
    case CIEC_ANY::e_DATE:
      *static_cast<CIEC_DATE*>(paValue) = static_cast<TForteUInt64>(lua_tointeger(paLuaState, paIndex));
      break;
    case CIEC_ANY::e_TIME_OF_DAY:
      *static_cast<CIEC_TIME_OF_DAY*>(paValue) = static_cast<TForteUInt64>(lua_tointeger(paLuaState, paIndex));
      break;
    case CIEC_ANY::e_DATE_AND_TIME:
      *static_cast<CIEC_DATE_AND_TIME*>(paValue) = static_cast<TForteUInt64>(lua_tointeger(paLuaState, paIndex));
      break;
    case CIEC_ANY::e_TIME:
      *static_cast<CIEC_TIME*>(paValue) = static_cast<TForteInt64>(lua_tointeger(paLuaState, paIndex));
      break;
    case CIEC_ANY::e_REAL:
      *static_cast<CIEC_REAL*>(paValue) = static_cast<TForteFloat>(lua_tonumber(paLuaState, paIndex));
      break;
    case CIEC_ANY::e_LREAL:
      *static_cast<CIEC_LREAL*>(paValue) = static_cast<TForteDFloat>(lua_tonumber(paLuaState, paIndex));
      break;
    case CIEC_ANY::e_STRING:
      *static_cast<CIEC_STRING*>(paValue) = lua_tostring(paLuaState, paIndex);
      break;
    case CIEC_ANY::e_WSTRING:
      *static_cast<CIEC_WSTRING*>(paValue) = lua_tostring(paLuaState, paIndex);
      break;
    case CIEC_ANY::e_ARRAY:
      return luaGetArray(paLuaState, static_cast<CIEC_ARRAY*>(paValue), paIndex);
    default:
      DEVLOG_ERROR("Conversion from Lua datatype unsupported for type with id %s\n", paValue->getDataTypeID());
      return false;
  }

  return true;
}

bool CLuaEngine::luaPushArray(lua_State *paLuaState, CIEC_ARRAY* paArray) {
  if(!paArray) {
    return false;
  }
  lua_createtable(paLuaState, paArray->size(), 0);
  for(int i = paArray->size(); i > 0; i--) {
    if(!luaPushAny(paLuaState, (*paArray)[(TForteUInt16) (i - 1)])) { // index starts at 0
      lua_pop(paLuaState, 1); // pop table
      return false;
    }
    lua_rawseti(paLuaState, -2, i); // index starts at 1
  }
  return true;
}

bool CLuaEngine::luaGetArray(lua_State *paLuaState, CIEC_ARRAY* paArray, int paIndex) {
  if(!paArray || !lua_istable(paLuaState, paIndex)) {
    return false;
  }
  for(int i = paArray->size(); i > 0; i--) {
    lua_rawgeti(paLuaState, paIndex, i); // index starts at 1
    bool res = luaGetAny(paLuaState, (*paArray)[(TForteUInt16) (i - 1)], -1); // index starts at 0
    lua_pop(paLuaState, 1); // pop element
    if(!res) {
      return false;
    }
  }
  return true;
}
