// start mingw
#ifdef __MINGW32__
#define __try
#define __finally
#endif
// end mingw

#include "MMF.h"
#include <iostream>
using namespace std;
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef WIN32
	#define snprintf	_snprintf
	#define vsnprintf	_vsnprintf
	#define strncasecmp	_strnicmp
	#define strcasecmp  _stricmp
#else
    #include "MMFMutex.h"
    #include "../common/unix.h"    
#endif

MMF::MMF() {
	SharedMemory = 0;
	pCanWrite = false;
	#ifdef WIN32
		hMapObject = NULL;
		lpvMem = 0;
	#else
		lpvMem = 0;
		pMMFMutex = 0;
		m_alloc = false;
	#endif
}

MMF::~MMF() {
	Close();
}

bool MMF::Open(const char* iName, int32 iSize) {
	if (iSize < 1) {
		cout << "Error Loading MMF: " << __FILE__ << ":" << __LINE__ << " OpenMMF: iSize < 1" << endl;
		return false;
	}
	if (strlen(iName) < 2) {
		cout << "Error Loading MMF: " << __FILE__ << ":" << __LINE__ << " OpenMMF: strlen(iName) < 2" << endl;
		return false;
	}
	char MMFname[200];
	memset(MMFname, 0, sizeof(MMFname));
	snprintf(MMFname, sizeof(MMFname), "memfilemap_%s", iName);
	int32 tmpSize = sizeof(MMF_Struct) + iSize;
#ifdef WIN32
	char MMFMutexName[200];
	memset(MMFMutexName, 0, sizeof(MMFMutexName));
	snprintf(MMFMutexName, sizeof(MMFMutexName), "MutexToProtectOpenMMF_%s", iName);
	
	HANDLE hMutex;
	hMutex = CreateMutex( 
	NULL,                       // no security attributes
	FALSE,                      // initially not owned
	MMFMutexName);  // name of mutex

	if (hMutex == NULL) {
		cout << "Error Loading MMF: " << __FILE__ << ":" << __LINE__ << " OpenMMF: hMutex == Null" << endl;
		return false;
	}

	DWORD dwWaitResult;
    // Request ownership of mutex.
	dwWaitResult = WaitForSingleObject( 
		hMutex,   // handle to mutex
		2000L);   // two-second time-out interval

	if (dwWaitResult != WAIT_OBJECT_0) {
		// Mutex not aquired, crap out
		cout << "Error Loading MMF: " << __FILE__ << ":" << __LINE__ << " OpenMMF: dwWaitResult != WAIT_OBJECT_0" << endl;
		return false;
	}

	// Finally, ready to rock.
	bool fInit = false;
	__try {
		hMapObject = CreateFileMapping( 
			INVALID_HANDLE_VALUE,		// use paging file
			NULL,						// default security attributes
			PAGE_READWRITE,				// read/write access
			0,							// size: high 32-bits
			tmpSize,					// size: low 32-bits
			MMFname);					// name of map object
		if (hMapObject == NULL) {
			cout << "Error Loading MMF: " << __FILE__ << ":" << __LINE__ << " OpenMMF: hMapObject == Null" << endl;
			return false;
		}

		// The first process to attach initializes memory.

		fInit = (bool) (GetLastError() != ERROR_ALREADY_EXISTS); 

		// Get a pointer to the file-mapped shared memory.

		lpvMem = MapViewOfFile( 
			hMapObject,					// object to map view of
			FILE_MAP_WRITE,				// read/write access
			0,							// high offset:  map from
			0,							// low offset:   beginning
			0);							// default: map entire file
		if (lpvMem == NULL) {
			cout << "Error Loading MMF: " << __FILE__ << ":" << __LINE__ << " OpenMMF: lpvMem == Null" << endl;
			Close();
			return false;
		}
 
		SharedMemory = (MMF_Struct*) lpvMem;
		// Initialize memory if this is the first process.
		if (fInit) {
			memset(lpvMem, 0, sizeof(MMF_Struct));
			pCanWrite = true;
			SharedMemory->Loaded = false;
			SharedMemory->datasize = iSize;
		}
		else {
			pCanWrite = false;
			if (SharedMemory->datasize != iSize) {
				cout << "Error Loading MMF: " << __FILE__ << ":" << __LINE__ << " OpenMMF: SharedMemory->datasize != iSize" << endl;
				Close();
				return false;
			}
		}
	} // end of try block

	__finally {
		// Clean up the Mutex stuff
		if (!ReleaseMutex(hMutex)) {
			cout << "Error Loading MMF: " << __FILE__ << ":" << __LINE__ << " OpenMMF: !ReleaseMutex(hMutex)" << endl;
			Close();
			return false;
		}
	}
	CloseHandle(hMutex);

	return true;
#else
	int load_share;
	int max_share = 7;
	key_t share_key;
	switch (MMFname[16]) {
		case 'I': load_share = 0;  break;
		case 'N': load_share = 1;  break;
		case 'D': load_share = 2;  break;
		case 'S': load_share = 3;  break;
		case 'F': load_share = 4;  break;
		case 'L': load_share = 5;  break;
		case 'M': load_share = 6;  break;
#ifdef CATCH_CRASH
		default:
		    cerr<<"Failed to load shared memory segment="<<MMFname<<endl;
		    // malloc some memory here or something fancy
		    return false; // and make this return true
		break;
#else
		default: cerr<<"FATAL="<<(char)MMFname[16]<<endl; return false; break;
#endif
	}
	switch (load_share) {
		// Item
		case 0: share_key = ftok(".", 'I'); break;
		// Npctype
		case 1: share_key = ftok(".", 'N'); break;
		// Door
		case 2: share_key = ftok(".", 'D'); break;
		// Spell
		case 3: share_key = ftok(".", 'S'); break;
		// Faction
		case 4: share_key = ftok(".", 'F'); break;
		case 5: share_key = ftok(".", 'L'); break;
		case 6: share_key = ftok(".", 'M'); break;
		// ERROR Fatal
		default: cerr<<"Opps!"<<endl; share_key = 0xFF; break;
	}
	pMMFMutex = new MMFMutex(share_key);
	if (!pMMFMutex){
		assert(false);
	}
	int share_id = shmget(share_key, tmpSize, IPC_CREAT|IPC_EXCL|IPC_NOWAIT|SHM_R|SHM_W);
	if ( share_id <= 0) {
		share_id = shmget(share_key, tmpSize, IPC_NOWAIT|0400);
		if (share_id <= 0) {
		    shmid_ds mem_size;
		    share_id = shmget(share_key, 0, IPC_NOWAIT|0400);
		    lpvMem = shmat(share_id, NULL,SHM_RDONLY);
		    if( (shmctl(share_id, IPC_STAT, &mem_size)) == 0){
		          if (mem_size.shm_segsz != tmpSize){
                        	cout<<"[Warning] requested shared memory of size:"<<tmpSize<<" but that Key is already in use with size:"<< mem_size.shm_segsz<<endl;
                        	shmid_ds mem_users;
                        	if( (shmctl(share_id, IPC_STAT, &mem_users)) == 0 && mem_users.shm_nattch == 1){
                                    cout<<"[Warning] Attempting resize"<<endl;
                                    shmctl(share_id, IPC_RMID, 0);
                                    shmdt(lpvMem);
                                    if ((share_id = shmget(share_key, tmpSize, IPC_CREAT|IPC_EXCL|IPC_NOWAIT|SHM_R|SHM_W)) <= 0) {
                                    	// Failed proceed on malloc
                                    }
                                    else{
                                    	// Success
                                    	lpvMem = shmat(share_id, NULL, SHM_R|SHM_W);
                                    	memset(lpvMem, 0, sizeof(MMF_Struct));
                                    	pCanWrite = true;
                                    	SharedMemory = (MMF_Struct*) lpvMem;
                                    	SharedMemory->Loaded = false;
                                    	SharedMemory->datasize = iSize;
                                    	pMMFMutex->Release(this);
                                    	delete pMMFMutex;
                                    	return true;
                                    }
                        	}
                        	else{
                                    cout<<"[Warning] Resize not possible"<<endl;
                        	}
		          }
		    }
		    // Can not attatch to shared memory we'll malloc it here
			if (!lpvMem && (lpvMem = malloc(tmpSize))) {
				cout<<"[Warning] Could not attach to shared memory proceeding on isolated memory (share_id <= 0)"<<endl;
				// Success!
				m_alloc = true;
				memset(lpvMem, 0, sizeof(MMF_Struct));
				pCanWrite = true;
				SharedMemory = (MMF_Struct*) lpvMem;
				SharedMemory->datasize = iSize;
				SharedMemory->Loaded = false;
				pMMFMutex->Release(this);
				delete pMMFMutex;
				return true;
			}  else if (!lpvMem){
				//LogFile->write(EQEMuLog::Error, "Could not connect to shared memory and allocation of isolated memory failed.");
				cout<<"Could not connect to shared memory and allocation of isolated memory failed."<<endl;
				pMMFMutex->Release(this);
				delete pMMFMutex;
				exit(1);
			}
			pCanWrite = false;
			SharedMemory = (MMF_Struct*) lpvMem;
			if (SharedMemory->datasize != iSize) {
	    	    		cerr<<"SharedMemory->datasize != iSize, We can rebuild him faster better STRONGER!"<<endl;
		    		cerr<<"Or not.. restart all servers on this machine"<<endl;
		    		shmctl(share_id, IPC_RMID, 0);
		    		pMMFMutex->Release(this);
		    		exit(1);
			}
			pMMFMutex->Release(this);
			delete pMMFMutex;
			return true;
		}
		shmid_ds mem_users;
		if ((shmctl(share_id, IPC_STAT, &mem_users)) != 0) {
			if ((lpvMem = malloc(tmpSize))) {
				// Success!
				cout<<"[Warning] Could not attach to shared memory proceeding on isolated memory"<<endl;
				m_alloc = true;
				memset(lpvMem, 0, sizeof(MMF_Struct));
				pCanWrite = true;
				SharedMemory = (MMF_Struct*) lpvMem;
				SharedMemory->datasize = iSize;
				SharedMemory->Loaded = false;
				pMMFMutex->Release(this);
				delete pMMFMutex;
				return true;
			}  else {
				//LogFile->write(EQEMuLog::Error, "Could not connect to shared memory and allocation of isolated memory failed.");
				cout<<"Could not connect to shared memory and allocation of isolated memory failed."<<endl;
				pMMFMutex->Release(this);
				delete pMMFMutex;
				exit(1);
			}
		}
		lpvMem = shmat(share_id, NULL,SHM_RDONLY);
		pCanWrite = false;
		SharedMemory = (MMF_Struct*) lpvMem;
		if (SharedMemory->datasize != iSize) {
	    	    cerr<<"SharedMemory->datasize != iSize, We can rebuild him faster better STRONGER!"<<endl;
		    cerr<<"Or not.. restart all servers on this machine"<<endl;
		    shmctl(share_id, IPC_RMID, 0);
		    pMMFMutex->Release(this);
		    exit(1);
		}
		pMMFMutex->Release(this);
		delete pMMFMutex;
		return true;
	}
	lpvMem = shmat(share_id, NULL, SHM_R|SHM_W);
	memset(lpvMem, 0, sizeof(MMF_Struct));
	pCanWrite = true;
	SharedMemory = (MMF_Struct*) lpvMem;
	SharedMemory->Loaded = false;
	SharedMemory->datasize = iSize;
	pMMFMutex->Release(this);
	delete pMMFMutex;
	return true;

#endif
}

void MMF::Close() {
	SharedMemory = 0;
	pCanWrite = false;
#ifdef WIN32
		if (lpvMem) {
			// Unmap shared memory from the process's address space.
			UnmapViewOfFile(lpvMem); 
			lpvMem = 0;
		}
		if (hMapObject) {
			// Close the process's handle to the file-mapping object.
			CloseHandle(hMapObject);
			hMapObject = NULL;
		}
#else
		if (lpvMem) {
			if (m_alloc == true)
				free(lpvMem);
	#ifndef FREEBSD  // for freeBSD
			else
				shmdt(lpvMem);
			if (lpvMem) {
				//LogFile->write(EQEMuLog::Error, "Warning something odd happened freeing shared memory");
				cout<<"Warning something odd happened freeing shared memory"<<endl;
			}
// start freeBSD
	#else
			else
				if (shmdt(lpvMem) == -1)
					//LogFile->write(EQEMuLog::Error, "Warning something odd happened freeing shared memory");
					cout<<"Warning something odd happened freeing shared memory"<<endl;
	#endif
// end freeBSD
			lpvMem = 0;
		}
#endif
}

