/*
 * Copyright (c) 2010 Tony Bybell.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/*
 * to compile: gcc -o transaction transaction.c -DHAVE_INTTYPES_H
 * then in this directory run: gtkwave transaction.fst transaction.sav
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#endif

#define OPTIONAL_DEBUG if(1)

/*
 * some structs we'll be using for event harvesting from the VCD that
 * gtkwave sends via stdin into this executable
 */
struct tim_t
{
struct tim_t *next;

uint64_t tim;
int val;
int delta;
};

struct event_t
{
struct event_t *next;

uint64_t tim;
char *name;
};


int main(void)
{
uint64_t min_time = 0, max_time = 0;
uint64_t tim = 0;
int prevtim = 0;
int prevval = 255;
struct tim_t *t_head = NULL, *t_curr = NULL;
struct tim_t *t_tmp;
int hcnt;
uint64_t control_start = 0, control_end = 0;
int blks;

struct event_t *hdr_head = NULL, *hdr_curr = NULL;
struct event_t *data_head = NULL, *data_curr = NULL;

OPTIONAL_DEBUG { fprintf(stderr, "*** t_filter executable init ***\n"); }

top:	/* main control loop */

hcnt = 0;
control_start = 0;
control_end = 0;
blks = 0;

while(1) /* reading input from stdin until data_end comment received */
	{
	char buf[1025];
	char *pnt = NULL;

	buf[0] = 0;
	pnt = fgets(buf, 1024, stdin); /* CACHE (VIA puts()) THIS INPUT TO A FILE TO DEVELOP YOUR CLIENT OFFLINE FROM GTKWAVE! */

	if(buf[0])
		{
		pnt = buf;
		while(*pnt) /* strip off end of line character */
			{
			if(*pnt != '\n')
				{
				pnt++;
				}
				else
				{
				*pnt = 0;
				break;
				}
			}

		if(buf[0] == '#') /* look for and extract time value */
			{
			char *str = buf+1;
			unsigned char ch;

			tim=0;
			while((ch=*(str++)))
			        {
			        if((ch>='0')&&(ch<='9'))
			                {
			                tim=(tim*10+(ch&15));
			                }
				}
			}
		else
		if(buf[0] == 'b') /* extract the binary value of the "val" symbol */
			{
			int collect = 0;
			pnt = buf+1;

			while(*pnt && (*pnt != ' '))
				{
				collect <<= 1;
				collect |= ((*pnt) & 1);
				pnt++;
				}
			
			if((prevval ^ collect) & 128)
				{
				t_tmp = calloc(1, sizeof(struct tim_t));
				t_tmp->tim = tim;
				t_tmp->val = collect;
				t_tmp->delta = tim - prevtim;

				prevtim = tim;

				if(!t_curr)
					{
					t_head = t_curr = t_tmp;
					}
					else
					{
					t_curr = t_curr->next = t_tmp;
					}
				}
			prevval = collect;
			}
		else
		if(buf[0] == '$') /* directive processing */
			{
			if(strstr(buf, "$comment"))
				{
				if((pnt = strstr(buf, "min_time")))
					{
					sscanf(pnt + 9, "%"SCNu64, &min_time);
					OPTIONAL_DEBUG { fprintf(stderr, "min: %d\n", (int)min_time); }
					}
				else
				if((pnt = strstr(buf, "max_time")))
					{
					sscanf(pnt + 9, "%"SCNu64, &max_time);
					OPTIONAL_DEBUG { fprintf(stderr, "max: %d\n", (int)max_time); }
					}
				else
				if(strstr(buf, "data_end"))
					{
					break;
					}
				}
			}
		}

	if(feof(stdin)) /* broken pipe coming up */
		{
		OPTIONAL_DEBUG { fprintf(stderr, "*** Terminated t_filter executable\n"); }
		exit(0);
		}
	}

printf("$name Decoded FSK\n"); /* 1st trace name */
printf("#0\n");

{
uint64_t p_tim = 0;
int state = 0;
int byte_remain = 0;
int bcnt = 0;

t_tmp = t_head;
while(t_tmp)
	{
	if(t_tmp->delta == 16)
		{
		int sync_cnt = 0;
		uint64_t t_start = p_tim;
		int i;
		for(i=0;i<16;i++)
			{
			if(t_tmp->delta == 16) { sync_cnt++; p_tim = t_tmp->tim; t_tmp = t_tmp->next; } else { break; }
			}
		if(sync_cnt==16)
			{
			printf("#%"PRIu64" ?darkblue?Sync\n", t_start); /* write out sync xact */
			printf("#%"PRIu64"\n", p_tim - 4); 		/* 'z' midline is no value after time */
			printf("MA%"PRIu64" Start\n", t_start);		/* set position/name for marker A */
			control_start = t_start;
			goto found_sync;
			}
		continue;
		}
	p_tim = t_tmp->tim;
	t_tmp = t_tmp->next;
	}

found_sync:

state = 0; byte_remain = 11;
/* printf("MB%"PRIu64" Num Blocks\n", p_tim); */

while(t_tmp)
	{
	int i;
	int collect = 0;
	uint64_t t_start = p_tim;

	if((state == 0) && (byte_remain == 10))
		{
		struct event_t *evt = calloc(1, sizeof(struct event_t));
		char buf[32];

		evt->tim = p_tim;
		sprintf(buf, "H%d", hcnt/2);
		evt->name = strdup(buf);

		if(!control_end) { control_end = p_tim; }

		if(!hdr_head) { hdr_head = hdr_curr = evt; }
		else { hdr_curr = hdr_curr->next = evt; }

		/**/
		if(data_curr)
			{
			evt = calloc(1, sizeof(struct event_t));
			evt->tim = p_tim;
	                evt->name = NULL;

			data_curr = data_curr->next = evt;
			}
		}

	if((state == 1) && (byte_remain == 64))
		{
		struct event_t *evt = calloc(1, sizeof(struct event_t));
		char buf[32];

		evt->tim = p_tim;
		sprintf(buf, "D%d:", hcnt/2);
		evt->name = calloc(1, 74);
		strcpy(evt->name, buf);

		if(!data_head) { data_head = data_curr = evt; }
		else { data_curr = data_curr->next = evt; }

		/**/
		if(hdr_curr)
			{
			evt = calloc(1, sizeof(struct event_t));
			evt->tim = p_tim;
	                evt->name = NULL;

			hdr_curr = hdr_curr->next = evt;
			}

		hcnt++;
		}


	for(i=0;(t_tmp) && (i<8);i++)
		{
		collect <<= 1;
		if(t_tmp->delta == 16) 
			{ 
			collect |= 1; 
			t_tmp = t_tmp->next; 
			}
			else
			{
			}

		p_tim = t_tmp->tim; t_tmp = t_tmp->next;
		}

	if(!bcnt)
		{
		printf("#%"PRIu64" ?gray24?%02X\n", t_start, collect);
		blks = collect;
		bcnt++;
		}
		else
		{
		if(state == 1)
			{
			char conv = ((collect < ' ') || (collect >= 127)) ? '.' : collect;
			int slen = strlen(data_curr->name);
			
			data_curr->name[slen] = conv;

			if((collect >= ' ') && (collect < 127))
				{
				printf("#%"PRIu64" ?darkgreen?%c\n", t_start, (unsigned char)collect);
				}
				else
				{
				printf("#%"PRIu64" ?darkred?%02X\n", t_start, collect);
				}

			}
			else
			{
			printf("#%"PRIu64" ?purple3?%02X\n", t_start, collect);
			}
		}	

	printf("#%"PRIu64"\n", p_tim - 4);

	byte_remain--;
	if(!byte_remain)
		{
		if(state == 0)
			{
			state = 1; byte_remain = 64;
			}
			else
			{
			state = 0; byte_remain = 10;
			}
		}

	}

}

t_tmp = t_head;			/* free up memory allocated */
while(t_tmp)
        {
	t_curr = t_tmp->next;
	free(t_tmp);
	t_tmp = t_curr;
	}
t_head = t_curr = NULL;

printf("$next\n");		/* indicate there is a next trace */
printf("$name Control\n");	/* next trace name */
printf("#0\n");       
printf("#%"PRIu64" ?darkblue?%02X blks\n", control_start, blks);
printf("#%"PRIu64"\n", control_end);


printf("$next\n");		/* indicate there is a next trace */
printf("$name Headers\n");	/* next trace name */
printf("#0\n");
while(hdr_head)
	{
	if(hdr_head->name)
		{
		printf("#%"PRIu64" ?purple3?%s\n", hdr_head->tim, hdr_head->name);
		free(hdr_head->name);
		}
		else
		{
		printf("#%"PRIu64"\n", hdr_head->tim);
		}

	hdr_curr = hdr_head->next;
	free(hdr_head);
	hdr_head = hdr_curr;
	}


printf("$next\n");		/* indicate there is a next trace */
printf("$name Data Payload\n");	/* next trace name */
printf("#0\n");
while(data_head)
	{
	if(data_head->name)
		{
		printf("#%"PRIu64" ?darkgreen?%s\n", data_head->tim, data_head->name);
		free(data_head->name);
		}
		else
		{
		printf("#%"PRIu64"\n", data_head->tim);
		}

	data_curr = data_head->next;
	free(data_head);
	data_head = data_curr;
	}



printf("$finish\n");		/* directive to return control to gtkwave */
fflush(stdout);			/* ensure nothing is stuck in output buffers which could hang gtkwave */

OPTIONAL_DEBUG { fprintf(stderr, "back to gtkwave...\n"); }

goto top;			/* loop forever in order to process next xact query */

return(0);
}
