#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include "crc8.h"
#include "crc32.h"
#include "cvend.h"

const char *cvend_path;
FILE *file;
pthread_mutex_t cvend_mutex = PTHREAD_MUTEX_INITIALIZER;
int last_seq = 0;

const char *stringify_msg_type(uint8_t msg_type)
{
	switch (msg_type) {
	case MTYPE_VERSION:
		return "MTYPE_VERSION";
	case MTYPE_VERSION_REPLY:
		return "MTYPE_VERSION_REPLY";
	case MTYPE_STATUS:
		return "MTYPE_STATUS";
	case MTYPE_STATUS_REPLY:
		return "MTYPE_STATUS_REPLY";
	case MTYPE_HEARTBEAT:
		return "MTYPE_HEARTBEAT";
	case MTYPE_STARTUP:
		return "MTYPE_STARTUP";
	case MTYPE_RESET:
		return "MTYPE_RESET";
	case MTYPE_RESET_REPLY:
		return "MTYPE_RESET_REPLY";
	case MTYPE_LEDS:
		return "MTYPE_LEDS";
	case MTYPE_BUZZER:
		return "MTYPE_BUZZER";
	case MTYPE_CARD_RELEASE:
		return "MTYPE_CARD_RELEASE";
	case MTYPE_ABORT_CARD_HANDLING:
		return "MTYPE_ABORT_CARD_HANDLING";
	case MTYPE_PUT_FILE:
		return "MTYPE_PUT_FILE";
	case MTYPE_PUT_FILE_REPLY:
		return "MTYPE_PUT_FILE_REPLY";
	case MTYPE_GET_FILE:
		return "MTYPE_GET_FILE";
	case MTYPE_GET_FILE_REPLY:
		return "MTYPE_GET_FILE_REPLY";
	case MTYPE_DELETE_FILE:
		return "MTYPE_DELETE_FILE";
	case MTYPE_DELETE_FILE_REPLY:
		return "MTYPE_DELETE_FILE_REPLY";
	case MTYPE_FILE_INFO:
		return "MTYPE_FILE_INFO";
	case MTYPE_FILE_INFO_REPLY:
		return "MTYPE_FILE_INFO_REPLY";
	case MTYPE_FILE_LIST:
		return "MTYPE_FILE_LIST";
	case MTYPE_FILE_LIST_REPLY:
		return "MTYPE_FILE_LIST_REPLY";
	case MTYPE_SET_TIME:
		return "MTYPE_SET_TIME";
	case MTYPE_SET_TIME_REPLY:
		return "MTYPE_SET_TIME_REPLY";
	case MTYPE_ITSO_DATA:
		return "MTYPE_ITSO_DATA";
	case MTYPE_ITSO_DATA_REPLY:
		return "MTYPE_ITSO_DATA_REPLY";
	case MTYPE_ITSO_CTRL:
		return "MTYPE_ITSO_CTRL";
	case MTYPE_ITSO_CTRL_REPLY:
		return "MTYPE_ITSO_CTRL_REPLY";
	case MTYPE_ITSO_READ:
		return "MTYPE_ITSO_READ";
	case MTYPE_APDU_PROX:
		return "MTYPE_APDU_PROX";
	case MTYPE_APDU_PROX_REPLY:
		return "MTYPE_APDU_PROX_REPLY";
	case MTYPE_SAM_CTRL:
		return "MTYPE_SAM_CTRL";
	case MTYPE_SAM_CTRL_REPLY:
		return "MTYPE_SAM_CTRL_REPLY";
	case MTYPE_DESFIRE_READ:
		return "MTYPE_DESFIRE_READ";
	case MTYPE_UNHANDLED_CARD:
		return "MTYPE_UNHANDLED_CARD";
	case MTYPE_EMV:
		return "MTYPE_EMV";
	case MTYPE_EMV_STATUS:
		return "MTYPE_EMV_STATUS";
	case MTYPE_PROX_CARD_FUNCTION:
		return "MTYPE_PROX_CARD_FUNCTION";
	case MTYPE_PROX_CARD_FUNCTION_REPLY:
		return "MTYPE_PROX_CARD_FUNCTION_REPLY";
	case MTYPE_SECURITY_SERVICES:
		return "MTYPE_SECURITY_SERVICES";
	case MTYPE_SECURITY_SERVICES_REPLY:
		return "MTYPE_SECURITY_SERVICES_REPLY";
	case MTYPE_DESFIRE_STATUS:
		return "MTYPE_DESFIRE_STATUS";
	case MTYPE_DESFIRE_REMOVED:
		return "MTYPE_DESFIRE_REMOVED";
	case MTYPE_DESFIRE_COMMAND:
		return "MTYPE_DESFIRE_COMMAND";
	case MTYPE_DESFIRE_COMMAND_REPLY:
		return "MTYPE_DESFIRE_COMMAND_REPLY";
	case MTYPE_LOG:
		return "MTYPE_LOG";
	default:
		return "UNKNOWN";
	}
}

void calc_hdr_crc(cvend_packet *packet)
{
	packet->magic = 0xbc;
	packet->msg_len = (packet->msg_len << 8) + (packet->msg_len >> 8);
	packet->hdr_crc = crc8((uint8_t *)packet, 6);
	packet->msg_len = (packet->msg_len << 8) + (packet->msg_len >> 8);
}

void calc_msg_crc(cvend_packet *packet)
{
	if (packet->msg_type & 0x80)
		packet->msg_crc = crc32(packet->msg_data, packet->msg_len);
	else
		packet->msg_crc = 0;
}

void cvend_init(const char *path)
{
	cvend_path = path;
	file = fopen(path, "wb+");
}

cvend_packet *cvend_read()
{
	pthread_mutex_lock(&cvend_mutex);
	uint8_t buf = 0x00;
	uint8_t res = 0;
	while (buf != 0xbc) {
		res = fread(&buf, 1, 1, file);
		if (res == 0) {
			fclose(file);
			file = fopen(cvend_path, "wb+");
		}
		if (buf != 0xbc) {
			printf("bad magic char: %x\n", buf);
		}
	}

	cvend_packet *packet = malloc(sizeof(cvend_packet));
	fread(&packet->seq, 1, 1, file);
	fread(&packet->flags, 1, 1, file);
	fread(&packet->msg_type, 1, 1, file);
	fread((uint8_t *)&packet->msg_len + 1, 1, 1, file);
	fread(&packet->msg_len, 1, 1, file);
	fread(&packet->hdr_crc, 1, 1, file);

	packet->msg_data = malloc(packet->msg_len);
	fread(packet->msg_data, 1, packet->msg_len, file);
	if (packet->msg_type & 0x80)
		fread(&packet->msg_crc, 1, 4, file);
	else
		packet->msg_crc = 0;
	pthread_mutex_unlock(&cvend_mutex);

	if (packet->msg_type != MTYPE_HEARTBEAT) {
		printf("RECEIVED:\n");
		print_packet(packet);
	}
	return packet;
}

cvend_packet *cvend_read_type(uint8_t msg_type)
{
	cvend_packet *packet = NULL;
	do {
		if (packet)
			cvend_free(packet);
		packet = cvend_read();
	} while (packet->msg_type != msg_type);
	return packet;
}

void cvend_write(uint8_t msg_type, uint8_t *msg_data,
		 uint16_t msg_len)
{
	pthread_mutex_lock(&cvend_mutex);
	cvend_packet packet;
	packet.seq = ++last_seq;
	packet.flags = 0;
	packet.msg_type = msg_type;
	packet.msg_data = msg_data;
	packet.msg_len = msg_len;
	calc_hdr_crc(&packet);
	calc_msg_crc(&packet);

	int res = fwrite(&packet.magic, 1, 1, file);
	printf("wrote %i\n", res);
	fwrite(&packet.seq, 1, 1, file);
	fwrite(&packet.flags, 1, 1, file);
	fwrite(&packet.msg_type, 1, 1, file);
	fwrite((uint8_t *)&packet.msg_len + 1, 1, 1, file);
	fwrite(&packet.msg_len, 1, 1, file);
	fwrite(&packet.hdr_crc, 1, 1, file);
	fwrite(packet.msg_data, 1, packet.msg_len, file);
	if (packet.msg_type & 0x80)
		fwrite(&packet.msg_crc, 1, 4, file);

	pthread_mutex_unlock(&cvend_mutex);
	printf("SENT:\n"); 
	print_packet(&packet);
}

void cvend_free(cvend_packet *packet)
{
	free(packet->msg_data);
	free(packet);
}

void print_packet(cvend_packet *packet)
{
	printf("seq      = %u\n", packet->seq);
	printf("flags    = %x\n", packet->flags);
	printf("msg_type = %s", stringify_msg_type(packet->msg_type));
	printf(" (%02X)\n", packet->msg_type);
	printf("msg_len  = %u\n", packet->msg_len);
	printf("hdr_crc  = %u\n", packet->hdr_crc);
	printf("msg_data = ");
	if (packet->msg_type == MTYPE_LOG) {
		printf("%s", packet->msg_data);
	} else {
		for (uint16_t i = 0; i < packet->msg_len; i++)
			printf("%02x ", packet->msg_data[i]);
	}
	printf("\n");
	printf("msg_crc  = %u\n\n", packet->msg_crc);
}
