#ifdef _WIN32
#include <windows.h>
#else
#include <signal.h>
#include <unistd.h>
#endif
#include "InertiaSDK.h"

#include "InertiaSample.h"


#ifdef _WIN32
int wmain()
{
	SetConsoleCtrlHandler((PHANDLER_ROUTINE)ConsoleHandler, TRUE);
#else
int main()
{
	signal(SIGINT, SignalHandler);
	signal(SIGTSTP, SignalHandler);
#endif

	MainApp app;

	int rc = app.Run();

	app.Destruct();

	return rc;
}


void MainApp::Destruct()
{
	m_listener.reset();
	inertia::Destruct();
}


int MainApp::Run()
{
	// configurable settings
	bool async = true;          // get received samples asynchronously, or synchronous by polling
	unsigned timeout = 120;     // stop after x seconds
	bool enableLogging = false; // create and convert a logfile


	// Get available USB and Bluetooth devices, use the first one
	std::wstring connectionName;
	std::map<std::wstring, std::wstring> detectedPorts = inertia::GetSerialPortList();
	if (detectedPorts.empty()) {
		detectedPorts = inertia::GetBluetoothPortList();
	}
	if (!detectedPorts.empty()) {
		connectionName = detectedPorts.begin()->first;
	}


	// settings for connecting to a device
	inertia::ConnectionSettings connSettings;
	connSettings.serialSettings.portname = connectionName;

	// settings for creating a logfile
	inertia::LogWriterSettings logWriterSettings;
	logWriterSettings.fileSettings = inertia::LogWriterFileSettings(true, L"logfile.itlog", inertia::EXIST_DATETIME);

	// create an asynchronous listener
	m_listener.reset(new InertiaListener(async));


	// open the connection
	if (inertia::ConnectionUpdate(connSettings)) {
		inertia::ConnectionAddListener(m_listener, connSettings.GetName());
	}
	else {
		std::wcerr << "Unable to open the connection." << std::endl;
		return -1;
	}

	unsigned i = 0;
	bool isLogging = false;
	std::wstring usedLogfile;

	// main loop
	while (i < (timeout * 1000) || (timeout == 0))
	{
		// check if connection is (being) opened
		if (m_listener->GetOpenedState() == 0) {
			std::wcerr << "Connection is closed." << std::endl;
			break;
		}

		// start logging after 5 seconds
		if (enableLogging && (i % 5000) == 0 && m_listener->GetOpenedState() == 1 && !isLogging) {
			usedLogfile = SetLogging(connSettings.GetName(), logWriterSettings);
			isLogging = true;
		}

		// poll samples
		if (!async) {
			unsigned const fetchSize = 1000; // fetch up to 1000 samples

			std::vector<unsigned> addrIDs = inertia::GetNodeIDs(connSettings.GetName());
			for (unsigned const& addrID : addrIDs) {
				std::vector<inertia::Sample> const& samples = inertia::DataGetSamples(connSettings.GetName(), addrID, fetchSize);
				if (samples.size() > 0) {
					std::wclog << connSettings.GetName() << ": Polled " << samples.size() << " sample(s) from addrID " << addrID << std::endl;
				}
			}
		}

		// aborted by user
		if (!m_keepRunning) {
			break;
		}

		SleepMs(50);
		if (timeout > 0) {
			i += 50;
		}
	}


	// stop logging
	if (isLogging) {
		logWriterSettings.fileSettings.enableLogging = false;
		SetLogging(connSettings.GetName(), logWriterSettings);
	}


	// convert the logfile to csv
	if (!usedLogfile.empty() && m_keepRunning) {
		ConvertFile(usedLogfile);
	}

	return 0;
}


/*
	Example showing how to start and stop logging to a file.
*/
std::wstring MainApp::SetLogging(std::wstring const& connection, inertia::LogWriterSettings const& logWriterSettings)
{
	std::wstring usedFile;

	if (inertia::LoggingUpdate(connection, logWriterSettings)) {
		if (logWriterSettings.fileSettings.enableLogging) {
			usedFile = inertia::LoggingCheckFileName(connection, logWriterSettings);
			std::wcout << "Logging started to " << usedFile << std::endl;
		}
		else {
			std::wcout << "Logging stopped" << std::endl;
		}
	}
	else {
		std::wcerr << "SetLogging failed." << std::endl;
	}

	return usedFile;
}


/*
	Example showing how to convert an .itlog file to a .csv file.
*/
void MainApp::ConvertFile(std::wstring const& filename)
{
	std::wcout << "Start converting..." << std::endl;

	inertia::ConvertFileSettings convertSettings;
	std::wstring csvFilename = filename.substr(0, filename.find_last_of('.')) + L".csv";
	convertSettings.logWriterSettings.fileSettings = inertia::LogWriterFileSettings(true, csvFilename, inertia::EXIST_AUTONUMBER);
	std::wstring createdFile;
	inertia::ConvertFileStart(filename, convertSettings, createdFile);

	while (inertia::ConvertFileIsActive(filename)) {
		SleepMs(100);
		if (!m_keepRunning) {
			std::wcout << "Aborting conversion" << std::endl;
			inertia::ConvertFileStop(filename);
		}
	}

	std::wcout << "Converting finished" << std::endl;
}


/*
	Example showing how to enable or disable transmission of a sensor-node.
*/
void MainApp::ToggleTransmit(std::wstring const& connection)
{
	std::vector<unsigned> addrIDs = inertia::GetNodeIDs(connection);

	for (unsigned const& addrID : addrIDs)
	{
		inertia::InertiaConfig const lastConfig = inertia::GetConfiguration(connection, addrID);
		if (!lastConfig.HasConfig(inertia::CONFIG_GLOBAL_SAMPLING)) {
			continue;
		}

		inertia::GlobalSamplingConfig const& globalOld = lastConfig.GetGlobalSamplingConfig();
		inertia::GlobalSamplingConfig* globalNew = new inertia::GlobalSamplingConfig(globalOld);
		globalNew->transmitEnable = !globalNew->transmitEnable;

		inertia::SensorConfigPtr globalConf(globalNew);
		inertia::InertiaConfig config;
		config.AddConfig(globalConf);

		std::wcout << "Sending configuration to " << addrID << ": " << globalNew->ToString() << std::endl;
		inertia::SendConfiguration(connection, config, addrID);
	}
}


/*
	Example showing how to change the accelerometer sensitivity of a ProMove-mini.
*/
void MainApp::ToggleAccelerometerRange(std::wstring const& connection)
{
	std::vector<unsigned> addrIDs = inertia::GetNodeIDs(connection);

	for (unsigned const& addrID : addrIDs)
	{
		inertia::InertiaConfig const lastConfig = inertia::GetConfiguration(connection, addrID);
		unsigned sensorID = lastConfig.GetSensorID(inertia::VALUE_TYPE_ACCEL);
		if (!lastConfig.HasConfig(inertia::CONFIG_TRIPLE_AXIS, (int)sensorID)) {
			continue;
		}

		inertia::TripleAxisConfig const& tripleOld = lastConfig.GetTripleAxisConfig(sensorID);
		inertia::AxisConfigPtr axisconfig(new inertia::TripleAxisConfig(tripleOld));
		inertia::TripleAxisConfig *triple = dynamic_cast<inertia::TripleAxisConfig*> (axisconfig.get());

		if (triple->sensitivity > 2)
			triple->sensitivity = 2;
		else
			triple->sensitivity = 8;

		inertia::SensorConfigPtr sensor(new inertia::GenericSensorConfig(inertia::CONFIG_TRIPLE_AXIS));
		inertia::GenericSensorConfig *gsc = dynamic_cast<inertia::GenericSensorConfig*> (sensor.get());
		gsc->configRevision = lastConfig.GetGenericSensorConfig(inertia::CONFIG_TRIPLE_AXIS).configRevision;
		gsc->configs.push_back(axisconfig);

		inertia::InertiaConfig config;
		config.AddConfig(sensor);

		std::wcout << "Sending configuration to " << addrID << ": " << triple->ToString() << std::endl;
		inertia::SendConfiguration(connection, config, addrID);
	}
}


/*
	Example showing how to send a command (get list of flash files) to all sensor-nodes of a connection.
*/
void MainApp::SendCommand(std::wstring const& connection)
{
	std::vector<unsigned> addrIDs = inertia::GetNodeIDs(connection);

	for (unsigned const& addrID : addrIDs)
	{
		inertia::InertiaCommandPtr commandPtr(new inertia::FlashListExRequest());
		inertia::InertiaCommands command(commandPtr);
		std::wcout << "Sending command to " << addrID << ": " << command.ToString() << std::endl;
		inertia::SendCommand(connection, command, addrID);
	}
}


/*
	InertiaListener handles asynchronous messages send by the library.
*/
InertiaListener::InertiaListener(bool const _async)
	: async(_async)
	, openedState(-1)
{}

void InertiaListener::StatusUpdate(std::wstring const& source, const bool isOpen, std::wstring const& feedback) const
{
	std::wcout << "StatusUpdate from " << source << ": isOpen=" << (isOpen ? "true" : "false") << ", feedback=" << feedback << std::endl;
	openedState = isOpen ? 1 : 0;
}

void InertiaListener::Received(std::wstring const& source, const unsigned addrID, inertia::ConfigVectorPtr const& configs, inertia::CommandVectorPtr const& commands, inertia::SampleVectorPtr const& samples) const
{
	if (!async)
		return;

	for (unsigned i = 0; configs.get() != nullptr && i < configs->size(); ++i) {
		std::wcout << "Received config from " << source << ", addrID=" << addrID << ", config=" << (*configs)[i].ToString() << std::endl;
	}

	for (unsigned i = 0; commands.get() != nullptr && i < commands->size(); ++i) {
		std::wcout << "Received command from " << source << ", addrID=" << addrID << ", command=" << (*commands)[i].ToString() << std::endl;
	}

	if (samples.get() != nullptr && samples->size() > 0) {
		std::wclog << source << ": Received " << samples->size() << " sample(s) from addrID " << addrID << std::endl;
	}
}

int InertiaListener::GetOpenedState() const
{
	return openedState;
}


void MainApp::SleepMs(unsigned const ms)
{
#ifdef _WIN32
	Sleep(ms);
#else
	usleep(ms * 1000);
#endif
}


#ifdef _WIN32

BOOL WINAPI ConsoleHandler(DWORD CEvent)
{
	switch (CEvent)
	{
		case CTRL_C_EVENT:
		case CTRL_BREAK_EVENT:
		case CTRL_CLOSE_EVENT:
		case CTRL_LOGOFF_EVENT:
		case CTRL_SHUTDOWN_EVENT:
			//Returning would make the process exit!
			//We just make the handler sleep until the main thread exits,
			//or until the maximum execution time for this handler is reached.
			m_keepRunning = false;
			MainApp::SleepMs(10000);
			break;
		default:
			break;
	}

	return TRUE;
}

#else

void SignalHandler(int s)
{
	(void)s;
	m_keepRunning = false;
}

#endif
