/*
 *  Lib(X)SVF  -  A library for implementing SVF and XSVF JTAG players
 *
 *  Copyright (C) 2009  RIEGL Research ForschungsGmbH
 *  Copyright (C) 2009  Clifford Wolf <clifford@clifford.at>
 *  
 *  Permission to use, copy, modify, and/or distribute this software for any
 *  purpose with or without fee is hereby granted, provided that the above
 *  copyright notice and this permission notice appear in all copies.
 *  
 *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */

#include "libxsvf.h"

static int read_command(struct libxsvf_host *h, char **buffer_p, int *len_p)
{
	char *buffer = *buffer_p;
	int braket_mode = 0;
	int len = *len_p;
	int p = 0;

	while (1)
	{
		if (len < p+10) {
			len = len < 64 ? 96 : len*2;
			buffer = LIBXSVF_HOST_REALLOC(buffer, len, LIBXSVF_MEM_SVF_COMMANDBUF);
			*buffer_p = buffer;
			*len_p = len;
			if (!buffer) {
				LIBXSVF_HOST_REPORT_ERROR("Allocating memory failed.");
				return -1;
			}
		}
		buffer[p] = 0;

		int ch = LIBXSVF_HOST_GETBYTE();
		if (ch < 0) {
handle_eof:
			if (p == 0)
				return 0;
			LIBXSVF_HOST_REPORT_ERROR("Unexpected EOF.");
			return -1;
		}
		if (ch <= ' ') {
insert_eol:
			if (!braket_mode && p > 0 && buffer[p-1] != ' ')
				buffer[p++] = ' ';
			continue;
		}
		if (ch == '!') {
skip_to_eol:
			while (1) {
				ch = LIBXSVF_HOST_GETBYTE();
				if (ch < 0)
					goto handle_eof;
				if (ch < ' ' && ch != '\t')
					goto insert_eol;
			}
		}
		if (ch == '/' && p > 0 && buffer[p-1] == '/') {
			p--;
			goto skip_to_eol;
		}
		if (ch == ';')
			break;
		if (ch == '(') {
			if (!braket_mode && p > 0 && buffer[p-1] != ' ')
				buffer[p++] = ' ';
			braket_mode++;
		}
		if (ch >= 'a' && ch <= 'z')
			buffer[p++] = ch - ('a' - 'A');
		else
			buffer[p++] = ch;
		if (ch == ')') {
			braket_mode--;
			if (!braket_mode)
				buffer[p++] = ' ';
		}
	}
	return 1;
}

static int strtokencmp(const char *str1, const char *str2)
{
	int i = 0;
	while (1) {
		if ((str1[i] == ' ' || str1[i] == 0) && (str2[i] == ' ' || str2[i] == 0))
			return 0;
		if (str1[i] < str2[i])
			return -1;
		if (str1[i] > str2[i])
			return +1;
		i++;
	}
}

static int strtokenskip(const char *str1)
{
	int i = 0;
	while (str1[i] != 0 && str1[i] != ' ') i++;
	while (str1[i] == ' ') i++;
	return i;
}

static int token2tapstate(const char *str1)
{
#define X(_t) if (!strtokencmp(str1, #_t)) return LIBXSVF_TAP_ ## _t;
	X(RESET)
	X(IDLE)
	X(DRSELECT)
	X(DRCAPTURE)
	X(DRSHIFT)
	X(DREXIT1)
	X(DRPAUSE)
	X(DREXIT2)
	X(DRUPDATE)
	X(IRSELECT)
	X(IRCAPTURE)
	X(IRSHIFT)
	X(IREXIT1)
	X(IRPAUSE)
	X(IREXIT2)
	X(IRUPDATE)
#undef X
	return -1;
}

struct bitdata_s {
	int len, alloced_len;
	int alloced_bytes;
	unsigned char *tdi_data;
	unsigned char *tdi_mask;
	unsigned char *tdo_data;
	unsigned char *tdo_mask;
	unsigned char *ret_mask;
	int has_tdo_data;
};

static void bitdata_free(struct libxsvf_host *h, struct bitdata_s *bd, int offset)
{
	LIBXSVF_HOST_REALLOC(bd->tdi_data, 0, offset+0);
	LIBXSVF_HOST_REALLOC(bd->tdi_mask, 0, offset+1);
	LIBXSVF_HOST_REALLOC(bd->tdo_data, 0, offset+2);
	LIBXSVF_HOST_REALLOC(bd->tdo_mask, 0, offset+3);
	LIBXSVF_HOST_REALLOC(bd->ret_mask, 0, offset+4);

	bd->tdi_data = (void*)0;
	bd->tdi_mask = (void*)0;
	bd->tdo_data = (void*)0;
	bd->tdo_mask = (void*)0;
	bd->ret_mask = (void*)0;
}

static int hex(char ch)
{
	if (ch >= 'A' && ch <= 'Z')
		return (ch - 'A') + 10;
	if (ch >= '0' && ch <= '9')
		return ch - '0';
	return 0;
}

static const char *bitdata_parse(struct libxsvf_host *h, const char *p, struct bitdata_s *bd, int offset)
{
	int i, j;
	bd->len = 0;
	bd->has_tdo_data = 0;
	while (*p >= '0' && *p <= '9') {
		bd->len = bd->len * 10 + (*p - '0');
		p++;
	}
	while (*p == ' ') {
		p++;
	}
	if (bd->len != bd->alloced_len) {
		bitdata_free(h, bd, offset);
		bd->alloced_len = bd->len;
		bd->alloced_bytes = (bd->len+7) / 8;
	}
	while (*p)
	{
		int memnum = 0;
		unsigned char **dp = (void*)0;
		if (!strtokencmp(p, "TDI")) {
			p += strtokenskip(p);
			dp = &bd->tdi_data;
			memnum = 0;
		}
		if (!strtokencmp(p, "TDO")) {
			p += strtokenskip(p);
			dp = &bd->tdo_data;
			bd->has_tdo_data = 1;
			memnum = 1;
		}
		if (!strtokencmp(p, "SMASK")) {
			p += strtokenskip(p);
			dp = &bd->tdi_mask;
			memnum = 2;
		}
		if (!strtokencmp(p, "MASK")) {
			p += strtokenskip(p);
			dp = &bd->tdo_mask;
			memnum = 3;
		}
		if (!strtokencmp(p, "RMASK")) {
			p += strtokenskip(p);
			dp = &bd->ret_mask;
			memnum = 4;
		}
		if (!dp)
			return (void*)0;
		if (*dp == (void*)0) {
			*dp = LIBXSVF_HOST_REALLOC(*dp, bd->alloced_bytes, offset+memnum);
		}
		if (*dp == (void*)0) {
			LIBXSVF_HOST_REPORT_ERROR("Allocating memory failed.");
			return (void*)0;
		}

		unsigned char *d = *dp;
		for (i=0; i<bd->alloced_bytes; i++)
			d[i] = 0;

		if (*p != '(')
			return (void*)0;
		p++;

		int hexdigits = 0;
		for (i=0; (p[i] >= 'A' && p[i] <= 'F') || (p[i] >= '0' && p[i] <= '9'); i++)
			hexdigits++;

		i = bd->alloced_bytes*2 - hexdigits;
		for (j=0; j<hexdigits; j++, i++, p++) {
			if (i%2 == 0) {
				d[i/2] |= hex(*p) << 4;
			} else {
				d[i/2] |= hex(*p);
			}
		}

		if (*p != ')')
			return (void*)0;
		p++;
		while (*p == ' ') {
			p++;
		}
	}
#if 0
	/* Debugging Output, needs <stdio.h> */
	printf("--- Parsed bitdata [%d] ---\n", bd->len);
	if (bd->tdi_data) {
		printf("TDI DATA:");
		for (i=0; i<bd->alloced_bytes; i++)
			printf(" %02x", bd->tdi_data[i]);
		printf("\n");
	}
	if (bd->tdo_data && has_tdo_data) {
		printf("TDO DATA:");
		for (i=0; i<bd->alloced_bytes; i++)
			printf(" %02x", bd->tdo_data[i]);
		printf("\n");
	}
	if (bd->tdi_mask) {
		printf("TDI MASK:");
		for (i=0; i<bd->alloced_bytes; i++)
			printf(" %02x", bd->tdi_mask[i]);
		printf("\n");
	}
	if (bd->tdo_mask) {
		printf("TDO MASK:");
		for (i=0; i<bd->alloced_bytes; i++)
			printf(" %02x", bd->tdo_mask[i]);
		printf("\n");
	}
#endif
	return p;
}

static int getbit(unsigned char *data, int n)
{
	return (data[n/8] & (1 << (7 - n%8))) ? 1 : 0;
}

static int bitdata_play(struct libxsvf_host *h, struct bitdata_s *bd, enum libxsvf_tap_state estate)
{
	int left_padding = (8 - bd->len % 8) % 8;
	int tdo_error = 0;
	int tms = 0;
	int i;

	for (i=bd->len+left_padding-1; i >= left_padding; i--) {
		if (i == left_padding && h->tap_state != estate) {
			h->tap_state++;
			tms = 1;
		}
		int tdi = -1;
		if (bd->tdi_data) {
			if (!bd->tdi_mask || getbit(bd->tdi_mask, i))
				tdi = getbit(bd->tdi_data, i);
		}
		int tdo = -1;
		if (bd->tdo_data && bd->has_tdo_data && (!bd->tdo_mask || getbit(bd->tdo_mask, i)))
			tdo = getbit(bd->tdo_data, i);
		int rmask = bd->ret_mask && getbit(bd->ret_mask, i);
		if (LIBXSVF_HOST_PULSE_TCK(tms, tdi, tdo, rmask, 0) < 0)
			tdo_error = 1;
	}

	if (tms)
		LIBXSVF_HOST_REPORT_TAPSTATE();

	if (!tdo_error)
		return 0;

	LIBXSVF_HOST_REPORT_ERROR("TDO mismatch.");
	return -1;
}

int libxsvf_svf(struct libxsvf_host *h)
{
	char *command_buffer = (void*)0;
	int command_buffer_len = 0;
	int rc, i;

	struct bitdata_s bd_hdr = { 0, 0, 0, (void*)0, (void*)0, (void*)0, (void*)0, (void*)0 };
	struct bitdata_s bd_hir = { 0, 0, 0, (void*)0, (void*)0, (void*)0, (void*)0, (void*)0 };
	struct bitdata_s bd_tdr = { 0, 0, 0, (void*)0, (void*)0, (void*)0, (void*)0, (void*)0 };
	struct bitdata_s bd_tir = { 0, 0, 0, (void*)0, (void*)0, (void*)0, (void*)0, (void*)0 };
	struct bitdata_s bd_sdr = { 0, 0, 0, (void*)0, (void*)0, (void*)0, (void*)0, (void*)0 };
	struct bitdata_s bd_sir = { 0, 0, 0, (void*)0, (void*)0, (void*)0, (void*)0, (void*)0 };

	int state_endir = LIBXSVF_TAP_IDLE;
	int state_enddr = LIBXSVF_TAP_IDLE;
	int state_run = LIBXSVF_TAP_IDLE;
	int state_endrun = LIBXSVF_TAP_IDLE;

	while (1)
	{
		rc = read_command(h, &command_buffer, &command_buffer_len);

		if (rc <= 0)
			break;

		const char *p = command_buffer;

		LIBXSVF_HOST_REPORT_STATUS(command_buffer);

		if (!strtokencmp(p, "ENDIR")) {
			p += strtokenskip(p);
			state_endir = token2tapstate(p);
			if (state_endir < 0)
				goto syntax_error;
			p += strtokenskip(p);
			goto eol_check;
		}

		if (!strtokencmp(p, "ENDDR")) {
			p += strtokenskip(p);
			state_enddr = token2tapstate(p);
			if (state_endir < 0)
				goto syntax_error;
			p += strtokenskip(p);
			goto eol_check;
		}

		if (!strtokencmp(p, "FREQUENCY")) {
			unsigned long number = 0;
			int exp = 0;
			p += strtokenskip(p);
			if (*p < '0' || *p > '9')
				goto syntax_error;
			while (*p >= '0' && *p <= '9') {
				number = number*10 + (*p - '0');
				p++;
			}
			if(*p == 'E' || *p == 'e') {
				p++;
				while (*p >= '0' && *p <= '9') {
					exp = exp*10 + (*p - '0');
					p++;
				}
				for(i=0; i<exp; i++)
					number *= 10;
			}
			while (*p == ' ') {
				p++;
			}
			p += strtokenskip(p);
			if (LIBXSVF_HOST_SET_FREQUENCY(number) < 0) {
				LIBXSVF_HOST_REPORT_ERROR("FREQUENCY command failed!");
				goto error;
			}
			goto eol_check;
		}

		if (!strtokencmp(p, "HDR")) {
			p += strtokenskip(p);
			p = bitdata_parse(h, p, &bd_hdr, LIBXSVF_MEM_SVF_HDR_TDI_DATA);
			if (!p)
				goto syntax_error;
			goto eol_check;
		}

		if (!strtokencmp(p, "HIR")) {
			p += strtokenskip(p);
			p = bitdata_parse(h, p, &bd_hir, LIBXSVF_MEM_SVF_HIR_TDI_DATA);
			if (!p)
				goto syntax_error;
			goto eol_check;
		}

		if (!strtokencmp(p, "PIO") || !strtokencmp(p, "PIOMAP")) {
			goto unsupported_error;
		}

		if (!strtokencmp(p, "RUNTEST")) {
			p += strtokenskip(p);
			int tck_count = -1;
			int sck_count = -1;
			int min_time = -1;
			int max_time = -1;
			while (*p) {
				int got_maximum = 0;
				if (!strtokencmp(p, "MAXIMUM")) {
					p += strtokenskip(p);
					got_maximum = 1;
				}
				int got_endstate = 0;
				if (!strtokencmp(p, "ENDSTATE")) {
					p += strtokenskip(p);
					got_endstate = 1;
				}
				int st = token2tapstate(p);
				if (st >= 0) {
					p += strtokenskip(p);
					if (got_endstate)
						state_endrun = st;
					else
						state_run = st;
					continue;
				}
				if (*p < '0' || *p > '9')
					goto syntax_error;
				int number = 0;
				int exp = 0, expsign = 1;
				int number_e6, exp_e6;
				while (*p >= '0' && *p <= '9') {
					number = number*10 + (*p - '0');
					p++;
				}
				if(*p == 'E' || *p == 'e') {
					p++;
					if(*p == '-') {
						expsign = -1;
						p++;
					}
					while (*p >= '0' && *p <= '9') {
						exp = exp*10 + (*p - '0');
						p++;
					}
					exp = exp * expsign;
					number_e6 = number;
					exp_e6 = exp + 6;
					while (exp < 0) {
						number /= 10;
						exp++;
					}
					while (exp > 0) {
						number *= 10;
						exp--;
					}
					while (exp_e6 < 0) {
						number_e6 /= 10;
						exp_e6++;
					}
					while (exp_e6 > 0) {
						number_e6 *= 10;
						exp_e6--;
					}
				} else {
					number_e6 = number * 1000000;
				}
				while (*p == ' ') {
					p++;
				}
				if (!strtokencmp(p, "SEC")) {
					p += strtokenskip(p);
					if (got_maximum)
						max_time = number_e6;
					else
						min_time = number_e6;
					continue;
				}
				if (!strtokencmp(p, "TCK")) {
					p += strtokenskip(p);
					tck_count = number;
					continue;
				}
				if (!strtokencmp(p, "SCK")) {
					p += strtokenskip(p);
					sck_count = number;
					continue;
				}
				goto syntax_error;
			}
			if (libxsvf_tap_walk(h, state_run) < 0)
				goto error;
			if (max_time >= 0) {
				LIBXSVF_HOST_REPORT_ERROR("WARNING: Maximum time in SVF RUNTEST command is ignored.");
			}
			if (sck_count >= 0) {
				for (i=0; i < sck_count; i++) {
					LIBXSVF_HOST_PULSE_SCK();
				}
			}
			if (min_time >= 0 || tck_count >= 0) {
				LIBXSVF_HOST_UDELAY(min_time >= 0 ? min_time : 0, 0, tck_count >= 0 ? tck_count : 0);
			}
			if (libxsvf_tap_walk(h, state_endrun) < 0)
				goto error;
			goto eol_check;
		}

		if (!strtokencmp(p, "SDR")) {
			p += strtokenskip(p);
			p = bitdata_parse(h, p, &bd_sdr, LIBXSVF_MEM_SVF_SDR_TDI_DATA);
			if (!p)
				goto syntax_error;
			if (libxsvf_tap_walk(h, LIBXSVF_TAP_DRSHIFT) < 0)
				goto error;
			if (bitdata_play(h, &bd_hdr, bd_sdr.len+bd_tdr.len > 0 ? LIBXSVF_TAP_DRSHIFT : state_enddr) < 0)
				goto error;
			if (bitdata_play(h, &bd_sdr, bd_tdr.len > 0 ? LIBXSVF_TAP_DRSHIFT : state_enddr) < 0)
				goto error;
			if (bitdata_play(h, &bd_tdr, state_enddr) < 0)
				goto error;
			if (libxsvf_tap_walk(h, state_enddr) < 0)
				goto error;
			goto eol_check;
		}

		if (!strtokencmp(p, "SIR")) {
			p += strtokenskip(p);
			p = bitdata_parse(h, p, &bd_sir, LIBXSVF_MEM_SVF_SIR_TDI_DATA);
			if (!p)
				goto syntax_error;
			if (libxsvf_tap_walk(h, LIBXSVF_TAP_IRSHIFT) < 0)
				goto error;
			if (bitdata_play(h, &bd_hir, bd_sir.len+bd_tir.len > 0 ? LIBXSVF_TAP_IRSHIFT : state_endir) < 0)
				goto error;
			if (bitdata_play(h, &bd_sir, bd_tir.len > 0 ? LIBXSVF_TAP_IRSHIFT : state_endir) < 0)
				goto error;
			if (bitdata_play(h, &bd_tir, state_endir) < 0)
				goto error;
			if (libxsvf_tap_walk(h, state_endir) < 0)
				goto error;
			goto eol_check;
		}

		if (!strtokencmp(p, "STATE")) {
			p += strtokenskip(p);
			while (*p) {
				int st = token2tapstate(p);
				if (st < 0)
					goto syntax_error;
				if (libxsvf_tap_walk(h, st) < 0)
					goto error;
				p += strtokenskip(p);
			}
			goto eol_check;
		}

		if (!strtokencmp(p, "TDR")) {
			p += strtokenskip(p);
			p = bitdata_parse(h, p, &bd_tdr, LIBXSVF_MEM_SVF_TDR_TDI_DATA);
			if (!p)
				goto syntax_error;
			goto eol_check;
		}

		if (!strtokencmp(p, "TIR")) {
			p += strtokenskip(p);
			p = bitdata_parse(h, p, &bd_tir, LIBXSVF_MEM_SVF_TIR_TDI_DATA);
			if (!p)
				goto syntax_error;
			goto eol_check;
		}

		if (!strtokencmp(p, "TRST")) {
			p += strtokenskip(p);
			if (!strtokencmp(p, "ON")) {
				p += strtokenskip(p);
				LIBXSVF_HOST_SET_TRST(1);
				goto eol_check;
			}
			if (!strtokencmp(p, "OFF")) {
				p += strtokenskip(p);
				LIBXSVF_HOST_SET_TRST(0);
				goto eol_check;
			}
			if (!strtokencmp(p, "Z")) {
				p += strtokenskip(p);
				LIBXSVF_HOST_SET_TRST(-1);
				goto eol_check;
			}
			if (!strtokencmp(p, "ABSENT")) {
				p += strtokenskip(p);
				LIBXSVF_HOST_SET_TRST(-2);
				goto eol_check;
			}
			goto syntax_error;
		}

eol_check:
		while (*p == ' ')
			p++;
		if (*p == 0)
			continue;

syntax_error:
		LIBXSVF_HOST_REPORT_ERROR("SVF Syntax Error:");
		if (0) {
unsupported_error:
			LIBXSVF_HOST_REPORT_ERROR("Error in SVF input: unsupported command:");
		}
		LIBXSVF_HOST_REPORT_ERROR(command_buffer);
error:
		rc = -1;
		break;
	}

	if (LIBXSVF_HOST_SYNC() != 0 && rc >= 0 ) {
		LIBXSVF_HOST_REPORT_ERROR("TDO mismatch.");
		rc = -1;
	}

	bitdata_free(h, &bd_hdr, LIBXSVF_MEM_SVF_HDR_TDI_DATA);
	bitdata_free(h, &bd_hir, LIBXSVF_MEM_SVF_HIR_TDI_DATA);
	bitdata_free(h, &bd_tdr, LIBXSVF_MEM_SVF_TDR_TDI_DATA);
	bitdata_free(h, &bd_tir, LIBXSVF_MEM_SVF_TIR_TDI_DATA);
	bitdata_free(h, &bd_sdr, LIBXSVF_MEM_SVF_SDR_TDI_DATA);
	bitdata_free(h, &bd_sir, LIBXSVF_MEM_SVF_SIR_TDI_DATA);

	LIBXSVF_HOST_REALLOC(command_buffer, 0, LIBXSVF_MEM_SVF_COMMANDBUF);

	return rc;
}

