/*
 * BBCTAPE.CPP
 *
 * This code is not intended to be readable - sorry about that.
 * I might comment it later on some time.
 *
 * Tab size 4.
 */

#define VERSION "0.91 beta"

#include <ctype.h>		// for isalpha()
#include <assert.h>		// for assert()
#include <stdlib.h>		// for exit()
#include <string.h>		// for strlen()
#include <fstream.h>
#include <stdio.h>
#include <iomanip.h>

typedef unsigned char BYTE;
typedef unsigned short WORD;

template <class T>
T min(T a, T b) { return a<b?a:b; }

template <class T>
T max(T a, T b) { return b<a?a:b; }

template <class T>
T sign0(T a) { return 0<a?1:a<0?-1:0; }

template <class T>
T sign(T a) { return 0<a?1:-1; }

template <class T>
T abs(T a) { return 0<=a?a:-a; }

template <class T>
int bound(T x, T a, T b) { return a<x && x<b; }

template <class T>
class CircularBuffer
	{
	int n, last;
	T *buf;

public:
	CircularBuffer(int _n) : n(_n), buf(new T[n]), last(0) {}
	~CircularBuffer() { delete[] buf; }
	T operator[](unsigned int i) const
		{ assert(i<n); int j=last-i; return buf[j<0 ? j+n : j]; }
	CircularBuffer<T> &operator <<(T x)
		{ last++; if (last==n) last = 0; buf[last]=x; return *this; }
	T leaving() { return (*this)[n-1]; }
	};

class TapeBlock
	{
public:
	char filename[11];		// 10 + ASCII 0
	unsigned long load_address, exec_address;
	unsigned short number, length;
	unsigned char flag, spare[4];
        unsigned short header_crc;              
	unsigned char *data;
        unsigned short data_crc;                

	TapeBlock() : data(0) {}
	~TapeBlock() { delete data; }
	};

class Exception
	{
	char *reason;
public:
	Exception(char *r) { reason = strdup(r); }
	virtual ~Exception() { free(reason); }
	void dump(ostream &o) const { o << reason; }
	};

class InputException : public Exception
	{
	int pos1, pos2;
public:
	InputException(char *r, int p) : Exception(r), pos1(p), pos2(p) {}
	InputException(char *r, int p1, int p2) : Exception(r), pos1(p1), pos2(p2) {}
	void dump(ostream &o) const
		{ Exception::dump(o);
		  if (pos1==pos2) o<<" at "<<pos1;
		  else o<<" in ["<<pos1<<","<<pos2<<"]"; }
	};

class GiveUpInputEx : public InputException
	{
public:
	GiveUpInputEx(char *r, int p) : InputException(r,p) {}
	};

class GiveUpBlockEx : public InputException
	{
public:
	GiveUpBlockEx(char *r, int p) : InputException(r,p) {}
	GiveUpBlockEx(char *r, int p1, int p2) : InputException(r,p1,p2) {}
	};

ifstream in;
ofstream log;
CircularBuffer<int> buffer(128);
int bytes_read = 0,
	dir = 0,
	dir_count = 0,
        bit_flank_sign = 1,
	bottom, top,
	funny_name;
const int min_flank_length = 5;
int prev_found_flank = 0;
float average_flank = 0.0, threshold, bit_length, bit_error,
	low_0, high_0, low_1, high_1;

const int maxbuflen = 65536;
BYTE inbuffer[maxbuflen];
int buflen = 0, bufpos = maxbuflen;

//for debugging
int block_number=-1;


int next_sample()
	{
	if (bufpos == maxbuflen)
		{
		in.read(inbuffer, maxbuflen);
		buflen = in.gcount();
		bufpos = 0;
		}

	bytes_read++;
	if (buflen < maxbuflen && bufpos == buflen)
		throw GiveUpInputEx("end of file", bytes_read);

	int b = inbuffer[bufpos++] - 0x7f;
	return bit_flank_sign<0 ? -b : b;
	}

float find_flank(int dir)
	{
	int *extreme = dir<0 ? &top : &bottom, dist;
	*extreme = buffer[0];
	for (;;)
		{
		buffer << next_sample();
		int delta = buffer[0]-*extreme;
		if (sign(delta) == -dir)
            // update extrema (top or bottom)
			*extreme = buffer[0];
		else
			if (abs(delta) > (top-bottom)/3)
                // We have moved from the last extrema by more than
                // one third of the height between the last *two*
                // extremas.  Then we have a flank.
				break;
		}
	dist = bytes_read - prev_found_flank;
	prev_found_flank = bytes_read;
	return dist;
	}

void initial_synch()
	{
	int i, prev_flank, dir = 1;
	const int flanks = 500;
	average_flank = 0;
	CircularBuffer<float> flank(flanks);

        // skip first n flanks (but add them to averaging buffer)

	for (i=0; i<flanks; i++)
		{
		flank << find_flank(dir); dir = -dir;
		average_flank += flank[0];
		}
	average_flank /= flanks;

    // keep getting flanks until all flank lengths in buffer are close
	// "enough" to the average

	int good_flanks = 0;
	while (good_flanks < flanks)
		{
		average_flank -= flank[flanks-1] / flanks;
		flank << find_flank(dir);
        dir=-dir;
		average_flank += flank[0] / flanks;
		if (bound(flank[0], float(0.7*average_flank), float(1.3*average_flank)))
			good_flanks++;
		else
			good_flanks = 0;
		}

	log << "average flank distance " << average_flank << " achieved at " << bytes_read << endl;
	bit_length = 4*average_flank;
	log << "average bit length " << bit_length << endl;
	bit_error = 0;

	low_1 = 0.3*average_flank;
	high_1 = 1.3*average_flank;
	low_0 = 1.3*average_flank;
	high_0 = 3.0*average_flank;
	log << "'0': " << low_0 << " - " << high_0 << endl;
	log << "'1': " << low_1 << " - " << high_1 << endl;

	}

int find_block_start_bit()
	{
	for (;;)
		{
        bit_flank_sign = 1;
		int dir = 1;
		while (!bound(find_flank(dir), low_0, high_0))
			{
                        dir = -dir;  // toggle expected flank direction
			}
		if (!bound(find_flank(-dir), low_0, high_0))
			{
			log << "bad start bit at " << bytes_read
//				<< " top " << top << " bottom " <<bottom<<" [0]="<<buffer[0]
				<<endl;
			if (top-bottom < 10)
				throw GiveUpBlockEx("weak signal", bytes_read);
			}
		else
			{
			bit_flank_sign *= sign(buffer[0] - buffer[1]);
			log << "bit flank sign is " << bit_flank_sign << " at " << bytes_read<<endl;
			return 1;
			}
		}
	return 0;
	}

void find_wave()
	{
	top = buffer[0];
	for (;;)
		{
		buffer << next_sample();
		if (buffer[0] > top)
			top = buffer[0];
		else
			if ((top - buffer[0]) >= (top - bottom)/2)
				break;
		}
	bottom = buffer[0];
	for (;;)
		{
		buffer << next_sample();
		if (buffer[0] < bottom)
			bottom = buffer[0];
		else
			if ((buffer[0] - bottom) >= (top - bottom)/2)
				break;
		}
	}

int get_next_bit()
	{
	int start = bytes_read, bit = -1;

	find_wave();
	if (bound((bytes_read - start) / bit_length, float(0.8), float(1.2)))
		bit = 0;
	else
		{
		find_wave();
		if (bound((bytes_read - start) / bit_length, float(0.8), float(1.2)))
			bit = 1;
		}
        if (bit < 0)
            log << "bad bit between " << start << " and " << bytes_read << endl;
	return bit;
	}

int get_start_bit()
	{
	int pos = bytes_read;
	if (get_next_bit() == 1)
		{
		log << "extra stop bit (?) at " << pos << endl;
		if (get_next_bit() != 0)
			throw GiveUpBlockEx("expected start bit (0)", pos);
		}

	return 1;
	}

int get_stop_bit()
	{
	int pos = bytes_read;
	if (get_next_bit() != 1)
		throw GiveUpBlockEx("expected stop bit (1)", pos);
	return 1;
	}


int get_byte(int have_start_bit = 0)
	{
	if (!have_start_bit)
		get_start_bit();
	int byte = 0;
	for (int bit=1; bit<=128; bit<<=1)
		if (get_next_bit())
			byte |= bit;
	get_stop_bit();

	return byte;
	}

// Calculate 16-bit CRC according to code at AUG p.348 (I think).

int crc16(BYTE *data, int len)
	{
	union
		{
		WORD w;
		struct { BYTE lb, hb; };
		} crc;

	crc.w = 0;
	for (int i=0; i<len; i++)
		{
		crc.hb ^= *(data++);
		int carry = 0;
		for (int j=0; j<8; j++)
			{
			if (crc.hb & 0x80)
				{
				crc.w ^= 0x810;
				crc.w <<= 1;
				crc.w |= 1;
				}
			else
				crc.w <<= 1;
			}
		}
	return crc.w;
	}

void get_block(TapeBlock &block)
	{
	int got_block = 0, byte, i, crc;
	while (!got_block)
		{
		try {
			memset(block.filename, 0, 11);
			find_block_start_bit();
			byte = get_byte(1);
			switch (byte)
				{
				case 0xaa:
					log << "sync bug fix byte (&AA) found" << endl;
					break;

				case 0x2a:
					i=0;
					do	{
						block.filename[i++] = byte = get_byte();
						if (byte != 0)
							cout << char(byte); // << "("<<bytes_read<<")";
						}
					while (byte != 0 && strlen(block.filename)<11);
					if (byte != 0)
						throw GiveUpBlockEx("filename too long", bytes_read);

					block.load_address = get_byte();
					block.load_address |= get_byte() << 8;
					block.load_address |= get_byte() << 16;
					block.load_address |= get_byte() << 24;

					cout << "\t" << hex << block.load_address;

					try{
					block.exec_address = get_byte();
					block.exec_address |= get_byte() << 8;
					block.exec_address |= get_byte() << 16;
					block.exec_address |= get_byte() << 24;
					} catch (...) {}

					cout << "\t" << block.exec_address;

					block.number = get_byte();
					block.number |= get_byte() << 8;
					block_number = block.number;

					cout << "\t" << block.number;

					block.length = get_byte();
					block.length |= get_byte() << 8;

					cout << "\t" << block.length;

					block.flag = get_byte();

					cout << "\t" << int(block.flag);

					for (i=0; i<4; i++)
						block.spare[i] = get_byte();

                                        // NOTE: header CRC verification not implemented!

                                        block.header_crc = get_byte() << 8; // MSB first
					block.header_crc |= get_byte();

					if (block.length)
						{
						delete block.data;
						block.data = new unsigned char[block.length];
						assert(block.data);
						for (i=0; i<block.length; i++)
							block.data[i] = get_byte();

                                                block.data_crc = get_byte() << 8;  // MSB first
						block.data_crc |= get_byte();

						crc = crc16(block.data, block.length);
						if (block.data_crc != crc)
							{
							log << hex << "data crc failed (read "<< block.data_crc
								<< ", calculated " << crc << ")" << endl<<dec;
							cout << " Data CRC error!";
							}
						}

					cout << "\n" << dec; //((block.flag & 0x80) ? "\n" : "\r");

					got_block = 1;
					break;

				default:
					log << "bad sync byte " << hex << byte << dec<<endl;
				}
			}
		catch (GiveUpBlockEx &e)
			{
			e.dump(log); log << endl;
			if (strlen(block.filename)>0)
				cout << " Bad block!" << endl;
			initial_synch();
			}

		cerr << flush;
		log << flush;
		cout << flush;
		}
	}

void show_help()
	{
    cout << "BBC tape decoder - version " << VERSION << endl <<
            "(C) Copyright 1996 by Robert Schmidt <robert@idt.unit.no>\n\n"
            "Decodes an unsigned 8-bit mono 22-44 kHz raw tape sample into data blocks.\n"
			"By default, blocks are *not* saved (i.e. equivalent to *CAT)\n."
			"Pressing any key will abort decoding before end of file.\n\n"
			"Parameters:\n"
			"filename : sample file to decode\n"
			"-c : append file info to __CATALOG__ (Xbeeb style)\n"
			"     (You will probably need the load and exec addresses anyway!)\n"
			"-d : enable verbose debugging and error messages\n"
			"     (You don't want this unless you get CRC errors.)\n"
			"-s : save decoded files to DOS files\n"
			"-?,-h : this help text\n";
	}

int main(int argc, char **argv)
	{
	char *filename;
	int save = 0, catalog = 0, prev_block;
	if (argc == 1)
		show_help();
	for (int i=1; i<argc; i++)
		{
		switch (argv[i][0])
			{
			case '-':
				switch (argv[i][1])
					{
					case 'c':
						catalog = argv[i][2] != '-';
						break;
					case 'd':
						log.open("con");
						break;
					case 's':
						save = argv[i][2] != '-';
						break;
					default:
						cerr << "unknown switch " << argv[i] << endl;
					case '?':
					case 'h':
						show_help();
						break;
					}
				break;

			default:
				in.open(argv[i], ios::in | ios::binary | ios::nocreate);
				try {
					if (in.fail())
						{
						cout << "Sample file not found.\n\n";
						show_help();
						}
					initial_synch();
					TapeBlock block;
					cout << "name\tload\texec\tblock\tlength\tflag (80=last)\n";
					for (;;)	// until EOF thrown
						{
						get_block(block);
						ofstream out;
						// Make a Win95 filename - not guaranteed unique!
						char fn[50];
						int fnl = 0;

                                                // replace characters that are illegal in Win95 filenames
                                                // with '~'.

						for (int i=0; i<strlen(block.filename); i++)
							{
							fn[fnl] = block.filename[i];
							if (fn[fnl] < 32 ||
								fn[fnl] == '\\' ||
								fn[fnl] == '*' ||
								fn[fnl] == '?' ||
								fn[fnl] == '-')
								{
								fn[fnl] = '~';
	//							fn[++fnl] = '0'+funny_name++;
								}
							fnl++;
							}
						fn[fnl] = 0;
						if (save)
							{
							if (block.number > 0)
								if (block.number > prev_block + 1)
									{
									cerr << "Missing block(s) " << prev_block+1
										<< " to " << block.number-1;
									cerr << endl << flush;
									}
							prev_block = block.number;
							out.open(fn, ios::out | ios::binary |
								(block.number ? ios::app : ios::trunc));
	// 						out.seekp(256L * block.number);
							out.write(block.data, block.length);
							out.close();
							}

                        // add to catalog if it's the last block

						if (catalog && (block.flag & 0x80))
							{
                            char temp[20];
                            strcpy(temp,"\"");
                            strcat(temp, block.filename);
                            strcat(temp,"\"");
							out.open("__CATALOG__", ios::out | ios::app);
                            out << setw(15) << setiosflags(ios::left) << temp << resetiosflags(ios::left)
								<< (block.flag&1?" L ":"   ") //locked?
                                << hex << setw(9) << block.load_address
								<< setw(9) << block.exec_address
								<< setw(5) << block.number*256 + block.length
								<< setw(5) << 0	// sector number
                                << '"' << fn << '"'  // Win95 file name
								<< endl;
							out.close();
							}
						}
					}
				catch (GiveUpInputEx &e)
					{
					e.dump(log); log << endl;
					}
				in.close();
			}
		}

	return 0;
	}
