//-------------------------------------------------------------------------------------------------
// <copyright file="LWEventLua.cpp" company="LiquidRobotics">
//  Copyright (c) Liquid Robotics Corporation, confidential and proprietary.  All rights reserved. 
// </copyright>
//
// <summary>
//	-- not currently used --
//	Implementation of LWEventLua class, an implementation of metatable methods exposed
//	to lua.  The event has an associated IEventBasePtr with which the methods
//	interact.
//	-- not currently used --
// </summary>
//
// <owner>Mike Cookson</owner>
//-------------------------------------------------------------------------------------------------

#include "LWEventLua.h"
#include "TelemetryEventBase.h"

using namespace std;
using namespace LiquidRobotics::SMACFramework;
using namespace LiquidRobotics::EventLib;

namespace LiquidRobotics {
namespace LuaWrapper {

const Luna2<LWEventLua>::RegType LWEventLua::registrations[] =
{
	{ "Duplicate",			&LWEventLua::Duplicate },
	{ "GetCreationTime",	&LWEventLua::GetCreationTime },
	{ "SetCreationTime",	&LWEventLua::SetCreationTime },
	{ "GetEventName",		&LWEventLua::GetEventName },
	{ "GetEventType",		&LWEventLua::GetEventType },
	{ "GetProperty",		&LWEventLua::GetProperty },
	{ "HasProperty",		&LWEventLua::HasProperty },
	{ "SetProperty",		&LWEventLua::SetProperty },
	{ "GetPropertyNames",	&LWEventLua::GetPropertyNames },
	{ "GetPropertyType",	&LWEventLua::GetPropertyType },
	{ "GetSourceAddress",	&LWEventLua::GetSourceAddress },
	{ "GetSourceBoardID",	&LWEventLua::GetSourceBoardID },
	{ "GetSourceTaskID",	&LWEventLua::GetSourceTaskID },
	{ "GetLocation",		&LWEventLua::GetLocation },
	{ "SetLocation",		&LWEventLua::SetLocation },
	{ "RemoveProperty",		&LWEventLua::RemoveProperty },
	{ "ToTable",			&LWEventLua::ToTable },
	{ "Transmit",			&LWEventLua::Transmit },
};

const size_t LWEventLua::registrationCount = sizeof(registrations)/sizeof(registrations[0]);

inline void LogAndThrow(stringstream& s, Logger* pLogger)
{
	string m = s.str();
	if (pLogger) Logger::Locked(*pLogger)->Error << m << endl;
	throw runtime_error(m);
}

inline string tostring(LuaInstance& L, int stackOffset)
{
	LuaInstance::StringPtr reading = L.tolstring(stackOffset);
	if (!reading)
		throw runtime_error("failed to read string at specified offset");
	return *reading;
}

template<typename T> void ValidateArrayTable(LuaInstance& L, int stackOffset)
{
	LuaInstance::StackStateHolder holder(L);
	T t(L);

	if (!L.istable(stackOffset))
		throw runtime_error("not a table");

	L.pushnil();

	if (L.next(stackOffset))
	{
		t.ValidateEntry(-1);

		const int numElems = L.objlen(stackOffset);

		do
		{
			if (!L.isnumber(-2))
				throw runtime_error("table indices may only be numeric");

			LuaInstance::Number idxNum = L.tonumber(-2);

			if ((idxNum < 1) || (idxNum > numElems))
			{
				stringstream msg;
				msg << "array index " << idxNum << " is out of range for a table of " << numElems << " elements";
				string m = msg.str();
				throw runtime_error(m);
			}

			t.ValidateEntry(-1);
			L.pop(2);

		} while(L.next(stackOffset) != 0);
	}
}

struct VATIntegerTable
{
	VATIntegerTable(LuaInstance& _L)
	: L(_L)
	{ }

	void ValidateEntry(int stackIndex)
	{
		if (L.type(stackIndex) != LUA_TNUMBER)
			throw runtime_error("table may contain only numbers");
	}

	LuaInstance& L;
};

typedef VATIntegerTable VATNumberTable;

LWEventLua::LWEventLua(LuaInstance& L)		// will not be called directly from lua since other information is required
: pController(NULL)
, pLogger(NULL)
{
}

LWEventLua::~LWEventLua()
{
}

void LWEventLua::Initialize(IEventBasePtr _eventPtr, LWControllerBase0* _pController, Logger* _pLogger)
{
	eventPtr = _eventPtr;
	pController = _pController;
	pLogger = _pLogger;
}

string LWEventLua::GetClassName()
{
	return "SMCEvent";
}

int LWEventLua::GetRegistrations(const Luna2<LWEventLua>::RegType*& regBase)
{
	regBase = registrations;
	return registrationCount;
}

void LWEventLua::PreRegistration(LuaInstance& L)
{
}

int LWEventLua::Duplicate(LuaInstance& L)			// Duplicate():event
{
	LWEventLua* luaep = NULL;
	int result = 0;

	try
	{
		IEventBase::Ptr newEvt = eventPtr->Duplicate();
		luaep = new LWEventLua(L);
		luaep->Initialize(newEvt, pController, pLogger);
		Luna2<LWEventLua>::Constructor(L, luaep);
		luaep = NULL;
		result = 1;

	}
	catch(exception& ex)
	{
		if (luaep)
			delete luaep;

		stringstream msg;
		msg << "Duplicate(): caught " << ex.what();
		LogAndThrow(msg, pLogger);
	}
	catch(...)
	{
		if (luaep)
			delete luaep;

		stringstream msg;
		msg << "Duplicate(): caught generic exception";
		LogAndThrow(msg, pLogger);
	}

	return result;
}

int LWEventLua::GetCreationTime(LuaInstance& L)		// GetCreationTime():time_t
{
	typedef TelemetryEventBase TEB;
	timeval tv = { 0, 0 };
	int result = 0;

	try
	{
		const TEB& teb = dynamic_cast<TEB&>(*eventPtr);
		tv = teb.GetGenerationTime();
		L.pushnumber(tv.tv_sec);
		result = 1;
	}
	catch(bad_cast& ex)
	{
		if (eventPtr->ContainsProperty(TEB::T_GenerationTimeTagSec))
		{
			tv.tv_sec = eventPtr->GetProperty(TEB::T_GenerationTimeTagSec).GetUInt32();
			L.pushnumber(tv.tv_sec);
			result = 1;
		}
	}

	return result;
}

int LWEventLua::SetCreationTime(LuaInstance& L)		// SetCreationTime(time_t or {time_t,microseconds} or nil or blank)
{
	typedef TelemetryEventBase TEB;
	timeval tv = { 0, 0 };

	if ((L.gettop() < 1) || L.isnil(-1))
		gettimeofday(&tv, NULL);
	else if (L.istable(-1))
	{
		if (L.objlen(-1) != 2)
			throw runtime_error("SetCreationTime: time table must contain 2 entries");

		ValidateArrayTable<VATIntegerTable>(L, -1);

		L.rawgeti(-1, 1);
		L.rawgeti(-2, 2);
		tv.tv_sec = L.tointeger(-2);
		tv.tv_usec = L.tointeger(-1);
		L.pop(2);
	}
	else
		tv.tv_sec = L.tointeger(-1);

	try
	{
		TEB& teb = dynamic_cast<TEB&>(*eventPtr);
		teb.SetGenerationTime(tv);
	}
	catch(bad_cast& ex)
	{
		IEventBase::PropertyValuePtr secs = eventPtr->CreateValue();
		secs->SetUInt32(tv.tv_sec);
		eventPtr->SetProperty(TEB::T_GenerationTimeTagSec, *secs);

		IEventBase::PropertyValuePtr usecs = eventPtr->CreateValue();
		usecs->SetUInt32(tv.tv_usec);
		eventPtr->SetProperty(TEB::T_GenerationTimeTagMicroSec, *usecs);
	}

	return 0;
}

int LWEventLua::GetEventName(LuaInstance& L)		// GetEventName():string
{
	string result = eventPtr->GetEventName();
	L.pushlstring(result);
	return 1;
}

int LWEventLua::GetEventType(LuaInstance& L)		// GetEventType():{string type-name, string domain-name)
{
	EventType etype = eventPtr->GetEventType();
	L.newtable();
	L.pushlstring(etype.GetTypeName());
	L.rawseti(-2, 1);
	L.pushlstring(etype.GetDomainName());
	L.rawseti(-2, 2);
	return 1;
}

int LWEventLua::GetProperty(LuaInstance& L)			// GetProperty(string propName[, string format]):propValue
{
	return 0;
}

int LWEventLua::HasProperty(LuaInstance& L)			// HasProperty(string propName):boolean
{
	string propName;

	if ((L.gettop() >= 1) && L.isstring(-1))
		propName = tostring(L, -1);
	else
		throw runtime_error("HasProperty: unexpected arguments or types");

	L.pushboolean(!!eventPtr->ContainsProperty(propName));
	return 1;
}

int LWEventLua::SetProperty(LuaInstance& L)			// SetProperty(string propName, propValue[, string format])
{
	return 0;
}

int LWEventLua::GetPropertyNames(LuaInstance& L)	// GetPropertyNames():{..strings..}
{
	typedef IEventBase::PropertyNameArrayType PNA;
	typedef IEventBase::PropertyNameArrayPtr PNAP;

	PNAP props = eventPtr->GetPropertyNames();
	int idx = 0;

	L.newtable();

	if (props)
		for (PNA::const_iterator ci = props->begin(); ci != props->end(); ++ci)
		{
			L.pushlstring(*ci);
			L.rawseti(-2, ++idx);
		}

	return 1;
}

int LWEventLua::GetPropertyType(LuaInstance& L)		// GetPropertyType(string propName):string
{
	typedef IEventBase::PropertyValueType PVT;
	string propName;
	string propType;
	int result = 0;

	if ((L.gettop() >= 1) && L.isstring(-1))
		propName = tostring(L, -1);
	else
		throw runtime_error("GetPropertyType: unexpected arguments or types");

	if (eventPtr->ContainsProperty(propName))
	{
		PVT::StorageType storageType = eventPtr->GetProperty(propName, false).GetStorageType();

		propType = PVT::ToString(storageType);
		L.pushlstring(propType);
		result = 1;
	}

	return result;
}

int LWEventLua::GetSourceAddress(LuaInstance& L)	// GetSourceAddress():number
{
	typedef TelemetryEventBase TEB;
	TEB::PropertyValueType::UInt8 boardID = 0, taskID = 0;
	int result = 0;

	try
	{
		const TEB& teb = dynamic_cast<TEB&>(*eventPtr);

		if (teb.HasGeneratorAddress())
		{
			teb.GetGeneratorAddress(boardID, taskID);
			result = 1;
		}
	}
	catch(bad_cast& ex)
	{
		if (eventPtr->ContainsProperty(TEB::T_BoardIDTag) && eventPtr->ContainsProperty(TEB::T_TaskIDTag))
		{
			boardID = eventPtr->GetProperty(TEB::T_BoardIDTag).GetUInt8();
			taskID = eventPtr->GetProperty(TEB::T_TaskIDTag).GetUInt8();
			result = 1;
		}
	}

	if (result)
	{
		TEB::PropertyValueType::UInt16 addr = boardID;
		addr <<= 8;
		addr |= taskID;
		L.pushinteger(addr);
	}

	return result;
}

int LWEventLua::GetSourceBoardID(LuaInstance& L)	// GetSourceBoardID():number
{
	int result = GetSourceAddress(L);

	if (result)
	{
		typedef TelemetryEventBase TEB;
		TEB::PropertyValueType::UInt16 addr = L.tointeger(-1);
		L.pop(1);

		TEB::PropertyValueType::UInt8 boardID = (addr >> 8);
		L.pushinteger(boardID);
	}

	return result;
}

int LWEventLua::GetSourceTaskID(LuaInstance& L)		// GetSourceTaskID():number
{
	typedef TelemetryEventBase TEB;
	int result = GetSourceAddress(L);

	if (result)
	{
		TEB::PropertyValueType::UInt16 addr = L.tointeger(-1);
		L.pop(1);

		TEB::PropertyValueType::UInt8 taskID = (addr & 0xFF);
		L.pushinteger(taskID);
	}

	return result;
}

int LWEventLua::GetLocation(LuaInstance& L)			// GetLocation():{number latitude, number longitude}
{
	typedef TelemetryEventBase TEB;
	int result = 0;
	TEB::PropertyValueType::DoubleFloat lat = 0, lon = 0;

	try
	{
		const TEB& teb = dynamic_cast<TEB&>(*eventPtr);

		if (teb.HasGenerationLocation())
		{
			teb.GetGenerationLocation(lat, lon);
			result = 1;
		}
	}
	catch(bad_cast& ex)
	{
		if (eventPtr->ContainsProperty(TEB::T_LatitudeTag) && eventPtr->ContainsProperty(TEB::T_LongitudeTag))
		{
			lat = eventPtr->GetProperty(TEB::T_LatitudeTag).GetDoubleFloat();
			lon = eventPtr->GetProperty(TEB::T_LongitudeTag).GetDoubleFloat();
			result = 1;
		}
	}

	if (result)
	{
		L.newtable();
		L.pushnumber(lat);
		L.rawseti(-1, 1);
		L.pushnumber(lon);
		L.rawseti(-1, 2);
	}

	return result;
}

int LWEventLua::SetLocation(LuaInstance& L)			// SetLocation(number latitude, number longitude)
{
	typedef TelemetryEventBase TEB;
	TEB::PropertyValueType::DoubleFloat lat = 0, lon = 0;

	if ((L.gettop() >= 1) && L.istable(-1))
	{
		if (L.objlen(-1) != 2)
			throw runtime_error("SetLocation: table parameter must have two entries");

		ValidateArrayTable<VATNumberTable>(L, -1);
		L.rawgeti(-1, 1);
		L.rawgeti(-2, 2);
		lat = L.tonumber(-2);
		lon = L.tonumber(-1);
		L.pop(2);
	}
	else if ((L.gettop() >= 2) && L.isnumber(-2) && L.isnumber(-1))
	{
		lat = L.tonumber(-2);
		lon = L.tonumber(-1);
	}
	else
		throw runtime_error("SetLocation: unexpected parameter count or types");

	try
	{
		TEB& teb = dynamic_cast<TEB&>(*eventPtr);
		teb.SetGenerationLocation(lat, lon);
	}
	catch(bad_cast& ex)
	{
		IEventBase::PropertyValuePtr latf = eventPtr->CreateValue();
		latf->SetDoubleFloat(lat);
		eventPtr->SetProperty(TEB::T_LatitudeTag, *latf);

		IEventBase::PropertyValuePtr lonf = eventPtr->CreateValue();
		lonf->SetDoubleFloat(lon);
		eventPtr->SetProperty(TEB::T_LongitudeTag, *lonf);
	}

	return 0;
}

int LWEventLua::RemoveProperty(LuaInstance& L)		// RemoveProperty(string propName)
{
	string propName;

	if ((L.gettop() >= 1) && L.isstring(-1))
		propName = tostring(L, -1);
	else
		throw runtime_error("RemoveProperty: unexpected arguments or types");

	if (eventPtr->ContainsProperty(propName))
		eventPtr->ClearProperty(propName);

	return 0;
}

int LWEventLua::ToTable(LuaInstance& L)				// ToTable():{}
{
	stringstream msg;
	msg << "ToTable(): not supported yet";
	LogAndThrow(msg, pLogger);
	return 0;
}

int LWEventLua::Transmit(LuaInstance& L)			// Transmit()
{
	if (!pController)
	{
		stringstream msg;
		msg << "Transmit(): controller was not initialized";
		LogAndThrow(msg, pLogger);
	}

	pController->TransmitEvent(*eventPtr);
	return 0;
}

}
}
