/*******************************************************************************
 * scriptel-easyscript.c - Copyright 2014 - Scriptel Corporation
 * ----
 * This file contains all of the logic required to implement the keyboard API
 * in platform independant C.
 ******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "scriptel-easyscript.h"
#include "scriptel-easyscript-private.h"

#define ABS(x)((x < 0) ? -x : x)
#define SIGNATURE_BUILDER_SIZE 20

static huffman_node *huffman_head = NULL;
static char signature_builder[SIGNATURE_BUILDER_SIZE];
static char last_error[SCRIPTEL_ERROR_MAX_LEN];
static huffman_node *huffman_current_node = NULL;
static int makeup_code;
static BOOL decoding_x;
static BOOL new_stroke_found;
static int x_sign, y_sign;
static int coordinate_x, prev_coordinate_x, prev_coordinate_y, cumulative_x, cumulative_y;
static signature_linkedlist_node *signature_list_head = NULL;
static signature_linkedlist_node *signature_list_tail = NULL;

static enum {
	SS_CARD_SENTINEL,					///< We're expecting a card sentinel to be next.
	SS_CARD_DATA,						///< We're expecting card data.
	SS_SIGNATURE_SENTINEL,				///< We're expecting a signature sentinel to be next
	SS_PROTOCOL_VERSION,				///< We're expecting a protocol version to be next.
	SS_MODEL,							///< We're expecting a model number to be next.
	SS_FIRMWARE_VERSION,				///< We're expecting a firmware version to be next.
	SS_SIGNATURE_COMPRESSED,			///< We're expecting a compressed signature to be next.
	SS_SIGNATURE_UNCOMPRESSED,			///< We're expecting an uncompressed signature to be next.
	SS_UNKNOWN,							///< Unknown state.
	SS_CARD_INTERRUPTING_UNCOMPRESSED,	///< A card swipe is currently interrupting an uncompressed stream.
	SS_CARD_INTERRUPTING_COMPRESSED		///< A card swipe is currently interrupting a compressed stream.
} signature_state = UNKNOWN;

SCRIPTEL_SYMBOL_EXPORT char * scriptel_get_last_error() {
	return last_error;
}

int append_char(char *string, char c, size_t max_length) {
	size_t string_length = strlen(string);
	if (string_length < max_length - 2) {
		string[string_length++] = c;
		string[string_length] = '\0';
		return 1;
	}
	return 0;
}

SCRIPTEL_SYMBOL_EXPORT scriptel_return_status scriptel_keyboard_parse_signature(scriptel_signature_protocol* sig_protocol, scriptel_signature* signature, char* buffer, int len) {

	scriptel_add_event_listener(scriptel_private_linkedlist_add_to_tail);
	scriptel_private_free_linked_list();

	while (*buffer) {
		if (SCRIPTEL_RETR_SUCCESS == scriptel_keyboard_parse_char(sig_protocol, *buffer++) == SCRIPTEL_RETR_ERROR) {
			scriptel_remove_event_listener(scriptel_private_linkedlist_add_to_tail);
			return SCRIPTEL_RETR_ERROR;
		}
	}

	scriptel_remove_event_listener(scriptel_private_linkedlist_add_to_tail);
	scriptel_private_pack_signature_linked_list(sig_protocol, signature, signature_list_head);
	scriptel_private_free_linked_list();
	return SCRIPTEL_RETR_SUCCESS;
}

SCRIPTEL_SYMBOL_EXPORT void scriptel_print_signature(scriptel_signature* signature) {
	int i, j;
    printf("Scriptel Signature:\n");
    printf("    Protocol Version: %s\n", signature->protocol_version);
    printf("               Model: %s\n", signature->model);
    printf("             Version: %s\n", signature->version);
    printf("               Width: %u\n", signature->width);
    printf("              Height: %u\n", signature->height);
    printf("   Strokes (length: %u):\n", signature->length);
    for(i = 0; i < signature->length; i++) {
        printf("        Stroke %u (length: %u):\n", i + 1, signature->strokes[i].length);
        for(j = 0; j < signature->strokes[i].length; j++) {
            printf("            Coordinate %u: x=%f, y=%f\n", j + 1, signature->strokes[i].coordinates[j].x, signature->strokes[i].coordinates[j].y);
        }
    }
}

SCRIPTEL_SYMBOL_EXPORT void scriptel_free_signature(scriptel_signature* signature) {
	int i;
    free(signature->protocol_version);
    free(signature->model);
    free(signature->version);
    for(i = 0; i < signature->length; i++) {
		free(signature->strokes[i].coordinates);
    }
    free(signature->strokes);
}

SCRIPTEL_SYMBOL_EXPORT void scriptel_free_metadata_data(signature_meta_data* md) {
	if (md->model) {
		free(md->model);
		md->model = NULL;
	}
	if (md->protocol_version) {
		free(md->protocol_version);
		md->model = NULL;
	}
	if (md->version) {
		free(md->version);
		md->model = NULL;
	}
}

SCRIPTEL_SYMBOL_EXPORT scriptel_return_status scriptel_keyboard_parse_cardswipe(const scriptel_cardswipe_protocol* swipe_protocol, scriptel_cardswipe* swipe, char* buffer, int len) {
	int protocolStart, dataStart;
	size_t dataLen;
	char *protocol_version, *card_data;

    if(scriptel_private_check_cardswipe_buffer(swipe_protocol, buffer, len) != SCRIPTEL_RETR_SUCCESS) {
        return SCRIPTEL_RETR_ERROR;
    }

    //TODO: Let's not just assume protocol will always be one character.
    protocolStart = (int) strlen(swipe_protocol->sentinel)+2;
    dataStart = protocolStart + 2;
    dataLen = len - dataStart - 1;

    protocol_version = (char*)malloc(2);
    card_data = (char*)malloc(dataLen);

    //Zero out everything, just to make sure we don't accidently pick up anything we shouldn't.
    memset(swipe, 0, sizeof(scriptel_cardswipe));
    memset(protocol_version, 0, 2);
    memset(card_data, 0, dataLen);
	swipe->type = CBT_CARD_DATA;

    //Copy the strings.
    memcpy(protocol_version, &buffer[protocolStart], 1);
    memcpy(card_data, &buffer[dataStart], dataLen - 1);

    swipe->protocol_version = protocol_version;
    swipe->card_data = card_data;

    //These can reasonably fail and failure can be checked with NULL.
    scriptel_private_parse_financial_card(swipe, card_data, dataLen - 1);
    scriptel_private_parse_identification_card(swipe, card_data, dataLen - 1);

    return SCRIPTEL_RETR_SUCCESS;
}

SCRIPTEL_SYMBOL_EXPORT void scriptel_print_cardswipe(scriptel_cardswipe* swipe) {
	char track_one_expiration[100], track_two_expiration[100], id_birth_date[100], id_expiration[100];
    printf("Scriptel Card Swipe:\n");
    printf("    Protocol Version: %s\n",swipe->protocol_version);
    printf("           Card Data: %s\n",swipe->card_data);
    if(swipe->financial_card != NULL) {
        printf("        Financial Card:\n");
        if(swipe->financial_card->track_one) {
			CTIME(&swipe->financial_card->track_one->expiration, track_one_expiration, 100);
            printf("Financial Card Track One:\n");
            printf("        Account Number: %s\n", swipe->financial_card->track_one->account_number);
            printf("            First Name: %s\n", swipe->financial_card->track_one->first_name);
            printf("             Last Name: %s\n", swipe->financial_card->track_one->last_name);
            printf("            Expiration: %s",   track_one_expiration);
            printf("          Serivce Code: %s\n", swipe->financial_card->track_one->service_code);
            printf("    Discretionary Data: %s\n", swipe->financial_card->track_one->discretionary_data);
        }

        if(swipe->financial_card->track_two) {
			CTIME(&swipe->financial_card->track_two->expiration, track_two_expiration, 100);
            printf("Financial Card Track Two:\n");
            printf("        Account Number: %s\n", swipe->financial_card->track_two->account_number);
            printf("            Expiration: %s",   track_two_expiration);
            printf("          Serivce Code: %s\n", swipe->financial_card->track_two->service_code);
            printf("    Discretionary Data: %s\n", swipe->financial_card->track_two->discretionary_data);
        }
        printf("\n");
        printf("                Issuer: %s\n", scriptel_get_card_issuer_name(swipe->financial_card->card_issuer));
        printf("          Number Valid: %s\n", swipe->financial_card->number_valid ? "True":"False");
    }

    if(swipe->identification_card != NULL) {
        printf("       Identification Card:\n");
        if(swipe->identification_card->track_one) {
            printf("Identification Card Track One:\n");
            printf("               State: %s\n", swipe->identification_card->track_one->state);
            printf("                City: %s\n", swipe->identification_card->track_one->city);
            printf("           Last Name: %s\n", swipe->identification_card->track_one->last_name);
            printf("          First Name: %s\n", swipe->identification_card->track_one->first_name);
            printf("         Middle Name: %s\n", swipe->identification_card->track_one->middle_name);
            printf("             Address: %s\n", swipe->identification_card->track_one->address);
            printf("  Discretionary Data: %s\n", swipe->identification_card->track_one->discretionary_data);
        }
        if(swipe->identification_card->track_two) {
			CTIME(&swipe->identification_card->track_two->expiration, id_expiration, 100);
			CTIME(&swipe->identification_card->track_two->birth_date, id_birth_date, 100);
            printf("Identification Card Track Two:\n");
            printf("       Issuer Number: %s\n", swipe->identification_card->track_two->issuer_number);
            printf("           ID Number: %s\n", swipe->identification_card->track_two->id_number);
            printf("          Expiration: %s", id_expiration);
            printf("          Birth Date: %s", id_birth_date);
        }
        if(swipe->identification_card->track_three) {
            printf("Identification Card Track Three:\n");
            printf("         CDS Version: %i\n", swipe->identification_card->track_three->cds_version);
            printf("Jurisdiction Version: %i\n", swipe->identification_card->track_three->jurisdiction_version);
            printf("            Zip Code: %s\n", swipe->identification_card->track_three->zip_code);
            printf("       License Class: %s\n", swipe->identification_card->track_three->license_class);
            printf("        Restrictions: %s\n", swipe->identification_card->track_three->restrictions);
            printf("        Endorsements: %s\n", swipe->identification_card->track_three->endorsements);
            printf("                 Sex: %s\n", swipe->identification_card->track_three->sex);
            printf("              Height: %s\n", swipe->identification_card->track_three->height);
            printf("              Weight: %s\n", swipe->identification_card->track_three->weight);
            printf("          Hair Color: %s\n", swipe->identification_card->track_three->hair_color);
            printf("           Eye Color: %s\n", swipe->identification_card->track_three->eye_color);
        }
    }
}

SCRIPTEL_SYMBOL_EXPORT void scriptel_free_cardswipe(scriptel_cardswipe* swipe) {
    free(swipe->protocol_version);
    free(swipe->card_data);

    if(swipe->financial_card != NULL) {
        if(swipe->financial_card->track_one != NULL) {
            free(swipe->financial_card->track_one->account_number);
            free(swipe->financial_card->track_one->first_name);
            free(swipe->financial_card->track_one->last_name);
            free(swipe->financial_card->track_one->service_code);
            free(swipe->financial_card->track_one->discretionary_data);
            free(swipe->financial_card->track_one);
        }
        if(swipe->financial_card->track_two != NULL) {
            free(swipe->financial_card->track_two->account_number);
            free(swipe->financial_card->track_two->service_code);
            free(swipe->financial_card->track_two->discretionary_data);
            free(swipe->financial_card->track_two);
        }
        free(swipe->financial_card);
    }

    if(swipe->identification_card != NULL) {
        if(swipe->identification_card->track_one != NULL) {
            free(swipe->identification_card->track_one->state);
            free(swipe->identification_card->track_one->city);
            free(swipe->identification_card->track_one->last_name);
            free(swipe->identification_card->track_one->first_name);
            free(swipe->identification_card->track_one->middle_name);
            free(swipe->identification_card->track_one->address);
            free(swipe->identification_card->track_one->discretionary_data);
            free(swipe->identification_card->track_one);
        }
        if(swipe->identification_card->track_two != NULL) {
            free(swipe->identification_card->track_two);
        }
        if(swipe->identification_card->track_three != NULL) {
            free(swipe->identification_card->track_three->zip_code);
            free(swipe->identification_card->track_three->license_class);
            free(swipe->identification_card->track_three->restrictions);
            free(swipe->identification_card->track_three->endorsements);
            free(swipe->identification_card->track_three->sex);
            free(swipe->identification_card->track_three->height);
            free(swipe->identification_card->track_three->weight);
            free(swipe->identification_card->track_three->hair_color);
            free(swipe->identification_card->track_three->eye_color);
            free(swipe->identification_card->track_three);
        }
    }
}

void scriptel_private_linkedlist_add_to_tail(streaming_event_data *sd) {
	signature_linkedlist_node *new_node = (signature_linkedlist_node *)malloc(sizeof(signature_linkedlist_node));
	memcpy(&new_node->streaming_event, sd, sizeof(signature_linkedlist_node));
	new_node->next = NULL;

	if (signature_list_head == NULL) {
		signature_list_head = signature_list_tail = new_node;
	} else {
		signature_list_tail->next = new_node;
		signature_list_tail = new_node;
	}
}

void scriptel_private_free_linked_list() {
	signature_linkedlist_node *current = signature_list_head;
	while (signature_list_head != NULL) {
		current = signature_list_head->next;
		free(signature_list_head);
		signature_list_head = current;
	}
	signature_list_tail = NULL;
}

scriptel_return_status scriptel_private_check_signature_buffer(scriptel_signature_protocol* sig_protocol, char* buffer, int len) {
    if(len == 0 || buffer == NULL) {
        SPRINTF_ERR("Buffer is either empty or is null.");
        return SCRIPTEL_RETR_ERROR;
    } else if(sig_protocol == NULL) {
        SPRINTF_ERR("Signature protocol is null.");
        return SCRIPTEL_RETR_ERROR;
    } else if(buffer[0] != sig_protocol->start_stream) {
        SPRINTF_ERR("Signature buffer doesn't start with the proper start character: %c != %c", buffer[0], sig_protocol->start_stream);
        return SCRIPTEL_RETR_ERROR;
    } else if(len <= 30) {
        SPRINTF_ERR("Signature is too short.");
        return SCRIPTEL_RETR_ERROR;
    } else if(buffer[len-1] != sig_protocol->end_stream) {
        SPRINTF_ERR("Signature buffer doesn't end with the proper end character: %c != %c", buffer[len-2], sig_protocol->end_stream);
        return SCRIPTEL_RETR_ERROR;
    } else if(!scriptel_private_equals_ignore_case(&buffer[1], (char*)sig_protocol->sentinel, (int) strlen(sig_protocol->sentinel))) {
        SPRINTF_ERR("Signature buffer doesn't start with the correct sentinel.");
        return SCRIPTEL_RETR_ERROR;
    }
    return SCRIPTEL_RETR_SUCCESS;
}

scriptel_return_status scriptel_private_check_cardswipe_buffer(const scriptel_cardswipe_protocol* swipe_protocol, char* buffer, int len) {
    if(len == 0 || buffer == NULL) {
        SPRINTF_ERR("Buffer is either empty or is null.");
        return SCRIPTEL_RETR_ERROR;
    } else if(swipe_protocol == NULL) {
        SPRINTF_ERR("Card swipe protocol is null.");
        return SCRIPTEL_RETR_ERROR;
    } else if(buffer[0] != swipe_protocol->start_stream) {
        SPRINTF_ERR("Card swipe buffer doesn't start with the proper start character: %c != %c", buffer[0], swipe_protocol->start_stream);
        return SCRIPTEL_RETR_ERROR;
    } else if(buffer[len-2] != swipe_protocol->end_stream) {
        SPRINTF_ERR("Card swipe buffer doesn't end with the proper end character: %c != %c", buffer[len-2], swipe_protocol->end_stream);
        return SCRIPTEL_RETR_ERROR;
    } else if((unsigned int)len > strlen(swipe_protocol->sentinel) + 1 && !scriptel_private_equals_ignore_case(&buffer[1], (char*)swipe_protocol->sentinel, (int) strlen(swipe_protocol->sentinel))) {
        SPRINTF_ERR("Card swipe buffer doesn't start with the correct sentinel.");
        return SCRIPTEL_RETR_ERROR;
    }
    return SCRIPTEL_RETR_SUCCESS;
}

void scriptel_private_pack_signature_linked_list(const scriptel_signature_protocol *sig_protocol, scriptel_signature* signature, signature_linkedlist_node *head) {
	signature_linkedlist_node *current_node, *current_stroke;
	int point_index, stroke_index;
	int stroke_count = 0;

	current_node = head;
	while (current_node != NULL) {
		if (current_node->streaming_event.type == CBT_NEWSTROKE) {
			stroke_count++;
		}
		if (stroke_count == 0 && current_node->streaming_event.type == CBT_COORDINATE) {
			stroke_count++;		// there is no new stroke at the begining, so if a coordinate is found, it is the first stroke
		}
		current_node = current_node->next;
	}

	signature->strokes = (stroke_count > 0) ? (scriptel_stroke*)malloc(sizeof(scriptel_stroke) * stroke_count) : NULL;
	signature->length = stroke_count;

	stroke_index = 0;
	current_stroke = head;
	while (current_stroke != NULL) {
		if (current_stroke->streaming_event.type == CBT_COORDINATE) {
			// count points in the current stroke
			current_node = current_stroke;
			int point_count = 0;
			while (current_node != NULL && current_node->streaming_event.type == CBT_COORDINATE) {
				point_count++;
				current_node = current_node->next;
			}
			signature->strokes[stroke_index].length = point_count;
			signature->strokes[stroke_index].coordinates = (scriptel_coordinate*)malloc(sizeof(scriptel_coordinate) * point_count);
			current_node = current_stroke;
			point_index = 0;
			while (current_node != NULL && current_node->streaming_event.type == CBT_COORDINATE)  {
				signature->strokes[stroke_index].coordinates[point_index].x = current_node->streaming_event.coordinate.x;
				signature->strokes[stroke_index].coordinates[point_index].y = current_node->streaming_event.coordinate.y;
				signature->strokes[stroke_index].coordinates[point_index].type = CBT_COORDINATE;
				current_node = current_node->next;
				point_index++;
			}
			current_stroke = current_node;
			stroke_index++;
		} else if (current_stroke->streaming_event.type == CBT_METADATA) {
			signature->model = (char *)malloc(strlen(current_stroke->streaming_event.meta_data.model) + 1);
			strcpy(signature->model, current_stroke->streaming_event.meta_data.model);
			signature->protocol_version = (char *)malloc(strlen(current_stroke->streaming_event.meta_data.protocol_version) + 1);
			strcpy(signature->protocol_version, current_stroke->streaming_event.meta_data.protocol_version);
			signature->version = (char *)malloc(strlen(current_stroke->streaming_event.meta_data.version) + 1);
			strcpy(signature->version, current_stroke->streaming_event.meta_data.version);
			signature->width = sig_protocol->width;
			signature->height = sig_protocol->height;
			current_stroke = current_stroke->next;
		} else if (current_stroke->streaming_event.type == CBT_CANCEL) {
			int i;
			for (i = 0; i < stroke_index; i++) {
				free(signature->strokes[i].coordinates);
			}
			scriptel_private_pack_signature_linked_list(sig_protocol, signature, current_stroke->next);
			return;
		} else {
			current_stroke = current_stroke->next;
		}
	}
}

int scriptel_private_decode_value(const scriptel_signature_protocol *sig_protocol, char c) {
	int axis, low_high, value;
	char v;
	for (axis = 0; axis < 2; axis++) {
		for (low_high = 0; low_high < 2; low_high++) {
			for (value = 0; value < 23; value++) {
				v = sig_protocol->value_table[axis][low_high][value];
				if (c == v && c != '\0') {
					return (low_high) ? value : value * 23;
				}
			}
		}
	}
	return -1;
}

scriptel_return_status scriptel_private_decode_point(const scriptel_signature_protocol* sig_protocol, scriptel_coordinate* coordinate, char* point) {
    int d[2][2] = {{-1,-1},{-1,-1}};
	int itr, axis, low_high, value, max;
	char c, v;

    for(itr=0; itr < 4; itr++) {
        c = point[itr];
        for( axis = 0; axis < 2; axis++) {
			for (low_high = 0; low_high < 2; low_high++) {
                for(value = 0; value < 23; value++) {
					v = sig_protocol->value_table[axis][low_high][value];
                    if(c == v && c != '\0') {
						d[axis][low_high] = value;
                    }
                }
            }
        }
    }

    if(d[0][0] < 0 || d[0][1] < 0 || d[1][0] < 0 || d[1][1] < 0) {
        SPRINTF_ERR("Invalid coordinate: x1=%i, x2=%i, y1=%i, y2=%i",d[0][0],d[0][1],d[1][0],d[1][1]);
        return SCRIPTEL_RETR_ERROR;
    }

    max = (21*23)+22;
    coordinate->x = (((double)(d[0][0] * 23) + d[0][1]) / (double)max) * sig_protocol->width;
    coordinate->y = (((double)(d[1][0] * 23) + d[1][1]) / (double)max) * sig_protocol->height;

    return SCRIPTEL_RETR_SUCCESS;
}

int scriptel_private_read_bits(const unsigned char **cur_byte, int *cur_bit, int num_bits) {
	int i;
	int value = 0;
	for (i = 0; i < num_bits; i++) {
		value <<= 1;
		value |= (**cur_byte >> *cur_bit) & 1;
		if (*cur_bit == 0) {
			*cur_bit = 7;
			(*cur_byte)++;
		} else {
			(*cur_bit)--;
		}
	}
	return value;
}

huffman_node* scriptel_private_create_huffman_tree(huffman_node *head, const unsigned char **current_byte, int *cur_bit, int bit_depth) {

	huffman_node *new_node =  (huffman_node *)malloc(sizeof(huffman_node));

	int leaf = scriptel_private_read_bits(current_byte, cur_bit, 1);
	if (leaf) {
		new_node->left = NULL;
		new_node->right = NULL;
		new_node->value = scriptel_private_read_bits(current_byte, cur_bit, bit_depth);
	} else {
		new_node->left = scriptel_private_create_huffman_tree(new_node, current_byte, cur_bit, bit_depth);
		new_node->right = scriptel_private_create_huffman_tree(new_node, current_byte, cur_bit, bit_depth);
		new_node->value = NODE;
	}
	return new_node;
}

huffman_node* scriptel_private_get_huffman_table() {
	const unsigned char huffman_table[] = {
		0x30, 0x20, 0x76, 0x80, 0x5c, 0x00, 0xa0, 0x80, 0x20, 0x08,
		0x80, 0xc4, 0x04, 0x48, 0x0e, 0x90, 0x2b, 0x23, 0x80, 0xba,
		0x04, 0xa0, 0x04, 0x90, 0x27, 0x00, 0x90, 0x05, 0x40, 0x22,
		0x81, 0x01, 0xe8, 0x01, 0x88, 0x0a, 0x40, 0xf0, 0x81, 0x54,
		0x05, 0xe0, 0x26, 0x80, 0x3a, 0x01, 0xd0, 0x0c, 0x02, 0x06,
		0x70, 0x37, 0x40, 0x8d, 0x03, 0xa8, 0x19, 0x10, 0x1b, 0x80,
		0xe4, 0x02, 0x90, 0x3d, 0x43, 0x01, 0x02, 0x64, 0x09, 0xc8,
		0xc0, 0x09, 0xe0, 0x34, 0x81, 0x78, 0x0a, 0x20, 0x28, 0x01,
		0x2c, 0x08, 0x16, 0x90, 0x00, 0x20, 0x4a, 0x81, 0x74, 0x0c,
		0x60, 0x20, 0x40, 0x36, 0x02, 0xc8, 0x02, 0xc0, 0x48, 0x20,
		0x0c, 0x80, 0xac, 0x0a, 0x10, 0x08, 0x80, 0x78, 0x20, 0x6c,
		0x13, 0x00, 0x9a, 0x01, 0x54, 0x0b, 0x20, 0x33, 0x01, 0x88,
		0x02, 0x90, 0x16, 0x80, 0x57, 0x02, 0xd8, 0x01, 0x7c, 0x0d,
		0x00, 0x30, 0x01, 0xb0, 0x03, 0x08, 0x1b, 0x40, 0x65, 0x02,
		0xb0, 0x03, 0x18, 0x16, 0x00, 0x6a, 0x03, 0x38, 0x06, 0x40,
		0x29, 0x80, 0xd6, 0x05, 0xc0, 0x22, 0x00, 0x81, 0x16, 0xe0,
		0x20, 0x03, 0x03, 0xe1, 0x00, 0x94, 0x07, 0xd0, 0x29, 0x81,
		0x7c, 0x20, 0x02, 0x07, 0x28, 0x1a, 0x40, 0xe2, 0x05, 0x88,
		0x1f, 0xc0, 0x84, 0x40, 0xd6, 0x04, 0x10, 0x1a, 0x20, 0x61,
		0x02, 0x48, 0x0c, 0x80
	};
	if (huffman_head == NULL) {		// is the tree already loaded?
		const unsigned char *current_byte = huffman_table;
		int current_bit = 7;
		int bit_depth = scriptel_private_read_bits(&current_byte, &current_bit, 6);
		huffman_head = scriptel_private_create_huffman_tree(huffman_head, &current_byte, &current_bit, bit_depth);
	}
	return huffman_head;
}

void scriptel_private_free_huffman_tree(huffman_node *head) {
	if (head != NULL) {
		if (head->left != NULL) {
			scriptel_private_free_huffman_tree(head->left);
		}
		if (head->right != NULL) {
			scriptel_private_free_huffman_tree(head->right);
		}
		free(head);
	}
}

SCRIPTEL_SYMBOL_EXPORT void scriptel_free_memory() {
	scriptel_private_free_huffman_tree(huffman_head);
}

int scriptel_private_equals_ignore_case(char* str1, char* str2, int len) {
	int i;
	char c1, c2;
    for(i = 0; i < len; i++) {
        c1 = tolower(str1[i]);
        c2 = tolower(str2[i]);

        if(c1 != c2) {
            return 0;
        }
    }
    return 1;
}

int scriptel_private_check_card_checksum(char* card_number) {
    char table[2][10] = {{0,1,2,3,4,5,6,7,8,9},{0,2,4,6,8,1,3,5,7,9}};
    int sum = 0, flip = 0, i;
	char c;
    for(i = (int)(strlen(card_number) - 1); i >= 0; i--) {
        c = card_number[i];
        if(c < 48 || c > 57) { return 0; }
        sum += table[flip++ & 1][c - 48];
    }
    return sum % 10 == 0;
}

scriptel_financial_card_vendor scriptel_private_check_card_type(char* card_number) {
    int len = (int) strlen(card_number);

    if(card_number[0] == '3' && (card_number[1] == '4' || card_number[1] == '7') && len == 15) {
        return AMERICAN_EXPRESS;
    } else if(card_number[0] == '3' && card_number[1] == '0' && card_number[2] >= '0' && card_number[2] <= '5' && len == 14) {
        return DINERS_CLUB;
    } else if(card_number[0] == '3' && card_number[1] == '6' && len == 14) {
        return DINERS_CLUB;
    } else if(card_number[0] == '3' && card_number[1] == '8' && len == 14) {
        return CARTE_BLANCHE;
    } else if(card_number[0] == '6' && card_number[1] == '0' && card_number[2] == '1' && card_number[3] == '1' && len == 16) {
        return DISCOVER;
    } else if(card_number[0] == '2' && card_number[1] == '1' && card_number[2] == '3' && card_number[3] == '1' && len == 15) {
        return ENROUTE;
    } else if(card_number[0] == '1' && card_number[1] == '8' && card_number[2] == '0' && card_number[3] == '0' && len == 15) {
        return ENROUTE;
    } else if(card_number[0] == '3' && len == 16) {
        return JCB;
    } else if(card_number[0] == '5' && card_number[1] >= '1' && card_number[1] <= '5' && len == 16) {
        return MASTERCARD;
    } else if(card_number[0] == '4' && len >= 13 && len <= 16) {
        return VISA;
    } else {
        return UNKNOWN;
    }
}

scriptel_return_status scriptel_private_parse_financial_card(scriptel_cardswipe* card, char* buffer, int len) {
	scriptel_financial_track_one* track_one;
	scriptel_financial_track_two* track_two;
	scriptel_financial_card* fin_card;
	char* account_number;

    track_one = (scriptel_financial_track_one*)malloc(sizeof(scriptel_financial_track_one));
    memset(track_one, 0, sizeof(scriptel_financial_track_one));
    if(scriptel_private_parse_financial_track_one(track_one, buffer, len) != SCRIPTEL_RETR_SUCCESS) {
        free(track_one);
        track_one = NULL;
    }

    track_two = (scriptel_financial_track_two*)malloc(sizeof(scriptel_financial_track_two));
    memset(track_two, 0, sizeof(scriptel_financial_track_two));
    if(scriptel_private_parse_financial_track_two(track_two, buffer, len) != SCRIPTEL_RETR_SUCCESS) {
        free(track_two);
        track_two = NULL;
    }

    if(track_one != NULL || track_two != NULL) {
        fin_card = (scriptel_financial_card*)malloc(sizeof(scriptel_financial_card));
        memset(fin_card, 0, sizeof(scriptel_financial_card));
        account_number = (track_one != NULL) ? track_one->account_number : track_two->account_number;
        fin_card->card_issuer = scriptel_private_check_card_type(account_number);
        fin_card->number_valid = scriptel_private_check_card_checksum(account_number);

        fin_card->track_one = track_one;
        fin_card->track_two = track_two;
        card->financial_card = fin_card;

        return SCRIPTEL_RETR_SUCCESS;
    }

    //Unable to parse any financial cards.
    return SCRIPTEL_RETR_ERROR;
}

scriptel_return_status scriptel_private_parse_financial_track_one(scriptel_financial_track_one* track, char* buffer, int len) {
	int track_one_end, account_number_end, name_end, i, name_split, trim_right;
    //Check for start and end characters:
    if(buffer[0] != '%') {
        return SCRIPTEL_RETR_ERROR;
    }
    track_one_end = scriptel_private_chrpos(buffer, '?', 0, len) + 1;

    //Check for the leading letter.
    if(buffer[1] < 'A' || buffer[1] > 'Z') {
        return SCRIPTEL_RETR_ERROR;
    }

    account_number_end = scriptel_private_chrpos(buffer, '^', 2, track_one_end);
    if(account_number_end < 0) {
        return SCRIPTEL_RETR_ERROR;
    }

    name_end = scriptel_private_chrpos(buffer, '^', account_number_end + 1, track_one_end);
    if(name_end < 0) {
        return SCRIPTEL_RETR_ERROR;
    }

    //Check to make sure account number is actually numeric.
    for(i=2; i < account_number_end; i++) {
        if(buffer[i] < '0' || buffer[i] > '9') {
            return SCRIPTEL_RETR_ERROR;
        }
    }

    //Check to make sure service code is actually numeric also.
    for(i=name_end + 5; i < name_end + 8 ; i++) {
        if(buffer[i] < '0' || buffer[i] > '9') {
            return SCRIPTEL_RETR_ERROR;
        }
    }

    track->account_number = scriptel_private_substr(buffer, 2, account_number_end - 2);

    name_split = scriptel_private_chrpos(buffer, '/', account_number_end + 1, name_end);
    trim_right = name_end;

    //Find the end of the first name.
    while(buffer[trim_right-1] == ' ') {
        trim_right--;
    }

    if(name_split >= 0) {
        track->first_name = scriptel_private_substr(buffer, name_split + 1, trim_right - name_split - 1);
        track->last_name = scriptel_private_substr(buffer, account_number_end + 1, name_split - account_number_end - 1);
    } else {
        track->first_name = scriptel_private_substr(buffer, account_number_end + 1, trim_right - account_number_end - 1);
        track->last_name = (char*)malloc(1);
        track->last_name[0] = 0x0;
    }

    track->expiration = scriptel_private_parse_expiration(&buffer[name_end+1]);

    track->service_code = scriptel_private_substr(buffer, name_end + 5, 3);
    track->discretionary_data = scriptel_private_substr(buffer, name_end + 8, track_one_end - (name_end + 8) - 1);

    return SCRIPTEL_RETR_SUCCESS;
}

time_t scriptel_private_parse_expiration(char* buffer) {
    int year = 2000 + ((buffer[0] - '0') * 10) + ((buffer[1] - '0'));
    int month = ((buffer[2] - '0') * 10) + ((buffer[3] - '0'));
	struct tm expires;

    if(month < 0) {
        month = 0;
    }

    memset(&expires, 0, sizeof(struct tm));
    expires.tm_year = year - 1900;
    expires.tm_mon = month;
    expires.tm_mday = 1;
    expires.tm_isdst = -1;

    return mktime(&expires) - 1;
}

scriptel_return_status scriptel_private_parse_financial_track_two(scriptel_financial_track_two* track, char* buffer, int len) {
	int track_two_start, track_two_end, account_number_end;

    track_two_start = scriptel_private_chrpos(buffer, ';', 0, len);
    if(track_two_start < 0) {
        return SCRIPTEL_RETR_ERROR;
    }

    track_two_end = scriptel_private_chrpos(buffer, '?', track_two_start, len);
    if(track_two_end < 0) {
        return SCRIPTEL_RETR_ERROR;
    }

    account_number_end = scriptel_private_chrpos(buffer, '=', track_two_start, track_two_end);
    if(account_number_end < 0) {
        return SCRIPTEL_RETR_ERROR;
    }
    
    track->account_number = scriptel_private_substr(buffer, track_two_start + 1, account_number_end - track_two_start - 1);
    track->expiration = scriptel_private_parse_expiration(&buffer[account_number_end + 1]);
    track->service_code = scriptel_private_substr(buffer, account_number_end + 5, 3);
    track->discretionary_data = scriptel_private_substr(buffer, account_number_end + 8, track_two_end - (account_number_end + 8));

    return SCRIPTEL_RETR_SUCCESS;
}

scriptel_return_status scriptel_private_parse_identification_card(scriptel_cardswipe* card, char* buffer, int len) {
	scriptel_identification_track_one* track_one;
	scriptel_identification_track_two* track_two;
	scriptel_identification_track_three* track_three;
	scriptel_identification_card* id_card;

    track_one = (scriptel_identification_track_one*)malloc(sizeof(scriptel_identification_track_one));
    memset(track_one, 0, sizeof(scriptel_identification_track_one));
    if(scriptel_private_parse_identification_track_one(track_one, buffer, len) != SCRIPTEL_RETR_SUCCESS) {
        free(track_one);
        track_one = NULL;
    }
    track_two = (scriptel_identification_track_two*)malloc(sizeof(scriptel_identification_track_two));
    memset(track_two, 0, sizeof(scriptel_identification_track_two));
    if(scriptel_private_parse_identification_track_two(track_two, buffer, len) != SCRIPTEL_RETR_SUCCESS) {
        free(track_two);
        track_two = NULL;
    }
    track_three = (scriptel_identification_track_three*)malloc(sizeof(scriptel_identification_track_three));
    memset(track_three, 0, sizeof(scriptel_identification_track_three));
    if(scriptel_private_parse_identification_track_three(track_three, buffer, len) != SCRIPTEL_RETR_SUCCESS) {
        free(track_three);
        track_three = NULL;
    }

    if(track_one != NULL || track_two != NULL || track_three != NULL) {
        id_card = (scriptel_identification_card*)malloc(sizeof(scriptel_identification_card));
        memset(id_card, 0, sizeof(scriptel_identification_card));

        id_card->track_one = track_one;
        id_card->track_two = track_two;
        id_card->track_three = track_three;
        
        card->identification_card = id_card;
        return SCRIPTEL_RETR_SUCCESS;
    }
    //Unable to parse any financial cards.
    return SCRIPTEL_RETR_ERROR;
}

scriptel_return_status scriptel_private_parse_identification_track_one(scriptel_identification_track_one* track, char* buffer, int len) {
	int track_one_end, city_end, name_end, name_end_trim, last_name_end, first_name_end, middle_name_end, address_end, discretionary_data_end;
    //Check for start and end characters:
    if(buffer[0] != '%') {
        return SCRIPTEL_RETR_ERROR;
    }

    track_one_end = scriptel_private_chrpos(buffer, '?', 0, len) + 1;
    if(track_one_end < 0) {
        return SCRIPTEL_RETR_ERROR;
    }

    city_end = scriptel_private_chrpos(buffer, '^', 1, track_one_end);
    if(city_end < 0) {
        return SCRIPTEL_RETR_ERROR;
    }

    name_end = scriptel_private_chrpos(buffer, '^', city_end + 1, track_one_end);
    if(name_end < 0) {
        return SCRIPTEL_RETR_ERROR;
    }
    name_end_trim = name_end;

    track->state = scriptel_private_substr(buffer, 1, 2);
    track->city = scriptel_private_substr(buffer, 3, city_end - 3);

    last_name_end = scriptel_private_chrpos(buffer, '$', city_end + 1, name_end);

	if(last_name_end < 0) {
		return SCRIPTEL_RETR_ERROR;
	}

    first_name_end = (last_name_end > 0) ? scriptel_private_chrpos(buffer, '$', last_name_end + 1, name_end) : -1;
    middle_name_end = (first_name_end > 0) ? name_end - 1 : -1;

    while(buffer[name_end_trim - 1] == ' ') {
        name_end_trim--;
    }

    track->last_name = scriptel_private_substr(buffer, city_end + 1, last_name_end - (city_end + 1));
    track->first_name = (first_name_end >= 0) ? scriptel_private_substr(buffer, last_name_end + 1, first_name_end - (last_name_end + 1)) : (char*)malloc(1);
    track->middle_name = (middle_name_end >= 0) ? scriptel_private_substr(buffer, first_name_end + 1, middle_name_end - (first_name_end)) : (char*)malloc(1);

    if(first_name_end < 0) {
        track->first_name[0] = 0;
    }
    if(middle_name_end < 0) {
        track->middle_name[0] = 0;
    }

    if(track->middle_name[strlen(track->middle_name) - 1] == '$') {
        track->middle_name[strlen(track->middle_name) - 1] = 0;
    }

    address_end = scriptel_private_chrpos(buffer, '^', name_end + 1, len);
    if(address_end < 0) {
        return SCRIPTEL_RETR_ERROR;
    }

    track->address = scriptel_private_substr(buffer, name_end + 1, address_end - name_end - 1);

    discretionary_data_end = scriptel_private_chrpos(buffer, '?', address_end + 1, len);
    track->discretionary_data = (discretionary_data_end >= 0) ? scriptel_private_substr(buffer, discretionary_data_end, discretionary_data_end - address_end - 1) : (char*)malloc(1);
    if(discretionary_data_end < 0) {
        track->discretionary_data[0] = 0;
    }

    return SCRIPTEL_RETR_SUCCESS;
}

scriptel_return_status scriptel_private_parse_identification_track_two(scriptel_identification_track_two* track, char* buffer, int len) {
	int track_two_start, track_two_end, account_number_end, i, birth_year, birth_month, birth_day;
	struct tm birth_date;

	track_two_start = scriptel_private_chrpos(buffer, ';', 0, len);
    if(track_two_start < 0) {
        return SCRIPTEL_RETR_ERROR;
    }

    track_two_end = scriptel_private_chrpos(buffer, '?', track_two_start, len);
    if(track_two_end < 0) {
        return SCRIPTEL_RETR_ERROR;
    }

    account_number_end = scriptel_private_chrpos(buffer, '=', track_two_start, track_two_end);
    if(account_number_end < 0) {
        return SCRIPTEL_RETR_ERROR;
    }

    if(track_two_end - track_two_start < 10) {
        return SCRIPTEL_RETR_ERROR;
    }
    
    track->issuer_number = scriptel_private_substr(buffer, track_two_start + 1, 6);
    track->id_number = scriptel_private_substr(buffer, track_two_start + 7, account_number_end - track_two_start - 8);
    track->expiration = scriptel_private_parse_expiration(&buffer[account_number_end + 1]);

    //Verify we're numeric.
    for(i = account_number_end + 5; i < account_number_end + 13; i++) {
        if(buffer[i] < '0' || buffer[i] > '9') {
            return SCRIPTEL_RETR_ERROR;
        }
    }

    birth_year = 0;
    birth_month = 0;
    birth_day = 0;

    birth_year += (buffer[account_number_end + 5] - '0') * 1000;
    birth_year += (buffer[account_number_end + 6] - '0') * 100;
    birth_year += (buffer[account_number_end + 7] - '0') * 10;
    birth_year += (buffer[account_number_end + 8] - '0');

    birth_month += (buffer[account_number_end + 9] - '0') * 10;
    birth_month += (buffer[account_number_end + 10] - '0');

    birth_day += (buffer[account_number_end + 11] - '0') * 10;
    birth_day += (buffer[account_number_end + 12] - '0');

	if(birth_year < 1800) {
		return SCRIPTEL_RETR_ERROR;
	} else if(birth_day < 1 || birth_day > 32) {
		return SCRIPTEL_RETR_ERROR;
	} else if(birth_month < 1 || birth_month > 12) {
		return SCRIPTEL_RETR_ERROR;
	}

    memset(&birth_date, 0, sizeof(struct tm));
    birth_date.tm_year = birth_year - 1900;
    birth_date.tm_mon = birth_month - 1;
    birth_date.tm_mday = birth_day;
    birth_date.tm_isdst = -1;

    track->birth_date = mktime(&birth_date);

    return SCRIPTEL_RETR_SUCCESS;
}

scriptel_return_status scriptel_private_parse_identification_track_three(scriptel_identification_track_three* track, char* buffer, int len) {
	int track_three_start, track_three_end;

    track_three_start = scriptel_private_rchrpos(buffer, '%', 0, len);
    if(track_three_start < 0) {
        return SCRIPTEL_RETR_ERROR;
    }

    track_three_end = scriptel_private_chrpos(buffer, '?', track_three_start, len);
    if(track_three_end < 0) {
        return SCRIPTEL_RETR_ERROR;
    }

    if(track_three_end - track_three_start < 40) {
        return SCRIPTEL_RETR_ERROR;
    }

    track->cds_version = buffer[track_three_start + 1] - '0';
    track->jurisdiction_version = buffer[track_three_start + 2] - '0';

	if(track->cds_version > 9 || track->cds_version < 0) {
		return SCRIPTEL_RETR_ERROR;
	} else if(track->jurisdiction_version > 9 || track->jurisdiction_version < 0) {
		return SCRIPTEL_RETR_ERROR;
	}

    track->zip_code = scriptel_private_substr(buffer, track_three_start + 3, 11);
    track->license_class = scriptel_private_substr(buffer, track_three_start + 14, 2);
    track->restrictions = scriptel_private_substr(buffer, track_three_start + 16, 10);
    track->endorsements = scriptel_private_substr(buffer, track_three_start + 26, 4);
    track->sex = scriptel_private_substr(buffer, track_three_start + 30, 1);
    track->height = scriptel_private_substr(buffer, track_three_start + 31, 3);
    track->weight = scriptel_private_substr(buffer, track_three_start + 34, 3);
    track->hair_color = scriptel_private_substr(buffer, track_three_start + 37, 3);
    track->eye_color = scriptel_private_substr(buffer, track_three_start + 40, 3);

    scriptel_private_rtrim(track->zip_code);
    scriptel_private_rtrim(track->license_class);
    scriptel_private_rtrim(track->restrictions);
    scriptel_private_rtrim(track->endorsements);
    scriptel_private_rtrim(track->sex);
    scriptel_private_rtrim(track->height);
    scriptel_private_rtrim(track->weight);
    scriptel_private_rtrim(track->hair_color);
    scriptel_private_rtrim(track->eye_color);


    return SCRIPTEL_RETR_SUCCESS;
}


/**
* @brief This array contains textual identifiers for the
* scriptel_financial_card_vendor enumeration.
*/
const static char SCRIPTEL_CARD_VENDORS[9][30] = {
	"American Express",
	"Diners Club",
	"Carte Blanche",
	"Discover",
	"EnRoute",
	"JCB",
	"Mastercard",
	"Visa",
	"Unknown"
};

SCRIPTEL_SYMBOL_EXPORT const char* scriptel_get_card_issuer_name(scriptel_financial_card_vendor vendor) {
	return SCRIPTEL_CARD_VENDORS[vendor];
}

int scriptel_private_chrpos(char* str1, char chr, int start, int max) {
	int x;
    for(x=start; x < max; x++) {
        if(str1[x] == chr) {
            return x;
        }
    }
    return -1;
}

int scriptel_private_rchrpos(char* str1, char chr, int start, int max) {
	int x;
    for (x = max - 1; x >= start; x--) {
        if(str1[x] == chr) {
            return x;
        }
    }
    return -1;
}

char* scriptel_private_substr(char* str1, int idx, int len) {
    char* new_str;
	new_str = (char*)malloc(len + 1);
    memset(new_str, 0, len + 1);
    memcpy(new_str, &str1[idx], len);
    return new_str;
}

void scriptel_private_rtrim(char* str) {
	int x;
    for (x = (int)(strlen(str) - 1); x >= 0 && str[x] == ' '; x--) {
        str[x] = 0;
    }
}

struct event_node {
	struct event_node *next;
	void(*eventListener)(streaming_event_data *sd);
};

static struct event_node *eventHead = NULL;

SCRIPTEL_SYMBOL_EXPORT void scriptel_add_event_listener(void(*eventListener)(streaming_event_data *sd)) {
	struct event_node *newNode = (struct event_node *) malloc(sizeof(struct event_node));
	newNode->eventListener = eventListener;
	newNode->next = eventHead;
	eventHead = newNode;
}

SCRIPTEL_SYMBOL_EXPORT void scriptel_remove_event_listener(void(*eventListener)(streaming_event_data *sd)) {
	struct event_node *node = eventHead;
	struct event_node *prevNode = NULL;
	while (node) {
		if (node->eventListener == eventListener) {
			if (prevNode == NULL) {
				eventHead = node->next;
			} else {
				prevNode->next = node->next;
			}
			free(node);
			return;
		} else {
			prevNode = node;
			node = node->next;
		}
	}
}

SCRIPTEL_SYMBOL_EXPORT void scriptel_remove_all_event_listeners() {
	struct event_node *node = eventHead;
	struct event_node *next;
	while (node) {
		next = node->next;
		free(node);
		node = next;
	}
	eventHead = NULL;
}

void scriptel_private_notify(streaming_event_data *sd) {
	struct event_node *node = eventHead;
	while (node) {
		node->eventListener(sd);
		node = node->next;
	}
}

void scriptel_private_initialize_streaming() {
	makeup_code = 0;
	huffman_current_node = NULL;
	decoding_x = TRUE;
	new_stroke_found = FALSE;
	x_sign = y_sign = 1;
	cumulative_x = cumulative_y = 0;
}

scriptel_return_status parse_uncompressed_signature(const scriptel_signature_protocol *sig_protocol, char c, int current_position) {
	streaming_event_data event;

	if (c == sig_protocol->pen_up) {
		new_stroke_found = TRUE;
		if (strlen(signature_builder) != 0) {
			SPRINTF_ERR("Buffer wasn't empty at position %d when a new stroke was found.", current_position);
			return SCRIPTEL_RETR_ERROR;
		}
	}
	else if (c == sig_protocol->end_stream) {
		event.type = CBT_END_OF_SIGNATURE;
		scriptel_private_notify(&event);
		signature_state = UNKNOWN;
	}
	else if (c == sig_protocol->cancel) {
		event.type = CBT_CANCEL;
		scriptel_private_notify(&event);
		signature_state = UNKNOWN;
	} else {
		append_char(signature_builder, c, SIGNATURE_BUILDER_SIZE);
		if (strlen(signature_builder) == 4) {
			scriptel_coordinate coordinate;
			scriptel_private_decode_point(sig_protocol, &coordinate, signature_builder);
			if (new_stroke_found) {
				event.type = CBT_NEWSTROKE;
				scriptel_private_notify(&event);
				new_stroke_found = FALSE;
			}
			event.type = CBT_COORDINATE;
			event.coordinate.x = coordinate.x;
			event.coordinate.y = coordinate.y;
			signature_builder[0] = '\0';
			scriptel_private_notify(&event);
		}
	}
	return SCRIPTEL_RETR_SUCCESS;
}

void scriptel_private_data_decode(const scriptel_signature_protocol *sig_protocol, int value) {
	streaming_event_data event;
	if (decoding_x) {
		switch (value) {
		case NEW_STROKE_CODE:
			new_stroke_found = TRUE;
			break;
		case CANCEL_CODE:
			event.type = CBT_CANCEL;
			scriptel_private_notify(&event);
			signature_state = UNKNOWN;
			break;
		case REVERSE_X_CODE:
			x_sign = -x_sign;
			break;
		case REVERSE_Y_CODE:
			y_sign = -y_sign;
			break;
		case REVERSE_X_AND_Y_CODE:
			x_sign = -x_sign;
			y_sign = -y_sign;
			break;
		case REPEAT_HALF_CODE:
			coordinate_x = prev_coordinate_x;
			decoding_x = FALSE;
			break;
		case REPEAT_HALF_SWAP_CODE:
			coordinate_x = REPEAT_HALF_CODE;
			decoding_x = FALSE;
			break;
		case REVERSE_X_SWAP_CODE:
			coordinate_x = REVERSE_X_CODE;
			decoding_x = FALSE;
			break;
		case REVERSE_Y_SWAP_CODE:
			coordinate_x = REVERSE_Y_CODE;
			decoding_x = FALSE;
			break;
		case REVERSE_X_AND_Y_SWAP_CODE:
			coordinate_x = REVERSE_X_AND_Y_CODE;
			decoding_x = FALSE;
			break;
		case NEW_STROKE_SWAP_CODE:
			coordinate_x = NEW_STROKE_CODE;
			decoding_x = FALSE;
			break;
		default:
			coordinate_x = value;
			decoding_x = FALSE;
			break;
		}
	} else {
		int coordinate_y;
		prev_coordinate_x = coordinate_x;
		switch (value) {
		case REPEAT_HALF_CODE:
			coordinate_y = prev_coordinate_y;
			break;
		case REPEAT_HALF_SWAP_CODE:
			coordinate_y = REPEAT_HALF_CODE;
			break;
		default:
			coordinate_y = value;
			prev_coordinate_y = coordinate_y;
			break;
		}
		if (new_stroke_found) {
			event.type = CBT_NEWSTROKE;
			scriptel_private_notify(&event);
			new_stroke_found = FALSE;
		}
		decoding_x = TRUE;
		cumulative_x += coordinate_x * x_sign;
		cumulative_y += coordinate_y * y_sign;
		event.type = CBT_COORDINATE;
		event.coordinate.x =  cumulative_x * (sig_protocol->width - 1) / 2999.0;
		event.coordinate.y =  cumulative_y * (sig_protocol->height - 1) / 2999.0;
		scriptel_private_notify(&event);
	}
}

void scripte_private_decompress_bits(const scriptel_signature_protocol *sig_protocol, int bits) {
	int i, b;
	if (huffman_current_node == NULL) {
		huffman_current_node = scriptel_private_get_huffman_table();
	}
	for (i = 256; i > 0; i >>= 1) {
		b = bits & i;
		if (b == 0) {
			huffman_current_node = huffman_current_node->left;
		} else {
			huffman_current_node = huffman_current_node->right;
		}
		if (huffman_current_node->value != NODE) {
			if (huffman_current_node->value < 64) {  // terminator code
				scriptel_private_data_decode(sig_protocol, makeup_code + huffman_current_node->value);
				makeup_code = 0;
			} else {                            // makeup code
				makeup_code = huffman_current_node->value;
			}
			huffman_current_node = scriptel_private_get_huffman_table();
		}
	}
};

int scriptel_private_convert_bodnar_chars_to_value(const scriptel_signature_protocol *sig_protocol, char *sig_chars) {
	if (strlen(sig_chars) < 2){
		return -1;
	}
	int v1, v2, v3, value;
	v1 = scriptel_private_decode_value(sig_protocol, sig_chars[0]);
	v2 = scriptel_private_decode_value(sig_protocol, sig_chars[1]);
	if (v1 < 0 || v2 < 0) {
		return -2;
	}
	value = v1 + v2;
	if (value == 505) {
		if (strlen(sig_chars) > 2) {
			v3 = scriptel_private_decode_value(sig_protocol, sig_chars[2]);
			if (v3 < 0) {
				return -2;
			}
			value += v3;
		}
		else {
			return -1;
		}
	}
	return value;
}

scriptel_return_status parse_compressed_signature(const scriptel_signature_protocol *sig_protocol, char c, int current_position) {
	streaming_event_data event;
	if (c == sig_protocol->end_stream) {
		event.type = CBT_END_OF_SIGNATURE;
		scriptel_private_notify(&event);
		return SCRIPTEL_RETR_SUCCESS;
	} else if (c == sig_protocol->cancel) {
		//Signature cancelled.
		event.type = CBT_CANCEL;
		scriptel_private_notify(&event);
		return SCRIPTEL_RETR_SUCCESS;
	}
	append_char(signature_builder, c, SIGNATURE_BUILDER_SIZE);
	if (strlen(signature_builder) < 2) {
		return SCRIPTEL_RETR_SUCCESS;     // we know we need at least 2 characters
	}
	int result = scriptel_private_convert_bodnar_chars_to_value(sig_protocol, signature_builder);
	if (result >= 0) {
		scripte_private_decompress_bits(sig_protocol, result);
		signature_builder[0] = '\0';
	} else if (result == -2) {
		SPRINTF_ERR("Bad values found in signature near position %d", current_position);
		return SCRIPTEL_RETR_ERROR;
	}
	return SCRIPTEL_RETR_SUCCESS;
}

#define CARD_BUFFER_SIZE (255)

void parse_card(char chr, cardState_t *card_state, char *card_buffer) {

	append_char(card_buffer, chr, CARD_BUFFER_SIZE);

	switch (*card_state) {
	case CS_UNKNOWN:
		if (chr == STN_CARDSWIPE_PROTOCOL.protocol_name[strlen(card_buffer) - 2]) {
			*card_state = CS_CARD_DATA;
			return;
		}
		*card_state = CS_NOT_A_CARD;
	case CS_CARD_DATA:
		if (chr == STN_CARDSWIPE_PROTOCOL.end_stream) {
			streaming_event_data streamingData;
			streamingData.type = CBT_CARD_DATA;
			scriptel_keyboard_parse_cardswipe(&STN_CARDSWIPE_PROTOCOL, &streamingData.card_swipe, card_buffer, strlen(card_buffer) + 1);
			scriptel_private_notify(&streamingData);
			*card_state = CS_CARD_PROCESSED;
		}
		break;
	default:
		*card_state = CS_UNKNOWN;
	}
}

#define SIGNATURE_BUFFER_SIZE 50
SCRIPTEL_SYMBOL_EXPORT scriptel_return_status scriptel_keyboard_parse_char(const scriptel_signature_protocol* sig_protocol, char chr) {
	static cardState_t card_state;
	static char signature_buffer[SIGNATURE_BUFFER_SIZE];
	static char card_buffer[CARD_BUFFER_SIZE];
	static int current_position = 0;
	static streaming_event_data streaming_data;

	current_position++;

	switch (signature_state) {
	case SS_UNKNOWN:
		if (chr == sig_protocol->start_stream) {
			signature_state = SS_SIGNATURE_SENTINEL;
			current_position = 1;
			signature_buffer[0] = '\0';
		}
		else if (chr == STN_CARDSWIPE_PROTOCOL.start_stream) {
			signature_state = SS_CARD_SENTINEL;
			current_position = 1;
			card_buffer[0] = chr;
			card_buffer[1] = '\0';
			card_state = CS_UNKNOWN;
		}
		break;
	case SS_CARD_SENTINEL:
		parse_card(chr, &card_state, card_buffer);
		if (card_state == CS_CARD_PROCESSED || card_state == CS_NOT_A_CARD) {
			card_state = CS_UNKNOWN;
			signature_state = SS_UNKNOWN;
		}
		break;
	case SS_SIGNATURE_SENTINEL:
		if (chr == sig_protocol->pen_up) {
			if (strcmp(signature_buffer, sig_protocol->sentinel) == 0) {
				signature_state = SS_PROTOCOL_VERSION;
				signature_buffer[0] = '\0';
				streaming_data.type = CBT_METADATA;
				streaming_data.meta_data.model = NULL;
				streaming_data.meta_data.protocol_version = NULL;
				streaming_data.meta_data.version = NULL;
			} else {
				SPRINTF_ERR("Signature sentinel doesn't appear to be correct at position %d: %s", current_position, signature_buffer);
				card_state = CS_UNKNOWN;
				return SCRIPTEL_RETR_ERROR;
			}
		}
		else {
			append_char(signature_buffer, chr, SIGNATURE_BUFFER_SIZE);
		}
		break;
	case SS_PROTOCOL_VERSION:
		if (chr == sig_protocol->pen_up) {
			int buffer_length = strlen(signature_buffer) + 2;
			streaming_data.meta_data.protocol_version = (char *)malloc(buffer_length);
			streaming_data.meta_data.protocol_version[0] = signature_buffer[0];
			streaming_data.meta_data.protocol_version[1] = '\0';
			strcpy(streaming_data.meta_data.protocol_version + 2, signature_buffer + 1);
			signature_state = SS_MODEL;
			signature_buffer[0] = '\0';
		}
		else {
			append_char(signature_buffer, chr, SIGNATURE_BUFFER_SIZE);
		}
		break;
	case SS_MODEL:
		if (chr == sig_protocol->pen_up) {
			int buffer_length = strlen(signature_buffer) + 1;
			streaming_data.meta_data.model = (char *)malloc(buffer_length);
			strcpy(streaming_data.meta_data.model, signature_buffer);
			signature_buffer[0] = '\0';
			signature_state = SS_FIRMWARE_VERSION;
		}
		else {
			append_char(signature_buffer, chr, SIGNATURE_BUFFER_SIZE);
		}
		break;
	case SS_FIRMWARE_VERSION:
		if (chr == sig_protocol->pen_up) {
			int buffer_length = strlen(signature_buffer) + 1;
			streaming_data.meta_data.version = (char *)malloc(buffer_length);
			strcpy(streaming_data.meta_data.version, signature_buffer);
			signature_buffer[0] = '\0';

			scriptel_private_notify(&streaming_data);
			signature_builder[0] = '\0';

			if ('A' == streaming_data.meta_data.protocol_version[0] || 'B' == streaming_data.meta_data.protocol_version[0] || 'C' == streaming_data.meta_data.protocol_version[0]) {
				//Uncompressed
				signature_state = SS_SIGNATURE_UNCOMPRESSED;
			}
			else if ('D' == streaming_data.meta_data.protocol_version[0] || 'E' == streaming_data.meta_data.protocol_version[0]) {
				//Compressed
				signature_state = SS_SIGNATURE_COMPRESSED;
			} else {
				signature_state = SS_UNKNOWN;
				SPRINTF_ERR("Unrecognized protocol version at position %d: %s", current_position, signature_buffer);
				return SCRIPTEL_RETR_ERROR;
			}
			signature_buffer[0] = '\0';
			scriptel_private_initialize_streaming();
		} else {
			append_char(signature_buffer, chr, SIGNATURE_BUFFER_SIZE);
		}
		break;
	case SS_SIGNATURE_UNCOMPRESSED:
	case SS_SIGNATURE_COMPRESSED:
		if (chr == sig_protocol->start_stream) {
			//Appears to be a new signature, assume the previous one was canceled.
			streaming_data.type = CBT_CANCEL;
			scriptel_private_notify(&streaming_data);
			signature_state = SS_UNKNOWN;
			signature_buffer[0] = '\0';
			//Re-enter this function.
			scriptel_keyboard_parse_char(sig_protocol, chr);
			break;
		} else if (chr == sig_protocol->end_stream) {
			streaming_data.type = CBT_END_OF_SIGNATURE;
			scriptel_private_notify(&streaming_data);
			signature_state = SS_UNKNOWN;
			break;
		}

		if (chr == STN_CARDSWIPE_PROTOCOL.start_stream && card_state != CS_NOT_A_CARD) {
			// This might be a card, give the card parser a swing at this.
			signature_state = (signature_state == SS_SIGNATURE_UNCOMPRESSED) ? SS_CARD_INTERRUPTING_UNCOMPRESSED : SS_CARD_INTERRUPTING_COMPRESSED;
			card_state = CS_UNKNOWN;
			card_buffer[0] = '\0';
			append_char(card_buffer, chr, CARD_BUFFER_SIZE);
		} else {
			scriptel_return_status retVal;
			if (signature_state == SS_SIGNATURE_UNCOMPRESSED) {
				retVal = parse_uncompressed_signature(sig_protocol, chr, current_position);
			}
			else {
				retVal = parse_compressed_signature(sig_protocol, chr, current_position);
			}
			if (retVal != SCRIPTEL_RETR_SUCCESS) {
				signature_state = SS_UNKNOWN;
				signature_buffer[0] = '\0';
				return retVal;
			}
		}
		break;
	case SS_CARD_INTERRUPTING_UNCOMPRESSED:
	case SS_CARD_INTERRUPTING_COMPRESSED:
		parse_card(chr, &card_state, card_buffer);
		if (card_state == CS_NOT_A_CARD) {
			//We were mistaken, this wasn't a card swipe after all.
			signature_state = (signature_state == SS_CARD_INTERRUPTING_UNCOMPRESSED) ? SS_SIGNATURE_UNCOMPRESSED : SS_SIGNATURE_COMPRESSED;
			char *p = card_buffer;
			while (*p != '\0') {
				scriptel_keyboard_parse_char(sig_protocol, *p++);
			}
			card_state = CS_UNKNOWN;
			card_buffer[0] = '\0';
		} else if (card_state == CS_CARD_PROCESSED) {
			signature_state = (signature_state == SS_CARD_INTERRUPTING_UNCOMPRESSED) ? SS_SIGNATURE_UNCOMPRESSED : SS_SIGNATURE_COMPRESSED;
		}
		break;
	default:
		//We're just going to ignore this character.
		break;
	}
	return SCRIPTEL_RETR_SUCCESS;
}

SCRIPTEL_SYMBOL_EXPORT scriptel_return_status scriptel_keyboard_parse_keycode(const scriptel_signature_protocol* sig_protocol, int code, int shift) {
	char c;
	switch (code) {
	case VK_BACK_QUOTE:    c = (!shift) ? '`' : '~';
	case VK_1:             c = (!shift) ? '1' : '!';
	case VK_2:             c = (!shift) ? '2' : '@';
	case VK_3:             c = (!shift) ? '3' : '#';
	case VK_4:             c = (!shift) ? '4' : '$';
	case VK_5:             c = (!shift) ? '5' : '%';
	case VK_6:             c = (!shift) ? '6' : '^';
	case VK_7:             c = (!shift) ? '7' : '&';
	case VK_8:             c = (!shift) ? '8' : '*';
	case VK_9:             c = (!shift) ? '9' : '(';
	case VK_0:             c = (!shift) ? '0' : ')';
	case VK_MINUS:         c = (!shift) ? '-' : '_';
	case VK_EQUALS:        c = (!shift) ? '=' : '+';

	case VK_Q:             c = (!shift) ? 'q' : 'Q';
	case VK_W:             c = (!shift) ? 'w' : 'W';
	case VK_E:             c = (!shift) ? 'e' : 'E';
	case VK_R:             c = (!shift) ? 'r' : 'R';
	case VK_T:             c = (!shift) ? 't' : 'T';
	case VK_Y:             c = (!shift) ? 'y' : 'Y';
	case VK_U:             c = (!shift) ? 'u' : 'U';
	case VK_I:             c = (!shift) ? 'i' : 'I';
	case VK_O:             c = (!shift) ? 'o' : 'O';
	case VK_P:             c = (!shift) ? 'p' : 'P';
	case VK_OPEN_BRACKET:  c = (!shift) ? '[' : '{';
	case VK_CLOSE_BRACKET: c = (!shift) ? ']' : '}';
	case VK_BACK_SLASH:    c = (!shift) ? '\\' : '|';

	case VK_A:             c = (!shift) ? 'a' : 'A';
	case VK_S:             c = (!shift) ? 's' : 'S';
	case VK_D:             c = (!shift) ? 'd' : 'D';
	case VK_F:             c = (!shift) ? 'f' : 'F';
	case VK_G:             c = (!shift) ? 'g' : 'G';
	case VK_H:             c = (!shift) ? 'h' : 'H';
	case VK_J:             c = (!shift) ? 'j' : 'J';
	case VK_K:             c = (!shift) ? 'k' : 'K';
	case VK_L:             c = (!shift) ? 'l' : 'L';
	case VK_SEMICOLON:     c = (!shift) ? ';' : ':';
	case VK_QUOTE:         c = (!shift) ? '\'' : '"';

	case VK_Z:             c = (!shift) ? 'z' : 'Z';
	case VK_X:             c = (!shift) ? 'x' : 'X';
	case VK_C:             c = (!shift) ? 'c' : 'C';
	case VK_V:             c = (!shift) ? 'v' : 'V';
	case VK_B:             c = (!shift) ? 'b' : 'B';
	case VK_N:             c = (!shift) ? 'n' : 'N';
	case VK_M:             c = (!shift) ? 'm' : 'M';
	case VK_COMMA:         c = (!shift) ? ',' : '<';
	case VK_PERIOD:        c = (!shift) ? '.' : '>';
	case VK_SLASH:         c = (!shift) ? '/' : '?';

	case VK_SPACE:         c = ' ';
	case VK_ENTER:         c = '\n';

	default:
		c = '\0';
	}

	if (c == '\0') {
		return SCRIPTEL_RETR_ERROR;
	}

	return scriptel_keyboard_parse_char(sig_protocol, c);
}
