#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdio.h>
#include <string.h>

/* DOS keyhit / getkeyhit */
#ifdef TARGET_DOS
#include <allegro.h>
#include <pc.h>
#include <conio.h>

#define keysetup() //

#define quitr() (kbhit() ? (getch() == 0x1b ? 1 : 0) : 0)
#define QKEY "ESCape"
#endif

/* Windows keyhit / getkeyhit */
#ifdef TARGET_WIN
#include <windows.h>
#include <conio.h>

#define keysetup() //

#define quitr() (_kbhit() ? (_getch() == 0x1b ? 1 : 0) : 0)
#define QKEY "ESCape"
#endif

/* POSIX keyhit / getkeyhit */
#ifdef TARGET_OSS
#include <signal.h>

volatile int quitval = 0;

void handler(int signum)
{
	quitval = 1;
}

void keysetup(void)
{
	if(signal(SIGINT, handler) == SIG_IGN) signal(SIGINT, SIG_IGN);
	if(signal(SIGHUP, handler) == SIG_IGN) signal(SIGHUP, SIG_IGN);
	if(signal(SIGTERM, handler) == SIG_IGN) signal(SIGTERM, SIG_IGN);
}

#define QKEY "CTRL+C"
#define quitr() quitval
#endif

#include "uef.h"
#include "card.h"

#define OUTPUT_HZ 44100
#define BIT_SIZE 36
#define BUF_SIZE 16384

void set_to_wave(signed char *buffer, int numwaves, unsigned char volume)
{
	int c = BIT_SIZE;
	float halfc;

	halfc = (float)c /2;

	while(c--)
	{
		buffer[c] = (signed char)( volume*-sin( ((float)c*3.141592654f*(float)numwaves)/halfc) );
	}
}

C_UEF infile;
bool (* get_bit_ptr)(void);
int internal_count, internal_count2, internal_target;
unsigned char data;
bool chunk_done, silence;

int uef_baudrate = 1200;

bool get_bit_implicit_data(void)
{
	bool ret;

	if(internal_count == 0)
		ret = false;

	if(internal_count == 9)
		ret = true;

	if(internal_count > 0 && internal_count < 9)
	{
		ret = ( (data >> (internal_count-1)) &1) ? true : false;
	}

	internal_count++;
	if(internal_count == 10)
	{
		internal_count = 0;

		if(infile.EOC())
			chunk_done = true;
		else
		{
			infile.ReadSome(&data, 1);
		}
	}

	return ret;
}

bool get_bit_data(void)
{
	bool ret;

	ret = ( (data >> (8-internal_count)) &1) ? true : false;

	internal_count2++;
	if(internal_count2 == internal_target)
	{
		chunk_done = true;
	}
	else
	{
		internal_count++;
		if(internal_count == 8)
		{
			internal_count = 0;
			infile.ReadSome(&data, 1);
		}
	}

	return ret;
}

bool get_bit_htone(void)
{
	if(!internal_count)
		chunk_done = true;
	internal_count--;

	return true;
}

bool get_bit_htonedummy(void)
{
	if(internal_count)
	{
		internal_count--;
		return internal_count ? true : false;
	}
	else
	{
		internal_count2--;
		if(!internal_count2)
			chunk_done = true;
	}

	return true;
}

void open_next_chunk(void)
{
	unsigned char low, high;

	chunk_done = true;

	while(chunk_done && !infile.OverRan())
	{
		infile.FindIdMajor(0x1);

		switch(infile.GetId()&255)
		{
			case 0x00 : //implicit data
				get_bit_ptr = get_bit_implicit_data;

				infile.ReadSome(&data, 1);
				internal_count = 0;

				chunk_done = infile.GetLength() ? false : true;
				silence = false;
			break;

			case 0x02 : //explicit data
				get_bit_ptr = get_bit_data;

				if(infile.GetVersion() >= 9)
				{
					infile.ReadSome(&data, 1);
					internal_target = ((infile.GetLength()-1) << 3) - data;
				}
				else
					internal_target = infile.GetLength() << 3;

				internal_count2 = 0;
				infile.ReadSome(&data, 1);
				internal_count = 0;

				chunk_done = (internal_target>0) ? false : true;
				silence = false;
			break;

			case 0x10 : //high tone
				get_bit_ptr = get_bit_htone;

				infile.ReadSome(&low, 1);
				infile.ReadSome(&high, 1);
				internal_count = low | (high << 8);

				if(infile.GetVersion() < 9)
					internal_count = (uef_baudrate*internal_count) / 1000;

				chunk_done = internal_count ? false : true;
				silence = false;
			break;

			case 0x11 : //high tone with dummy
				get_bit_ptr = get_bit_htonedummy;

				infile.ReadSome(&low, 1);
				infile.ReadSome(&high, 1);
				internal_count = (low | (high << 8)) + 1;
				infile.ReadSome(&low, 1);
				infile.ReadSome(&high, 1);
				internal_count2 = low | (high << 8);

				if(infile.GetVersion() < 9)
				{
					internal_count = (uef_baudrate*internal_count) / 1000;
					internal_count2 = (uef_baudrate*internal_count) / 1000;
				}

				/* time for extra byte */
				internal_count += 10;

				chunk_done = false;
				silence = true;
			break;

			case 0x12 :	//silence
				get_bit_ptr = get_bit_htone;

				infile.ReadSome(&low, 1);
				infile.ReadSome(&high, 1);
				internal_count = low | (high << 8);

				if(infile.GetVersion() < 9)
					internal_count = (uef_baudrate*internal_count) / 1000;

				chunk_done = internal_count ? false : true;
				silence = true;
			break;

			case 0x13 :	//change of baud rate
				infile.ReadSome(&low, 1);
				infile.ReadSome(&high, 1);
				uef_baudrate = low | (high << 8);
			break;
		}
	}

	if(infile.OverRan())
		silence = true;
}

bool get_bit(void)
{
	while(chunk_done && !infile.OverRan())
		open_next_chunk();

	return get_bit_ptr();
}

bool (* begin_sound)(int freq, int bufsize);
void (* end_sound)(void);
unsigned char *(* get_next_pointer)(void);
void (* done_with_pointer)(void);

int main(int argc, char **argv)
{
	signed char one[BIT_SIZE], zero[BIT_SIZE], *bitbuf;
	bool quit = false;
	unsigned char *sndbuf;
	int bitpos = BIT_SIZE << 16, bufpos;

	if(argc < 2)
	{
		printf("\nUSAGE : freeuef <UEF filename> [mode]\n");
		printf("If mode is 'file', output will be to sound.raw rather than your soundcard.\n\n");
		exit(1);
	}

	keysetup();

	if(!infile.Open(argv[1], "r", 9, 0))
	{
		begin_sound = begin_sound_card;
		end_sound = end_sound_card;
		get_next_pointer = get_next_pointer_card;
		done_with_pointer = done_with_pointer_card;

		if(argc > 2)
		{
			if(!strcmp(argv[2], "file"))
			{
				begin_sound = begin_sound_file;
				end_sound = end_sound_file;
				get_next_pointer = get_next_pointer_file;
				done_with_pointer = done_with_pointer_file;
			}
		}

		open_next_chunk();
		set_to_wave(one, 2, 90);
		set_to_wave(zero, 1, 120);

		if(!begin_sound(OUTPUT_HZ, BUF_SIZE))
		{
			printf("ERROR : cannot open sound device\n");
			exit(3);
		}

		printf("RECORD then enter - %s to quit at any time (may take up to 1 second)\n", QKEY);
		getchar();

		while(!infile.OverRan() && !quit)
		{
			if(quitr())
			{
				quit = true;
				break;
			}

			sndbuf = get_next_pointer();

			for(bufpos = 0; bufpos < BUF_SIZE; bufpos++)
			{
				if(bitpos >= (BIT_SIZE << 16))
				{
					bitbuf = get_bit() ? one : zero;
					bitpos -= BIT_SIZE << 16;
				}

				sndbuf[bufpos] = 127 + (silence ? 0 : bitbuf[bitpos >> 16]);
				bitpos += uef_baudrate*((BIT_SIZE*65536) / OUTPUT_HZ);
			}

			done_with_pointer();
		}

		end_sound();

		if(infile.OverRan())
			printf("Done\n");
		else
			printf("Aborted\n");
	}
	else
	{
		printf("ERROR : cannot open UEF\n");
		exit(2);
	}

	return 0;
}
