/**
 * server/src/lua/event.cpp - Lua event wrapper
 *
 * This file is part of the traintastic source code.
 *
 * Copyright (C) 2021-2022 Reinder Feenstra
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "event.hpp"
#include "to.hpp"
#include "checkarguments.hpp"
#include "error.hpp"
#include "eventhandler.hpp"
#include "sandbox.hpp"
#include "../core/abstractevent.hpp"
#include "../core/object.hpp"

namespace Lua {

struct EventData
{
  ObjectPtrWeak object;
  AbstractEvent& event;

  EventData(AbstractEvent& _event) :
    object{_event.object().weak_from_this()},
    event{_event}
  {
  }
};

AbstractEvent& Event::check(lua_State* L, int index)
{
  auto& data = *static_cast<EventData*>(luaL_checkudata(L, index, metaTableName));
  if(!data.object.expired())
    return data.event;

  errorDeadObject(L);
}

AbstractEvent* Event::test(lua_State* L, int index)
{
  auto* data = static_cast<EventData*>(luaL_testudata(L, index, metaTableName));
  if(!data)
    return nullptr;
  if(!data->object.expired())
    return &data->event;

  errorDeadObject(L);
}

void Event::push(lua_State* L, AbstractEvent& value)
{
  new(lua_newuserdata(L, sizeof(EventData))) EventData(value);
  luaL_getmetatable(L, metaTableName);
  lua_setmetatable(L, -2);
}

void Event::registerType(lua_State* L)
{
  luaL_newmetatable(L, metaTableName);
  lua_pushcfunction(L, __index);
  lua_setfield(L, -2, "__index");
  lua_pushcfunction(L, __call);
  lua_setfield(L, -2, "__call");
  lua_pushcfunction(L, __gc);
  lua_setfield(L, -2, "__gc");
  lua_pop(L, 1);
}

int Event::__index(lua_State* L)
{
  auto& event = check(L, 1);
  auto name = to<std::string_view>(L, 2);

  if(name == "connect")
  {
    push(L, event);
    lua_pushcclosure(L, connect, 1);
  }
  else if(name == "disconnect")
  {
    push(L, event);
    lua_pushcclosure(L, disconnect, 1);
  }
  else
    lua_pushnil(L);

  return 1;
}

int Event::__call(lua_State* L)
{
  checkArguments(L, 2, 3);
  auto& event = check(L, 1);
  auto handler = std::make_shared<EventHandler>(event, L, 2);
  event.connect(handler);
  lua_pushinteger(L, Sandbox::getStateData(L).registerEventHandler(handler));
  return 1;
}

int Event::__gc(lua_State* L)
{
  static_cast<EventData*>(lua_touserdata(L, 1))->~EventData();
  return 0;
}

int Event::connect(lua_State* L)
{
  checkArguments(L, 1, 2);

  auto& event = check(L, lua_upvalueindex(1));
  auto handler = std::make_shared<EventHandler>(event, L);
  event.connect(handler);
  lua_pushinteger(L, Sandbox::getStateData(L).registerEventHandler(handler));

  return 1;
}

int Event::disconnect(lua_State* L)
{
  checkArguments(L, 1);

  auto& event = check(L, lua_upvalueindex(1));
  auto handler = Sandbox::getStateData(L).getEventHandler(luaL_checkinteger(L, 1));

  lua_pushboolean(L, handler && &handler->event() == &event && handler->disconnect());

  return 1;
}

}
