/*

	MakeUEF by Thomas Harte 2000

	Distributed under the GPL - see 'Copying' for details

*/

#include <stdlib.h>
#include <ctype.h>
#include <sys/stat.h>
#include <string.h>
#include "defines.h"
#include "uef.h"

#define PROGRAM_NAME	"MakeUEF 0.3b"

#define UEF_MINOR	5
#define UEF_MAJOR	0

#define ORIGIN_BLOCK	0x0000
#define LEADIN_BLOCK	0x0110
#define DATA_BLOCK	0x0100
#define GAP_BLOCK	0x0112

char *inname, *outname, *error, *bstring;

unsigned char buffer[131072], *cpos;

int delays[20];
unsigned char cbyte, sparebits;
bool h2400, bit, bit_error, byte_error;

int understood_one, understood_ten;

bool sign;
int span;

enum span_calibration_types
{
	FREQUENCY_CALIBRATE, WAVE_CALIBRATE, VALUE_CALIBRATE
} span_type;

enum leadin_tone_types
{
	DUMMYBIT_TONE, LONG_TONE
} leadin_type;

C_snd_input *i_drv;
C_raw raw_drv;
C_wav wav_drv;
C_voc voc_drv;
C_sndcard card_drv;

C_UEF outfile;

void (* get_next_length)(void);
void get_next_length_sign();
void get_next_length_peak();

char parse_line(char **argv, int argc)
{
	unsigned char help = 0;
	int c, freq;
	char buffer[3];
	char *argument, *end;

	inname = outname = NULL;
	buffer[2] = '\0';
	error = NULL;

	i_drv = &card_drv;
	span_type = FREQUENCY_CALIBRATE;
	raw_drv.SetSigned(false);
	raw_drv.SetFrequency(22050);
	get_next_length = get_next_length_peak;
	bstring = "";
	leadin_type = DUMMYBIT_TONE;

	c = 1;
	while(c < argc)
	{
		memcpy(buffer, argv[c], 2);

		if(buffer[0] == '/' || buffer[0] == '-')
		{
			switch(tolower(buffer[1]))
			{
				default : help = 2; break;
				case 'h' : help = 1; break;
				case 'b' : bstring = "\a"; break;

				case 'f' :
				case 'o' :
				case 'i' :
				case 't' :
				case 'e' :
				case 's' :
				case 'l' :

					if(strlen(argv[c]) == 2)
					{
						c++;

						if(c < argc)
							argument = argv[c];
						else
						{
							help = 2;
							break;
						}
					}
					else
						argument = argv[c]+2;

					switch(tolower(buffer[1]))
					{
						case 'i' :
							i_drv = &raw_drv;
							inname = argument;
						break;

						case 'o' :
							outname = argument;
						break;

						case 'f' :
							freq = strtol(argument, &end, 0);
							if(*end != '\0')
								help = 2;
							raw_drv.SetFrequency(freq);
						break;

						case 't' :
							switch(tolower(argument[0]))
							{
								case 's' : raw_drv.SetSigned(true); break;
								case 'u' : raw_drv.SetSigned(false); break;
							}
						break;

						case 'e' :
							switch(tolower(argument[0]))
							{
								case 'l' : leadin_type = LONG_TONE; break;
								case 'b' : leadin_type = DUMMYBIT_TONE; break;
							}
						break;

						case 'l' :
							switch(tolower(argument[0]))
							{
								case 'p' : get_next_length = get_next_length_peak; break;
								case 's' : get_next_length = get_next_length_sign; break;
							}
						break;

						case 's' :
							switch(tolower(argument[0]))
							{
								default :
									span_type = WAVE_CALIBRATE;
									span = atol(argument);
								break;

								case 't' :
									span_type = WAVE_CALIBRATE;
								break;

								case 'f' :
									span_type = FREQUENCY_CALIBRATE;
								break;
							}
						break;
					}
				break;
			}
		}
		else
			help = 2;

		c++;
	}

	if(!outname)
	{
		help = 1;
	}
	else
	{
		if(inname)
		{
			argument = inname + strlen(inname);
			while(*argument-- != '.');
			argument+=2;

			if(!strcmp(argument, "wav"))
				i_drv = &wav_drv;

			if(!strcmp(argument, "voc"))
				i_drv = &voc_drv;
		}
	}

	switch(help)
	{
		case 2 :
			printf("\nINVALID OPTION\n");
		case 1 :
			printf("\n%s [-i <input filename>] [-o <output filename>] [-f <input frequency>] [-h] [-t <u or s>] [-s <t, f or |nn|>] [-l <p or s>] [-b] [-e <b or l>]\n\n", PROGRAM_NAME);
			printf("Converts sound files or soundcard input to UEF tape images.\n");

			#ifdef TARGET_ALLEGRO
			printf("SoundBlaster, AudioDrive, Windows Sound System & Ensoniq Soundscape supported\n");
			#endif
			#ifdef TARGET_OSS
			printf("Open Sound System will be used\n");
			#endif
			#ifdef TARGET_WIN
			printf("Windows sound hardware will be used\n");
			#endif

			printf("\n");
			printf("\t-h brings this message\n");
			printf("\t-f is for raw sound files - takes decimal or hexadecimal (0x) argument\n");
			printf("\t-t is for raw sound files - 'u' for unsigned, 's' for signed.\n");
			printf("\t-s is for time calibration - 't' for by sample, 'f' for by frequency, or supply value\n");
			printf("\t-l sets frequency measure method - s for sign, p for peak\n");
			printf("\t-b if this switch is set, a beep occurs on errors\n");
			printf("\t-e determines leadin tone type - l for long, b for with dummy bit\n");
			printf("\n");
			printf("\taccepts '/' in place of '-'\n");
			printf("\tworks with or without spaces between options and parameters\n");
			printf("\twav and raw sound files are supported - voc to be soon\n");
			printf("\tif no input name is supplied, the soundcard is used\n");
		break;
	}

	return help;
}

void push_along_delays(void)
{
	int c;
	c = 19;
	while(c--)
	{
		delays[c+1] = delays[c];
	}
}

void get_next_length_sign(void)
{
	push_along_delays();
	delays[0] = 0;

	do
	{
		sign = i_drv->GetNextValue() < 0;
		delays[0]++;
	}
	while(sign && !i_drv->Finished());

	do
	{
		sign = i_drv->GetNextValue() < 0;
		delays[0]++;
	}
	while(!sign && !i_drv->Finished());
}

void get_next_length_peak(void)
{
	char value, old_value;

	push_along_delays();
	delays[0] = 1;

	value = i_drv->GetNextValue();

	do
	{
		old_value = value;
		value = i_drv->GetNextValue();
		delays[0]++;
	}
	while(value>=old_value && !i_drv->Finished());

	do
	{
		old_value = value;
		value = i_drv->GetNextValue();
		delays[0]++;
	}
	while(value<=old_value && !i_drv->Finished());
}


void advance(int len)
{
	while(len--)
		get_next_length();
}

void calibrate_span(void)
{
	switch(span_type)
	{
		case FREQUENCY_CALIBRATE :
			span = i_drv->GetFrequency() /1200;
		break;

		case WAVE_CALIBRATE :
			advance(2);
			span = delays[0] + delays[1];
		break;
	}
}

int get_bit(int spos)
{
	if(delays[spos] > (span << 1))	//a very wide margin for error
		bit_error = true;

	if(abs(delays[spos] - (span >> 1)) > 2)
	{
		//low/high
		spos--;

		bit = false;
	}
	else
	{
		//low/high/low/high
		spos-=2;

		bit = true;
	}

	return spos;
}

void re_eval_byte(void)
{
	int bufpos = 19;
	int count;
	sparebits = cbyte = 0;

	bufpos = get_bit(bufpos);
	understood_one = 19-bufpos;
	if(bit)
		sparebits |= 1;

	count = 8;
	while(count--)
	{
		cbyte >>= 1;

		bufpos = get_bit(bufpos);
		cbyte |= bit ? 0x80 : 0;
	}

	bufpos = get_bit(bufpos);
	understood_ten = 19-bufpos;

	if(bit)
		sparebits |= 2;

	byte_error = bit_error || !(sparebits&2);
	h2400 = ((cbyte == 0xff) && (sparebits == 3)) ? true : false;
}

void init_state(void)
{
	advance(20);
	re_eval_byte();
}

void get_block(void)
{
	cpos = buffer;
	memset(buffer, 0, 131072);

	while(!h2400 && !i_drv->Finished())
	{
		advance(1);
		re_eval_byte();
	}

	while(h2400 && !i_drv->Finished())
	{
		advance(1);
		re_eval_byte();
	}

	do
	{
		advance(understood_one);
		re_eval_byte();
	}
	while((cbyte != 0x2a) && !i_drv->Finished());

	byte_error = bit_error = false;
	while(!h2400 && !i_drv->Finished() && !byte_error)
	{
		*cpos++ = cbyte;
		advance(understood_ten);
		re_eval_byte();
	}
}

unsigned char h, l;

void get_crc(unsigned char *start, int len)
{
	unsigned char x;

	h = l = 0;
	while(len--)
	{
		h ^= *start;

		x = 8;
		while(x--)
		{
			if(h&128)
			{
				h ^= 8;
				l ^= 16;

				h = (h << 1) | (l >> 7);
				l = (l << 1) | 1;
			}
			else
			{
				h = (h << 1) | (l >> 7);
				l = (l << 1);
			}
		}

		start++;
	}
}

bool check_block(void)
{
	int eon;
	int length;

	if(byte_error)
		return true;

	eon = strlen((char *)buffer+1)+2;

	if(cpos-buffer < eon)
		return true;

	get_crc(buffer+1, eon+16);

	if(h != buffer[eon+17] || l != buffer[eon+18])
		return true;

	if(!(buffer[eon+12]&0x40))
	{
		length = buffer[eon+10] | (buffer[eon+11] << 8);
		get_crc(buffer+eon+19, length);

		if(h != buffer[eon+19+length] || l != buffer[eon+19+length+1])
			return true;
	}

	return false;
}

void safe_printname(char *name)
{
	char *newname, *namestart;

	namestart = newname = strdup(name);
	while(*namestart)
	{
		if(*namestart < 32 || *namestart > 126)
			*namestart = '?';
		namestart++;
	}

	printf("%10s", newname);
}

void parse(void)
{
	char *cname;
	int blocknum, newblock, eon;
	bool loading, new_file, first_block = true;
	unsigned char length[2];

	cname = strdup("(new file)");
	new_file = true;

	while(!i_drv->Finished())
	{
		get_block();

		eon = 2 + strlen((char *)buffer + 1);

		if(!check_block())
		{
			newblock = buffer[eon + 8] | (buffer[eon+9] << 8);

			printf("Found : ");
			safe_printname((char *)buffer+1);
			printf(" %02x\t", newblock);

			loading = false;

			if(new_file && !newblock)
			{
				if(!first_block)
				{
					length[0] = 208;
					length[1] = 7;
					outfile.WriteChunk(LEADIN_BLOCK, 2, length);
					outfile.WriteChunk(GAP_BLOCK, 2, length);
				}
				else
					first_block = false;

				// quick hack : long header for start
				switch(leadin_type)
				{
					case DUMMYBIT_TONE :
						length[0] = 220;
						length[1] = 5;
						outfile.WriteChunk(LEADIN_BLOCK, 2, length);
						outfile.WriteChunk(DATA_BLOCK, 1, length);
						outfile.WriteChunk(LEADIN_BLOCK, 2, length);
					break;

					case LONG_TONE :
						length[0] = 136;
						length[1] = 19;
						outfile.WriteChunk(LEADIN_BLOCK, 2, length);
					break;
				}

				new_file = false;
				loading = true;
			}
			else
				if(!strcmp((char *)buffer+1, cname) && (newblock == blocknum+1))
				{
					// quick hack : short header for component
					length[0] = 88;
					length[1] = 2;
					outfile.WriteChunk(LEADIN_BLOCK, 2, length);
					loading = true;
				}

			if(loading)
			{
				outfile.WriteChunk(DATA_BLOCK, cpos-buffer, buffer);

				free(cname);
				cname = strdup((char *)buffer+1);

				blocknum = newblock;

				if(buffer[eon+12]&0x80)
				{
					new_file = true;
					printf("Loaded\n");
				}
				else
					printf("Loading");
			}
			else
			{
				printf("Expecting ");
				if(new_file)
					printf("(new file)");
				else
					safe_printname(cname);

				printf(" %02x", blocknum+1);
			}
		}
		else
		{
			if(eon < (cpos-buffer))
				printf("%s ", buffer+1);

			if(eon+10 < (cpos-buffer))
				printf("%02x ", buffer[eon + 8] | (buffer[eon+9] << 8));

			printf("Data?");

			if(loading)
			{
				printf("%s", bstring);
				loading = false;
			}
		}

		printf("\n");
	}
	outfile.WriteChunk(LEADIN_BLOCK, 2, length);
	outfile.WriteChunk(GAP_BLOCK, 2, length);
}

int main(int argc, char **argv)
{
	char result;
	bool print_error = false;
	char origin_uef[] = PROGRAM_NAME;

	if(result = parse_line(argv, argc))
 		return result;

	if(i_drv->Open(inname))
	{
		init_state();
		calibrate_span();

		outfile.Open(outname, "w", UEF_MINOR, UEF_MAJOR);
		outfile.WriteChunk(ORIGIN_BLOCK, 1+strlen(origin_uef), (unsigned char *)origin_uef);

		parse();

		i_drv->Close();
		outfile.Close();
	}
	else
		print_error = true;

	if(print_error)
	{
		if(error)
			fprintf(stderr, "ERROR : %s\n", error);
		else
			fprintf(stderr, "Undetermined error\n", error);
		return 3;
	}

	return 0;
}
