// =================================================================================================
// Copyright Adobe
// Copyright 2011 Adobe
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it. 
// =================================================================================================

#include "ModuleUtils.h"
#include "source/UnicodeConversions.hpp"
#include "source/XIO.hpp"

#include <iostream>
#include <map>
#include <limits>
#include <dlfcn.h>
#include <errno.h>

namespace XMP_PLUGIN
{

// global map of loaded modules (handle, full path)
typedef std::map<OS_ModuleRef, std::string>			ModuleRefToPathMap;
static ModuleRefToPathMap							sMapModuleRefToPath;
//typedef std::map<void*, std::string>				ResourceFileToPathMap;
typedef std::map<OS_ModuleRef, std::string>			ResourceFileToPathMap;
static ResourceFileToPathMap						sMapResourceFileToPath;
static XMP_ReadWriteLock							sMapModuleRWLock;

typedef std::shared_ptr<int>						FilePtr;

static std::string GetModulePath( OS_ModuleRef inOSModule );
/** ************************************************************************************************************************
** CloseFile()
*/
void CloseFile(
	int* inFilePtr)
{
	close(*inFilePtr);
	delete inFilePtr;
}

/** ************************************************************************************************************************
** OpenResourceFile()
*/
static FilePtr OpenResourceFile(
	OS_ModuleRef inOSModule,
	const std::string& inResourceName,
	const std::string& inResourceType)
{
	// It is assumed, that all resources reside in a folder with
	// the same name as the shared object plus '.resources' extension
	std::string path( GetModulePath(inOSModule) );
	
	XMP_StringPtr extPos = path.c_str() + path.size();
	for ( ; (extPos != path.c_str()) && (*extPos != '.'); --extPos ) {}
	path.erase( extPos - path.c_str() ); // Remove extension
	
	path += ".resources";
	path += kDirChar;
	path += inResourceName + "." + inResourceType;

	FilePtr file;
	if( Host_IO::GetFileMode(path.c_str()) == Host_IO::kFMode_IsFile )
	{
		int fileRef = ::open( path.c_str(), O_RDONLY );
		if (fileRef != -1)
		{
			file.reset(new int(fileRef), CloseFile);
		}
	}
	return file;
}

OS_ModuleRef LoadModule( const std::string & inModulePath, bool inOnlyResourceAccess)
{
	OS_ModuleRef result = NULL;
	if( inOnlyResourceAccess )
	{
		int fileHandle = open(reinterpret_cast<const char*>(inModulePath.c_str()), O_RDONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
		if( !fileHandle )
		{
			std::cerr << "Cannot open library for resource access: " << strerror(errno) << std::endl;
		}
		else
		{	// success !
			result = (void*) fileHandle;
			ResourceFileToPathMap::const_iterator iter = sMapResourceFileToPath.find(result);
			if (iter == sMapResourceFileToPath.end())
			{
				// not found, so insert
				sMapResourceFileToPath.insert(std::make_pair(result, inModulePath));
			}
		}

	}
	else
	{
		result = dlopen(reinterpret_cast<const char*>(inModulePath.c_str()), RTLD_LAZY/*RTLD_NOW*/);

		if( !result )
		{
			std::cerr << "Cannot open library: " << dlerror() << std::endl;
		}
		else
		{	// success !
			XMP_AutoLock writeLock ( &sMapModuleRWLock, kXMP_WriteLock );
			ModuleRefToPathMap::const_iterator iter = sMapModuleRefToPath.find(result);
			if( iter == sMapModuleRefToPath.end() )
			{
				// not found, so insert
				sMapModuleRefToPath.insert(std::make_pair(result, inModulePath));
			}
		}
	}

	return result;
}

void UnloadModule( OS_ModuleRef inModule, bool inOnlyResourceAccess )
{
	if( inModule != NULL )
	{
		// we bluntly assume, that only one instance of the same library is loaded ad therefore added to the global map !
		if( inOnlyResourceAccess )
		{
			ResourceFileToPathMap::iterator iter = sMapResourceFileToPath.find(inModule);
			if( iter != sMapResourceFileToPath.end() )
			{
				close((long) inModule);
				sMapResourceFileToPath.erase(iter);
			}
			else
			{
				XMP_Throw("OS_Utils_Linux::UnloadModule called with invalid module handle", kXMPErr_InternalFailure);
			}
		}
		else
		{
			XMP_AutoLock writeLock ( &sMapModuleRWLock, kXMP_WriteLock );
			ModuleRefToPathMap::iterator iter = sMapModuleRefToPath.find(inModule);
			if( iter != sMapModuleRefToPath.end() )
			{
				dlclose(inModule);
				sMapModuleRefToPath.erase(iter);
			}
			else
			{
				XMP_Throw("OS_Utils_Linux::UnloadModule called with invalid module handle", kXMPErr_InternalFailure);
			}
		}
	}
}

/** ************************************************************************************************************************
** GetModulePath()
*/
static std::string GetModulePath(
	OS_ModuleRef inOSModule)
{
	std::string result;

	if( inOSModule != NULL )
	{
		ModuleRefToPathMap::const_iterator iter;
		{
			XMP_AutoLock readLock ( &sMapModuleRWLock, kXMP_ReadLock );
			iter = sMapModuleRefToPath.find(inOSModule);
		}

		ResourceFileToPathMap::const_iterator iter2 = sMapResourceFileToPath.find(inOSModule);
		if( (iter != sMapModuleRefToPath.end()) && (iter2 != sMapResourceFileToPath.end()) )
		{
			XMP_Throw("OS_Utils_Linux::GetModulePath: Module handle is present in both global maps", kXMPErr_InternalFailure);
		}
		if (iter != sMapModuleRefToPath.end())
		{
			result = iter->second;
		}
		else if (iter2 != sMapResourceFileToPath.end())
		{
			result = iter2->second;
		}
		else
		{
			XMP_Throw("OS_Utils_Linux::GetModulePath: Failed to find inOSModule in global map !", kXMPErr_InternalFailure);
		}
	}
	
	return result;
}

void* GetFunctionPointerFromModuleImpl(	OS_ModuleRef inOSModule, const char* inSymbol )
{
	void* proc = NULL;
	if( inOSModule != NULL )
	{
		proc = dlsym(inOSModule, inSymbol);
		if( !proc )
		{
			std::cerr << "Cannot get function " << inSymbol << " : " << dlerror() << std::endl;
		}
	}
	return proc;
}

bool GetResourceDataFromModule(
	OS_ModuleRef inOSModule,
	const std::string & inResourceName,
	const std::string & inResourceType,
	std::string & outBuffer)
{
	bool success = false;
	FilePtr file = OpenResourceFile( inOSModule, inResourceName, inResourceType );

	if( file )
	{
		ssize_t	size = 0;
		ssize_t	file_size = ::lseek(*file.get(), 0, SEEK_END);
		// presumingly we don't want to load more than 2GByte at once (!)
		if( file_size < std::numeric_limits<XMP_Int32>::max() )
		{
			size = file_size;
			if( size > 0 )
			{
				outBuffer.resize(size);

				::lseek(*file.get(), 0, SEEK_SET);
				ssize_t bytesRead = ::read( *file.get(), (unsigned char*)&outBuffer[0], size );
				success = bytesRead == size;
			}
		}
	}
	return success;
}

} //namespace XMP_PLUGIN
