#include "SlotList.h"
#include "Pkcs11Exceptions.h"
#include "time.h"

extern SCARDCONTEXT g_hContext;
extern CK_ULONG g_ulGlobalFlags;

#ifndef LPSCARD_READERSTATE
typedef SCARD_READERSTATE * LPSCARD_READERSTATE;
#endif

/********************************************************************************************
	Constructor.
	Inicializa el contador con el próximo ID a asignar
********************************************************************************************/
CSlotList::CSlotList(void)
{
	m_ulNextId = 0x10000;
}


/********************************************************************************************
	Destructor.
********************************************************************************************/
CSlotList::~CSlotList(void)
{
	//
	// Al ser un singleton, la llamada el destructor indica que
	// se está descargando el módulo.
	// Liberamos todos los objetos utilizados
	//
	map<unsigned long, CSlot *>::iterator it;

	for ( it=m_mapId2Slot.begin() ; it != m_mapId2Slot.end(); it++ )
		delete it->second;

	m_mapId2Slot.clear();
	m_mapName2Id.clear();
	m_slotList.clear();
}


/********************************************************************************************
	Método que obtiene un nuevo ID de lector.
	Mantiene actualizado el contador de IDs.
********************************************************************************************/
/*
unsigned long CSlotList::GetNewId()
{
	return m_ulNextId++;
}
*/

/********************************************************************************************
	Método que actualiza la lista de lectores del equipo.
********************************************************************************************/
void CSlotList::Update()
{
	/*LPTSTR          psReaders = NULL;
	LPTSTR          pReader;
	LONG            lReturn, lReturn2;
	DWORD           cch = SCARD_AUTOALLOCATE;*/

	LPTSTR psReaders = NULL;
	LPTSTR psReader = NULL;
	DWORD dwReadersLen = 0; //SCARD_AUTOALLOCATE;
	LONG lReturn;
	string sReader;

	pair<map<string, unsigned long>::iterator, bool> pairRet;

	// Vaciamos la lista de lectores
	m_slotList.clear();

	// Obtenemos la lista de nombres de los lectores
	lReturn = SCardListReaders(g_hContext, NULL, NULL, &dwReadersLen);

	switch (lReturn)
	{
		case SCARD_S_SUCCESS: // Ningún error

			psReaders = (LPTSTR) calloc(dwReadersLen,sizeof(char));
			lReturn = SCardListReaders(g_hContext, NULL, psReaders, &dwReadersLen);
			if (lReturn != SCARD_S_SUCCESS)
				throw Pkcs11Exception(CKR_GENERAL_ERROR);

			psReader = psReaders;

			// Para cada uno de los nombres de lector
			while ('\0' != *psReader)
			{
				sReader = string(psReader);

				// Insertamos el nombre del lector en la caché.
				// El primer elemento del valor de retorno es una referencia al elemento insertado,
				// o a la entrada que ya hubiera previamente de ese mismo elemento, en cuyo caso
				// no se realizaría una nueva inserción del mismo.
				// El segundo elemento del valor de retorno es un booleano que indica si se ha
				// producido o no una inserción del elemento.
				pairRet = m_mapName2Id.insert(pair<string, unsigned long>(sReader, m_ulNextId));
				m_slotList.push_back(pairRet.first->second);

				if (pairRet.second) // Nuevo lector
				{
					m_mapId2Slot.insert(pair<unsigned long, CSlot *>(m_ulNextId, new CSlot(sReader,m_ulNextId)));
					m_ulNextId++;
				}

				psReader += sReader.length() + 1;
			}

			// Free the memory.
			/*lReturn = SCardFreeMemory( g_hContext, psReaders );
			if ( SCARD_S_SUCCESS != lReturn )
				printf("Failed SCardFreeMemory\n");*/

			break;

		case SCARD_E_NO_READERS_AVAILABLE: // Ningún lector encontrado
			// Vaciamos la lista
			break;

		case SCARD_E_NO_MEMORY: // No hay suficiente memoria para completar la operación
			throw Pkcs11Exception(CKR_HOST_MEMORY);
			break;
		case SCARD_E_INVALID_PARAMETER:
			throw Pkcs11Exception(CKR_ARGUMENTS_BAD);
			break;
		default:
			throw Pkcs11Exception(CKR_GENERAL_ERROR);
	}

}


/********************************************************************************************
	Método que obtiene el número de lectores según la última actualización de la lista.

	Parámetros:
		- bool bSupportedTokenPresent: Indica si se quieren contar sólo los lectores que
		  tengan una tarjeta de las soportadas por el módulo o todos los lectores.

	Devuelve: unsigned long con el número de lectores.
********************************************************************************************/
unsigned long CSlotList::GetNumberOfSlots(bool bSupportedTokenPresent)
{
	unsigned long	ulNumberOfSlots = 0;
	unsigned long	idx;
	CSlot *			pSlot;

	if (bSupportedTokenPresent)
	{
		// Recorremos la lista de lectores para ir comprobando si tienen una tarjeta válida
		for( idx=0; idx<m_slotList.size(); ++idx )
		{
			pSlot = m_mapId2Slot[m_slotList[idx]];
			try
			{

				if (pSlot->SupportedTokenPresent())
				{
					ulNumberOfSlots++;
				}

			// Salvamos las excepciones generadas cuando no se encuentra el lector o no está
			// disponible o listo. No se cuentan como lectores con tarjeta válida.
			}
			catch (Pkcs11Exception & e)
			{
				throw e;
			}

		}

	} else
	{
		ulNumberOfSlots = (unsigned long)m_slotList.size();
	}

	return ulNumberOfSlots;
}


/********************************************************************************************
	Método que genera una lista de los IDs de lectores según la última actualización de la
	lista.

	Parámetros:
		- unsigned long * pulSlots: Buffer donde almacenar la lista le los IDs de lectores.
		  Debe tener asignado a priori un espacio de memoria suficiente.
		- bool bSupportedTokenPresent: Indica si se quiere formar la lista sólo los lectores
		  que tengan una tarjeta de las soportadas por el módulo o con todos los lectores.
********************************************************************************************/
void CSlotList::GetSlots(unsigned long * pulSlots, bool bTokenPresent)
{
	unsigned long idx;
	unsigned long idxSlot = 0;

	// Recorremos la lista de lectores
	for( idx=0; idx<m_slotList.size(); ++idx )
	{
		try
		{
			if (!bTokenPresent
				|| m_mapId2Slot[m_slotList[idx]]->SupportedTokenPresent())
			{
				// Añadimos el ID del lector
				pulSlots[idxSlot++] = m_slotList[idx];
			}

		// Salvamos las excepciones generadas cuando no se encuentra el lector o no está listo
		// o disponible. No se cuentan como lectores con tarjeta válida.
		}
		catch (Pkcs11Exception & e)
		{
			throw e;
		}

	}

}


/********************************************************************************************
	Método que obtiene el lector con un determinado ID.

	Parámetros:
		- unsigned long ulId: ID del lector.

	Devuelve: CSlot que representa el lector con ese ID.
********************************************************************************************/
CSlot & CSlotList::GetSlot(unsigned long ulId)
{
	//
	// Actualizamos la lista de slots por si actualizó desde C_GetSlotList
	//
	Update();

	map <unsigned long, CSlot *>::iterator itId2Slot;

	itId2Slot = m_mapId2Slot.find(ulId);

	// Si no encuentra el lector lanzamos una excepción
	if (itId2Slot == m_mapId2Slot.end())
	{
		throw Pkcs11Exception(CKR_SLOT_ID_INVALID);
	}

	return *(itId2Slot->second);
}


/********************************************************************************************
	Método que esperará a que ocurra un evento en alguno de los lectores de la lista

	Parámetros:
		- unsigned long * pSlotId: Slot en el que ha ocurrido el evento
		- DWORD dwTimeout: timeout a usar en la espera
********************************************************************************************/
unsigned long CSlotList::WaitForSlotStatusChange(unsigned long *pSlotId, DWORD dwTimeout)
{
	LPTSTR mszReaders		= NULL;
	unsigned int i			= 0;

	LONG lResult			= SCARD_E_TIMEOUT;

	// Variables para controlar el estado de los slots y los cambios.
	LPSCARD_READERSTATE pReaderStates = NULL;
	DWORD dwReaders			= 0;

	time_t	startTime		= time(NULL);

	DBG_TRACE(("CSlotList::WaitForSlotStatusChange(0x%08x,%d)...\n",pSlotId,dwTimeout));

	try
	{
		dwReaders = (DWORD)m_slotList.size();
		pReaderStates = new SCARD_READERSTATE[dwReaders];

		for (i=0; i<dwReaders; i++)
		{
			CSlot & slot = GetSlot(m_slotList.at(i));
			pReaderStates[i] = slot.GetState();
		}

		do
		{
			// Comprobamos que no se haya hecho un C_Finalize entre medias
			if (!(g_ulGlobalFlags & FLAG_LIBRERIA_INICIALIZADA))
				throw Pkcs11Exception(CKR_CRYPTOKI_NOT_INITIALIZED);

			lResult = SCardGetStatusChange(g_hContext,
				1000, pReaderStates, dwReaders);

			if( lResult != SCARD_S_SUCCESS )
				throw (lResult);

			for (i=0; i<dwReaders; i++)
			{
				if (pReaderStates[i].dwEventState & SCARD_STATE_CHANGED)
				{
					// Anotamos el estado actual para ese lector
					pReaderStates[i].dwCurrentState =
						(pReaderStates[i].dwEventState & ~(SCARD_STATE_CHANGED));

					//
					map <string, unsigned long>::iterator itName2Id;
					itName2Id = m_mapName2Id.find(pReaderStates[i].szReader);

					if(itName2Id == m_mapName2Id.end())
					{
						// No hemos encontrado el lector en la lista de lectores disponibles
						// ¿extraído el lector?
						break;
					}

					// Devolvemos el Id del slot encontrado
					*pSlotId = itName2Id->second;

					CSlot & slot = GetSlot(itName2Id->second);

					// Cambiamos el estado actual de este lector y lo actualizamos en el slot
					// Sólo anotamos el que devolvemos como modificado. Si hay más, se procesarán
					// en otras llamadas.
					pReaderStates[i].dwCurrentState = (pReaderStates[i].dwEventState & ~(SCARD_STATE_CHANGED));
					slot.SetState(pReaderStates[i]);

					// Si ya no hay tarjeta en el slot, borramos el estado
					if(pReaderStates[i].dwCurrentState & (SCARD_STATE_EMPTY) )
						slot.Disconnect(true);

					break;

				} // fin del IF

			} // fin del FOR

		}
		while( (startTime+dwTimeout) < time(NULL) );

	}
	catch(LONG hres)
	{
		lResult = hres;
	}
	catch (Pkcs11Exception & e)
	{
		// Liberamos la memoria reservada
		if(pReaderStates)
			delete(pReaderStates);

		throw e;
	}
	catch(...)
	{

	    throw Pkcs11Exception(CKR_FUNCTION_FAILED);
	}

	// Liberamos la memoria reservada
	if(pReaderStates)
		delete(pReaderStates);

	DBG_TRACE(("CSlotList::WaitForSlotStatusChange() = 0x%08x\n",lResult));

	return lResult;
}
