#include "FlashSender.h"

//  Copyright (C) 2009 Georg Kaindl
//  This file is part of Touché.
//
//  Touché is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as
//  published by the Free Software Foundation, either version 3 of
//  the License, or (at your option) any later version.
//
//  Touché 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 Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with Touché. If not, see <http://www.gnu.org/licenses/>.


#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if !defined(WIN32)
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/time.h>
#include <fcntl.h>

#if defined(__APPLE__)
#include <mach/mach.h>
#include <mach/mach_time.h>
#else
#include <utmpx.h>
#endif

#endif

#define FLASHLC_SHM_SIZE				(64528)	// This is what Flash always uses as size
#define	FLASHLC_SHM_LISTENERS_OFFSET	(40976)	// Another "magic number"
#define MAX_LISTENER_NAME_LEN			(64)
#define MAX_LISTENER_METHOD_NAME		(64)

#define	PROTOCOL_NAME					("localhost")

#define WIN32_SEMAPHORE_NAME			TEXT("MacromediaMutexOmega")
#define WIN32_SHMEM_NAME				TEXT("MacromediaFMOmega")
#define POSIX_SEMAPHORE_NAME			("MacromediaSemaphoreDig")
#define	POSIX_SEMAPHORE_INITIAL_VALUE	(10)

#define	AMF_TYPE_STRING				(0x02)
#define AMF_TYPE_AMF3OBJ			(0x11)
#define AMF3_TYPE_BYTEARRAY			(0x0C)

#define AMF_ENVELOPE_LEN				(16)
#define AMF_ENVELOPE_TIMESTAMP_POS		(8)
#define AMF_ENVELOPE_SIZE_POS			(12)

void _TFLCSLockSemaphore(TFLCSLocalConnection_t* connection);
void _TFLCSUnlockSemaphore(TFLCSLocalConnection_t* connection);

// returns non-zero if the given connection is in a usable state, zero otherwise
int _TFLCSConnectionStructureIsValidForUse(TFLCSLocalConnection_t* connection);

// On WIN32, we cannot create the shared memory and semaphore ourselves, because
// we don't know the exact parameters. Therefore, if Flash is not running when we
// try to connect, we'll try again whenever we should send data, until we finally
// manage to connect.
int _TFLCSDelayedConnect(TFLCSLocalConnection_t* connection);

#define	ENSURE_CONNECTION_UP(c, rv)		do { if (!(c)->open && !_TFLCSDelayedConnect((c))) return (rv); } while (0)

u_int32_t _TFLCSKnownDarwinKeys[] = { 0x53414e44, 0 };

TFLCSError_t	TFLCSErrno = TFLCSErrorSuccess;

TFLCSLocalConnection_t* TFLCSConnect(const char* listenerName,
									 const char* listenerMethod,
									 void* shmemKey,
									 void* semaphoreKey)
{
	TFLCSLocalConnection_t* connection = NULL;
	
	if (NULL == listenerName || NULL == listenerMethod ||
		strlen(listenerName) > TFLCS_LISTENER_NAME_MAX_LEN ||
		strlen(listenerMethod) > TFLCS_LISTENER_METHOD_MAX_LEN) {
		TFLCSErrno = TFLCSErrorInvalidArgument;
		return NULL;
	}
	
	connection = (TFLCSLocalConnection_t*)malloc(sizeof(TFLCSLocalConnection_t));
	if (NULL == connection) {
		TFLCSErrno = TFLCSErrorOutOfMemory;
		goto errorReturn;
	}
	
	connection->open = 0;
	
#if defined(WIN32)
	// initialize the structure
	connection->semaphore = NULL;
	connection->mapFile = NULL;
	connection->mapAddress = NULL;
	connection->data = NULL;
	
	_TFLCSDelayedConnect(connection);
#else
	// initialize the structure
	connection->semaphore = (sem_t*)SEM_FAILED;
	connection->shmid = -1;
	connection->data = (char*)-1;
	
	// we don't need a delayed connect on MacOS X, so let's just connect now!
	key_t key;
	char* semName;
	
	if (NULL != shmemKey)
		key = *(key_t*)shmemKey;
	else {
		key_t* guessedKey = (key_t*)TFLCSGuessShmemKey();
		if (NULL != guessedKey)
			key = *(key_t*)guessedKey;
		else {
			TFLCSErrno = TFLCSErrorShmemKeyNotFound;
			goto errorReturn;
		}
	}
	
	if (NULL != semaphoreKey)
		semName = (char*)semaphoreKey;
	else
		semName = (char*)POSIX_SEMAPHORE_NAME;
	
	connection->semaphore = sem_open(semName,
									 O_CREAT,
									 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH,
									 POSIX_SEMAPHORE_INITIAL_VALUE);
	if ((sem_t*)SEM_FAILED == connection->semaphore) {
		TFLCSErrno = TFLCSErrorSemaphoreCreationFailed;
		goto errorReturn;
	}
	
	if (-1 == (connection->shmid = shmget(key, FLASHLC_SHM_SIZE, 0666 | IPC_CREAT))) {
		TFLCSErrno = TFLCSErrorShmemIDNotFound;
		goto errorReturn;
	}
	
	connection->data = (char*)shmat(connection->shmid, NULL, 0);
	if ((char*)-1 == connection->data) {
		TFLCSErrno = TFLCSErrorShmemOpeningFailed;
		goto errorReturn;
	}
	
	connection->open = 1;
	
	// initialize the memory region the way Flash likes it :-)
	connection->data[0] = 1;
	connection->data[4] = 1;
#endif
	
	strncpy(connection->listenerName, listenerName, TFLCS_LISTENER_NAME_MAX_LEN);
	strncpy(connection->listenerMethod, listenerMethod, TFLCS_LISTENER_METHOD_MAX_LEN);
	
	return connection;
	
errorReturn:
	TFLCSDisconnect(connection);
	
	return NULL;
}

void TFLCSDisconnect(TFLCSLocalConnection_t* connection)
{
	if (NULL != connection) {
#if defined(WIN32)
		if (NULL != connection->semaphore)
			CloseHandle(connection->semaphore);
		
		if (NULL != connection->mapAddress)
			UnmapViewOfFile(connection->mapAddress);
		
		if (NULL != connection->mapFile)
			CloseHandle(connection->mapFile);
#else
		if ((sem_t*)SEM_FAILED != connection->semaphore)
			sem_close(connection->semaphore);
		
		if ((char*)-1 != connection->data)
			(void)shmdt(connection->data);
#endif
		free(connection);
	}
}

void TFLCSChangeListenerName(TFLCSLocalConnection_t* connection, const char* newListenerName)
{
	if (NULL != connection && NULL != newListenerName)
		strncpy(connection->listenerName, newListenerName, TFLCS_LISTENER_NAME_MAX_LEN);
}

void TFLCSChangeMethodName(TFLCSLocalConnection_t* connection, const char* newMethodName)
{
	if (NULL != connection && NULL != newMethodName)
		strncpy(connection->listenerMethod, newMethodName, TFLCS_LISTENER_METHOD_MAX_LEN);
}

int TFLCSConnectionHasConnectedClient(TFLCSLocalConnection_t* connection)
{
	ENSURE_CONNECTION_UP(connection, 0);
	
	int retval = 0;
	
	if (_TFLCSConnectionStructureIsValidForUse(connection)) {
		_TFLCSLockSemaphore(connection);
		
		int c, l = strlen(connection->listenerName);
		char* p = &connection->data[FLASHLC_SHM_LISTENERS_OFFSET];
		
		while (!retval && (char)0 != *p) {
			c = strlen(p) + 1;
			if (0 == strncmp(p, connection->listenerName, l))
				retval = 1;
			
			p += c;
		}
		
		_TFLCSUnlockSemaphore(connection);
	}
	
	return retval;
}

int TFLCSGetConnectedConnectionNames(TFLCSLocalConnection_t* connection, char* dest, int destLen)
{
	ENSURE_CONNECTION_UP(connection, 0);
	
	int retval = 0;
	
	if (NULL != dest && 0 < destLen && _TFLCSConnectionStructureIsValidForUse(connection)) {
		_TFLCSLockSemaphore(connection);
		
		int c, i=0;
		char* p = &connection->data[FLASHLC_SHM_LISTENERS_OFFSET];
		
		while ((char)0 != *p && destLen > 0) {
			c = strlen(p) + 1;
			if (0 == (i % 3)) {
				strncpy(dest, p, destLen < c ? destLen : c);
				destLen -= c;
				dest += c;
			}
			
			p += c;
			i++;
		}
		
		retval = i/3;
		
		_TFLCSUnlockSemaphore(connection);
	}
	
	return retval;
}

void* TFLCSGuessShmemKey()
{
#if defined(WIN32)
	return (void*)WIN32_SHMEM_NAME;
#else
	static int found = 0;
	static key_t key;
	
	// first, try our known keys
	int i = 0;
	while(!found && _TFLCSKnownDarwinKeys[i]) {
		if (-1 != shmget(_TFLCSKnownDarwinKeys[i], FLASHLC_SHM_SIZE, 0)) {
			found = 1;
			key = _TFLCSKnownDarwinKeys[i];
		}
		
		i++;
	}
	
	/* // magic numbers for MacOS X 10.5, probably later and earlier versions too, dunno...
	 int minid = 0xffff;
	 int maxid = 0x500000;
	 struct shmid_ds shm_info;
	 for (i=minid; !found && i<=maxid; i++) {
	 if (shmctl(i, IPC_STAT, &shm_info) < 0)
	 continue;
	 
	 if (FLASHLC_SHM_SIZE == shm_info.shm_segsz) {
	 found = 1;
	 key = shm_info.shm_perm._key;
	 }
	 } */
	
	// if we didn't find anything, set the key to a reasonable guess
	// this is necessary because the shared segment might not exist yet.
	if (!found) {
		key = _TFLCSKnownDarwinKeys[0];
		found = 1;
	}
	
	return &key;
#endif
}

u_int32_t TFLCSGetTickCount()
{
#if defined(WIN32)
	return (u_int32_t)GetTickCount();
#elif defined(__APPLE__)
	static mach_timebase_info_data_t timeBase;
	
	u_int64_t nanos = mach_absolute_time();
	
	if (0 == timeBase.denom) {
		(void)mach_timebase_info(&timeBase);
	}
	return (u_int32_t)((nanos * (u_int64_t)timeBase.numer / (u_int64_t)timeBase.denom) / (u_int64_t)1000000);
#else
	static struct timeval bt;
	static int initialized = 0;
	
	if (!initialized) {
		struct utmpx* ut;
		while (NULL != (ut = getutxent())) {
			if (BOOT_TIME == ut->ut_type) {
				memcpy(&bt, &ut->ut_tv, sizeof(struct timeval));
				break;
			}
		}
		initialized = 1;
	}
	
	struct timeval nt, btc;
	(void)gettimeofday(&nt, NULL);
	memcpy(&btc, &bt, sizeof(struct timeval));
	
	if (nt.tv_usec < btc.tv_usec) {
		u_int64_t n = (btc.tv_usec - nt.tv_usec) / 1000000 + 1;
		btc.tv_usec -= 1000000 * n;
		btc.tv_sec += n;
	}
	if (nt.tv_usec - btc.tv_usec > 1000000) {
		u_int64_t n = (btc.tv_usec - nt.tv_usec) / 1000000;
		btc.tv_usec += 1000000 * n;
		btc.tv_sec -= n;
	}
	
	nt.tv_usec = nt.tv_usec - btc.tv_usec;
	nt.tv_sec = nt.tv_sec - btc.tv_sec;
	
	return (u_int32_t)((nt.tv_sec * 1000) + (nt.tv_usec / 1000));
#endif
}

int TFLCSWriteAMF3Integer(char* buffer, int value, int pos)
{
	if (value < 0) {
		buffer[pos++] = (0x80 | ((value >> 22) & 0xff));
		buffer[pos++] = (0x80 | ((value >> 15) & 0x7f));
		buffer[pos++] = (0x80 | ((value >> 8) & 0x7f));
		buffer[pos++] = (value & 0xff);
	} else if (value <= 0x7f) {
		buffer[pos++] = value;
	} else if (value <= 0x3fff) {
		buffer[pos++] = (0x80 | ((value >> 7) & 0x7f));
		buffer[pos++] = (value & 0x7f);
	} else if (value <= 0x1fffff) {
		buffer[pos++] = (0x80 | ((value >> 14) & 0x7f));
		buffer[pos++] = (0x80 | ((value >> 7) & 0x7f));
		buffer[pos++] = (value & 0x7f);
	} else {
		buffer[pos++] = (0x80 | ((value >> 22) & 0xff));
		buffer[pos++] = (0x80 | ((value >> 15) & 0x7f));
		buffer[pos++] = (0x80 | ((value >> 8) & 0x7f));
		buffer[pos++] = (value & 0xff);
	}
	
	return pos;
}

int TFLCSWriteAMFString(char* buffer, const char * str, int pos)
{
	int len = strlen(str);
	
	buffer[pos++] = AMF_TYPE_STRING;
	buffer[pos++] = 0; // TODO: string length is badly encoded here!
	buffer[pos++] = (char)(len & 0xff);
	
	strcpy((char*)&buffer[pos], str);
	
	pos += len;
	return pos;
}

int TFLCSWriteAMF3ByteArray(char* buffer, const char* bytes, int pos, int len)
{
	// buffer[pos++] = AMF_TYPE_AMF3OBJ;
	buffer[pos++] = AMF3_TYPE_BYTEARRAY; // AMF3_TYPE_BYTE_ARRAY
	
	pos = TFLCSWriteAMF3Integer(buffer, ((len << 1) | 1), pos);
	
	int i=0;
	for(i=0; i<len; i++)
		buffer[pos++] = bytes[i];
	
	return pos;
}

int TFLCSWriteLCAMFEnvelopeHeader(TFLCSLocalConnection_t* connection)
{
	if (NULL == connection)
		return 0;
	
	u_int32_t* timestamp = (u_int32_t*)&connection->data[AMF_ENVELOPE_TIMESTAMP_POS];
	
	int pos = AMF_ENVELOPE_LEN;
	// check timestamp and size
	
	memset(connection->data, 0, AMF_ENVELOPE_LEN);
	
	connection->data[0] = 1;
	connection->data[4] = 1;
	
	*timestamp = TFLCSGetTickCount();
	
	// write connection name
	pos = TFLCSWriteAMFString(connection->data, connection->listenerName, pos);
	// write protocol
	pos = TFLCSWriteAMFString(connection->data, PROTOCOL_NAME, pos);
	
	// I have no idea what this is, but we apparently need it...
	char weirdBytes[] = {
		0x01, 0x01, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		0x40, 0x24, 0, 0, 0, 0, 0, 0, 0, 0x40, 0x08, 0, 0,
		0, 0, 0, 0};
	int i;
	for (i=0; i<31; i++)
		connection->data[pos++] = weirdBytes[i];
	
	// write method name
	pos = TFLCSWriteAMFString(connection->data, connection->listenerMethod, pos);
	
	return pos;
}

int TFLCSWriteLCAMFEnvelopeTrailer(TFLCSLocalConnection_t* connection, int pos)
{
	if (NULL == connection)
		return pos;
	
	u_int32_t* size = (u_int32_t*)&connection->data[AMF_ENVELOPE_SIZE_POS];
	
	//pos = writeAMF3ByteArray(buffer, msg, pos, strlen(msg));
	*size = pos-16;
	
	return pos;
}

int TFLCSSendByteArray(TFLCSLocalConnection_t* connection, const char* bytes, int len)
{
	ENSURE_CONNECTION_UP(connection, 0);
	
	if (NULL == bytes || 0 == len || !_TFLCSConnectionStructureIsValidForUse(connection)) {
		TFLCSErrno = TFLCSErrorInvalidArgument;
		return 0;
	}
	
	_TFLCSLockSemaphore(connection);
	
	int pos = TFLCSWriteLCAMFEnvelopeHeader(connection);
	pos = TFLCSWriteAMF3ByteArray(connection->data, bytes, pos, len);
	(void)TFLCSWriteLCAMFEnvelopeTrailer(connection, pos);
	
	_TFLCSUnlockSemaphore(connection);
	
	return 1;
}

void TFLCSDumpMemory(char* buffer, int offset, int size)
{
	int i = 0;
	int c = 0;
	char b;
	
	while (i < size) {
		while ((c < 16) && (i+c < size)) {
			b = buffer[offset+i+c];
			printf("%X%X ", b/16 & 0x0f, b & 0x0f );
			c++;
		}
		
		while (c++ < 16)
			printf("   ");
		
		c = 0;
		
		while ((c < 16) && (i+c < size)) {
			b = buffer[offset+i+c];
			if (b > 31)
				printf("%c", (char)b);
			else
				printf(".");
			c++;
		}
		
		i += 16;
		c = 0;
		printf("\n");
	}
}

void _TFLCSLockSemaphore(TFLCSLocalConnection_t* connection)
{
	if (NULL != connection) {
#if defined(WIN32)
		if (NULL != connection->semaphore)
			WaitForSingleObject(connection->semaphore, INFINITE);
#else
		if ((sem_t*)SEM_FAILED != connection->semaphore)
			sem_wait(connection->semaphore);
#endif
	}
}

void _TFLCSUnlockSemaphore(TFLCSLocalConnection_t* connection)
{
	if (NULL != connection) {
#if defined(WIN32)
		if (NULL != connection->semaphore)
			ReleaseMutex(connection->semaphore);
#else
		if ((sem_t*)SEM_FAILED != connection->semaphore)
			sem_post(connection->semaphore);
#endif
	}
}

int _TFLCSConnectionStructureIsValidForUse(TFLCSLocalConnection_t* connection)
{
	return 	(NULL != connection
			 && 0 != connection->open
#if defined(WIN32)
			 && NULL != connection->semaphore
			 && NULL != connection->mapFile
			 && NULL != connection->mapAddress
			 && NULL != connection->data
#else
			 && (sem_t*)SEM_FAILED != connection->semaphore
			 && (char*)-1 != connection->data
#endif
			 );
}

int _TFLCSDelayedConnect(TFLCSLocalConnection_t* connection)
{
#if defined(WIN32)
	if (NULL == connection)
		return 0;
	if (connection->open)
		return 1;
	
	if (NULL == (connection->semaphore = OpenMutex(MUTEX_ALL_ACCESS,
												   FALSE,
												   WIN32_SEMAPHORE_NAME))) {
		TFLCSErrno = TFLCSErrorSemaphoreCreationFailed;
		goto errorReturn;
	}
	
	if (NULL == (connection->mapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS,
													   FALSE,
													   WIN32_SHMEM_NAME))) {
		TFLCSErrno = TFLCSErrorShmemIDNotFound;
		goto errorReturn;
	}
	
	if (NULL == (connection->mapAddress = MapViewOfFile(connection->mapFile,
														FILE_MAP_ALL_ACCESS,
														0,
														0,
														0))) {
		TFLCSErrno = TFLCSErrorShmemOpeningFailed;
		goto errorReturn;
	}
	
	connection->data = (char*)connection->mapAddress;
	connection->open = 1;
	
errorReturn:
#endif
	
	return connection->open;
}

/*
 TUIO C++ Library
 Copyright (c) 2005-2017 Martin Kaltenbrunner <martin@tuio.org>
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 3.0 of the License, or (at your option) any later version.
 
 This library 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
 Lesser General Public License for more details.
 
 You should have received a copy of the GNU Lesser General Public
 License along with this library.
*/

using namespace TUIO;

FlashSender::FlashSender() {
	local = true;
	buffer_size = MAX_FLASH_SIZE;
	lcConnection = TFLCSConnect(DEFAULT_LC_CONN_NAME,DEFAULT_LC_METH_NAME,NULL,NULL);
	std::cout << "TUIO/FLC "<< DEFAULT_LC_METH_NAME << "@" << DEFAULT_LC_CONN_NAME << std::endl;
}

FlashSender::FlashSender(const char *conn_name, const char *meth_name) {
	local = true;
	buffer_size = MAX_FLASH_SIZE;
	lcConnection = TFLCSConnect(conn_name,meth_name,NULL,NULL);
	std::cout << "TUIO/FLC "<< meth_name << "@" << conn_name << std::endl;
}

FlashSender::~FlashSender() {
	TFLCSDisconnect(lcConnection);	
}

bool FlashSender::isConnected() { 
	if (TFLCSConnectionHasConnectedClient(lcConnection)) return true;
	else return false;
}

bool FlashSender::sendOscPacket (osc::OutboundPacketStream *bundle) {
	if (lcConnection==NULL) return false;
	if (!TFLCSConnectionHasConnectedClient(lcConnection)) return false; 
	if ( bundle->Size() > buffer_size ) return false;
	if ( bundle->Size() == 0 ) return false;

	TFLCSSendByteArray(lcConnection, bundle->Data(), bundle->Size());
	return true;
}
