//*****************************************************************************
// @brief daemon process to monitor RFID card and provide I/O services
// @author Allen Curtis
// @date 03/38/2023
//
// @details
//
// ----------------------------------------------------------------------------
//        Copyright (c) 2023  Ideate Medical All Rights Reserved.
//
// NOTICE: All information contained herein is, and remains the property of
// Ideate Medical. and its suppliers, if any. The intellectual and technical
// concepts contained herein are proprietary to Ideate Medical and its suppliers
// and may be covered by U.S. and Foreign Patents, patents in process, and are
// protected by trade secret or copyright law. Dissemination of this information
// or reproduction of this material is strictly forbidden unless prior written
// permission is obtained from Ideate Medical.
//
//*****************************************************************************

// C library includes
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

// OpenSSL includes
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>

// PCSC library includes
#include <winscard.h>

// Application includes
#include "ntag424.h"

///////////////////////////////////////////////////////////////////////////////
// Public Declarations                                                       //
///////////////////////////////////////////////////////////////////////////////
typedef enum {
	NO_ERROR = 0,

	ERROR_NO_DRIVER = 1,
	ERROR_READER_NOT_FOUND,

	ERROR_INTERNAL_ERROR = 99
} ErrorCodes;

#define NTAG424_ATR                        \
	{                                      \
		0x3B, 0x81, 0x80, 0x01, 0x80, 0x80 \
	}

#define KEY_0                                          \
	{                                                  \
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \
	}
#define KEY_1                                          \
	{                                                  \
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \
	}
#define KEY_2                                          \
	{                                                  \
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \
	}
#define KEY_3                                          \
	{                                                  \
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \
	}
#define KEY_4                                          \
	{                                                  \
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \
	}

#define DATA_FILE                \
	{                            \
		.file_no = DATA_FILE_NO, \
		.file_id = DATA_FILE_ID, \
		.access = USE_KEY_0,     \
		.comm_mode = ENCRYPTED,  \
		.size = 128,             \
	}

#define APPLICATION_FILE        \
	{                           \
		.file_id = APP_FILE_ID, \
		.access = USE_KEY_0,    \
	}

/**
 * @brief RFID tag control structure
 * 
 */
Ntag424 RfidTag = {
	.atr = NTAG424_ATR,
	.keys = {
		KEY_0,
		KEY_1,
		KEY_2,
		KEY_3,
		KEY_4,
	},
	.files = {
		[APP_FILE_NO] = APPLICATION_FILE,
		[DATA_FILE_NO] = DATA_FILE,
	},
};

///////////////////////////////////////////////////////////////////////////////
// Private Declarations                                                      //
///////////////////////////////////////////////////////////////////////////////

//*****************************************************************************
// Macros
//*****************************************************************************
/* PCSC error message pretty print */
#define CHECK_ERROR(rv, text)                                         \
	if (rv != SCARD_S_SUCCESS)                                       \
	{                                                                \
		printf(text ": %s (0x%lX)\n", pcsc_stringify_error(rv), rv); \
		return ERROR_INTERNAL_ERROR;                                                   \
	}

//*****************************************************************************
// Constants and Typedefs (private)
//*****************************************************************************

//*****************************************************************************
// Variable Declarations (private)
//*****************************************************************************
pthread_cond_t card_status_changed = PTHREAD_COND_INITIALIZER;
pthread_mutex_t card_status_lock = PTHREAD_MUTEX_INITIALIZER;

///////////////////////////////////////////////////////////////////////////////
// Implementation                                                            //
///////////////////////////////////////////////////////////////////////////////

/**
 * @brief decode the reader state and print the status
 * 
 * @param dwState - reader state to be decoded
 */
void show_reader_state(DWORD dwState)
{
	char status[100] = {0};
	sprintf(status, "0x%04lx: ", dwState);
	if (dwState & SCARD_STATE_UNAWARE)
		strcat(status, "UNAWARE ");

	if (dwState & SCARD_STATE_IGNORE)
		strcat(status, "IGNORE ");

	if (dwState & SCARD_STATE_CHANGED)
		strcat(status, "CHANGED ");

	if (dwState & SCARD_STATE_UNKNOWN)
		strcat(status, "UNKNOWN ");

	if (dwState & SCARD_STATE_UNAVAILABLE)
		strcat(status, "UNAVAILABLE ");

	if (dwState & SCARD_STATE_EMPTY)
		strcat(status, "EMPTY ");

	if (dwState & SCARD_STATE_PRESENT)
		strcat(status, "PRESENT ");

	if (dwState & SCARD_STATE_ATRMATCH)
		strcat(status, "ATRMATCH ");

	if (dwState & SCARD_STATE_EXCLUSIVE)
		strcat(status, "EXCLUSIVE ");

	if (dwState & SCARD_STATE_INUSE)
		strcat(status, "INUSE ");

	if (dwState & SCARD_STATE_MUTE)
		strcat(status, "MUTE ");

	if (dwState & SCARD_STATE_UNPOWERED)
		strcat(status, "UNPOWERED ");

	printf("Status: %s\n", status);
}

/**
 * @brief decode the state of a connected card (ntag) and print
 * 
 * @param dwState - card state to be decoded
 */
void show_card_status(DWORD dwState)
{
	char status[100] = {0};
	if (dwState & SCARD_UNKNOWN)
		strcat(status, "UNKNOWN ");

	if (dwState & SCARD_ABSENT)
		strcat(status, "ABSENT ");

	if (dwState & SCARD_PRESENT)
		strcat(status, "PRESENT ");

	if (dwState & SCARD_SWALLOWED)
		strcat(status, "SWALLOWED ");

	if (dwState & SCARD_POWERED)
		strcat(status, "POWERD ");

	if (dwState & SCARD_NEGOTIABLE)
		strcat(status, "NEGOTIABLE ");

	if (dwState & SCARD_SPECIFIC)
		strcat(status, "SPECIFIC ");

	printf("Status: %s\n", status);
}

/**
 * @brief Thread to monitor and update the status of the card reader
 * 
 * @param args 
 * @return * void* 
 */
void* monitor_thread(void* args)
{
	Ntag424 *ntag = (Ntag424*)args;

	printf("Monitor: %s\n", ntag->reader_name);

	ntag->reader_state.szReader = ntag->reader_name;
	ntag->reader_state.dwCurrentState = SCARD_STATE_UNAWARE;
	memcpy(ntag->reader_state.rgbAtr, ntag->atr, ATR_LEN);
	ntag->reader_state.cbAtr = ATR_LEN;

	while (true)
	{
		// Get current state
		LONG rv = SCardGetStatusChange(ntag->context, INFINITE, &ntag->reader_state, 1);

		if (rv == SCARD_S_SUCCESS)
		{
			show_reader_state(ntag->reader_state.dwEventState);
			ntag->reader_state.dwCurrentState = ntag->reader_state.dwEventState;
			pthread_cond_signal(&card_status_changed);
		}
		else
		{
			printf("Error: %s (0x%lX)\n", pcsc_stringify_error(rv), rv);
		}
	}
}

/**
 * Find all the readers connected to the system
 *
 * Find card readers is only expected to find one reader. Therefore,
 * this interface has been simplified based on that assumption. If
 * more than one reader is found, it will be an error.
 *
 * return number of readers found connected
 */
static int init_reader(Ntag424 *ntag)
{
	LONG rv;
	DWORD dwReaders;
	char *ptr;
	char *found_readers = NULL;
	char *acs_picc_reader = NULL;
	int e_code = NO_ERROR;

	/* Get Context to the driver */
	rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &ntag->context);
	CHECK_ERROR(rv, "SCardEstablishContext");

	/* Retrieve the available readers list. */
	dwReaders = SCARD_AUTOALLOCATE;
	rv = SCardListReaders(ntag->context, NULL, (LPSTR)&found_readers, &dwReaders);
	CHECK_ERROR(rv, "SCardListReaders");

	/* Extract readers from the null separated string and get the total
	 * number of readers */
	unsigned nbReaders = 0;
	ptr = found_readers;
	while (*ptr != '\0')
	{
		printf("Reader %d: %s\n", nbReaders, ptr);
		char *p_acr1252 = strstr(ptr, "ACR1252");
		if (p_acr1252)
		{
			char *p_picc = strstr(ptr, "PICC");
			if (p_picc)
			{
				acs_picc_reader = ptr;
			}
		}

		ptr += strlen(ptr) + 1;
		nbReaders++;
	}

	if (nbReaders == 0)
	{
		printf("No card readers were found!\n");
		e_code = ERROR_INTERNAL_ERROR;
	}

	if (acs_picc_reader == NULL)
	{
		printf("Error: ACR1252 Dual Reader PICC not found!\n");
		e_code = ERROR_INTERNAL_ERROR;
	}
	else
	{
		printf("found: %s\n", acs_picc_reader);

		ntag->reader_name = malloc(strlen(acs_picc_reader)+1);
		strcpy(ntag->reader_name, acs_picc_reader);

		rv = pthread_create(&ntag->tid, NULL, monitor_thread, ntag);
		if (rv)
		{
			printf("Error %ld creating monitor thread", rv);
			e_code = ERROR_INTERNAL_ERROR;
		}
	}

	SCardFreeMemory(ntag->context, found_readers);

	return rv;
}

//*****************************************************************************
// @brief
// @retval <value> <value definition>
//*****************************************************************************
int main()
{
	printf("PC/SC sample code\n");
	printf("V 1.4 2003-2009, Ludovic Rousseau <ludovic.rousseau@free.fr>\n");

	printf("\nTHIS PROGRAM IS NOT DESIGNED AS A TESTING TOOL FOR END USERS!\n");
	printf("Do NOT use it unless you really know what you do.\n\n");

	int e_code = init_reader(&RfidTag);
	if (e_code != NO_ERROR)
	{
		printf("Initialization error %d, aborting\n", e_code);
		return e_code;
	}


	bool previously_present = false;

	while (true) 
	{
		pthread_mutex_lock(&card_status_lock);
		pthread_cond_wait(&card_status_changed, &card_status_lock);

		bool is_tag_present = (RfidTag.reader_state.dwCurrentState & SCARD_STATE_PRESENT);
		if (is_tag_present && !previously_present)
		{
			printf("calling SCardConnect\n");
			DWORD dwActiveProtocol = SCARD_PROTOCOL_UNSET;
			LONG rv = SCardConnect(
				RfidTag.context, 
				RfidTag.reader_name, 
				SCARD_SHARE_SHARED,
				(SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1), 
				&RfidTag.handle, 
				&dwActiveProtocol);

			switch (dwActiveProtocol)
			{
			case SCARD_PROTOCOL_T0:
				printf("Active protocol: SCARD_PCI_T0\n");
				RfidTag.card_protocol = *SCARD_PCI_T0;
				break;

			case SCARD_PROTOCOL_T1:
				printf("Active protocol: SCARD_PCI_T1\n");
				RfidTag.card_protocol = *SCARD_PCI_T1;
				break;
			}
			SCardDisconnect(RfidTag.handle, SCARD_LEAVE_CARD);

			previously_present = true;
		}
		else if (!is_tag_present && previously_present)
		{
			previously_present = false;
		}

		pthread_mutex_unlock(&card_status_lock);
	}

	SCardReleaseContext(RfidTag.context);

	return 0;
}
