/*
 * Copyright 2003-2015 (C) Raster Software Vigo (Sergio Costas)
 * This file is part of FBZX
 *
 * FBZX is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * FBZX is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/*
 * tape.cpp
 *
 *  Created on: 15/03/2015
 *      Author: raster
 */

#include "tape.hh"
#include "z80free/Z80free.h"

#include <iostream>
#include <fstream>

class Tape *OOTape; // Tape object

class TapeBlock {
private:
	uint8_t signal;

protected:
	bool read_8bit(ifstream *file, uint8_t&value) {
		uint8_t data;

		file->read((char *) &data, 1);
		if (file->eof()) {
			file->close(); // end-of-file
			return true;
		}
		value = data;
		return false;
	}

	bool write_8bit(ofstream *file, uint8_t value) {
		file->write((char *) &value, 1);
		return false;
	}

	bool read_16bit(ifstream *file, uint16_t&value) {
		uint8_t data[2];

		file->read((char *) data, 2);
		if (file->eof()) {
			file->close(); // end-of-file
			return true;
		}
		value = ((uint16_t) data[0]) + 256 * ((uint16_t) data[1]);
		return false;
	}

	bool write_16bit(ofstream *file, uint16_t value) {
		uint8_t data[2];

		data[0] = (uint8_t) (value % 256);
		data[1] = (uint8_t) (value / 256);
		file->write((char *) data, 2);
		return false;
	}

	bool read_24bit(ifstream *file, uint32_t&value) {
		uint8_t data[3];

		file->read((char *) data, 3);
		if (file->eof()) {
			file->close(); // end-of-file
			return true;
		}
		value = ((uint32_t) data[0]) + 256 * ((uint32_t) data[1]) + 65536 * ((uint32_t) data[2]);
		return false;
	}

	bool write_24bit(ofstream *file, uint32_t value) {
		uint8_t data[3];

		data[0] = (uint8_t) (value % 256);
		data[1] = (uint8_t) ((value / 256) % 256);
		data[2] = (uint8_t) (value / 65536);
		file->write((char *) data, 3);
		return false;
	}

	bool read_string(ifstream *file, string&value) {
		uint8_t length;
		char    data[256];

		if (this->read_8bit(file, length)) {
			return true;
		}
		file->read(data, length);
		if (file->eof()) {
			file->close(); // end-of-file
			return true;
		}
		data[length] = 0;
		value        = data;
		return false;
	}

	bool write_string(ofstream *file, string value) {
		uint8_t length;

		length = (uint8_t) value.length();
		if (this->write_8bit(file, length)) {
			return true;
		}
		file->write(value.c_str(), length);
		return false;
	}

protected:
	class TapeBlock *next;
	uint32_t counter0;
	uint32_t counter1;

public:
	bool has_data;
	TapeBlock() {
		this->has_data = true;
		this->next     = NULL;
		this->signal   = 0;
		this->counter0 = 0;
		this->counter1 = 0;
	}

	virtual ~TapeBlock() {
		if (this->next != NULL) {
			delete(this->next);
		}
	}

	virtual bool load_block(ifstream *) {
		return false;
	}

	virtual bool save_block(ofstream *) = 0;

	virtual bool fast_load(uint8_t *data, uint16_t&size, uint8_t&flag) {
		size = 0;
		flag = 0;
		return false;
	}

	/**
	 * Adds a new block to the end of the list
	 * @param block The block to add to the list
	 */
	virtual void add_block(class TapeBlock *new_block) {
		if (this->next == NULL) {
			this->next = new_block;
		} else {
			this->next->add_block(new_block);
		}
	}

	/**
	 * Returns the next block in the list
	 * @return The block next to this one
	 */
	virtual class TapeBlock *next_block() {
		return this->next;
	}

	/**
	 * Returns the tape signal at the current instant
	 * @return Tape signal
	 */
	uint8_t read_signal() {
		return this->signal;
	}

	/**
	 * Emulates the block for the specified time
	 * @param tstates The number of tstates to emulate
	 * @return The number of tstates that remained to emulate because the block ended prematurely
	 */
	uint32_t play(uint32_t tstates) {
		if (this->counter0 > 0) {
			if (this->counter0 >= tstates) {
				this->counter0 -= tstates;
				this->signal    = 0;
				return 0;
			} else {
				tstates       -= this->counter0;
				this->counter0 = 0;
			}
		}
		if (this->counter1 > 0) {
			if (this->counter1 >= tstates) {
				this->counter1 -= tstates;
				this->signal    = 1;
				return 0;
			} else {
				tstates       -= this->counter1;
				this->counter1 = 0;
			}
		}
		return tstates;
	}

	/**
	 * Sets the time values for the next bit to be read from the tape
	 * @return TRUE if there is a new bit; FALSE if there are no more bits in this block
	 */
	virtual bool next_bit() = 0;

	/**
	 * Resets a block to its initial state. Mandatory before starting working with it
	 */
	virtual void reset() {
	}
};

class EndLoopBlock : public TapeBlock {
private:
	uint16_t counter;
	class TapeBlock *loop;

public:
	bool save_block(ofstream *file) {
		if (this->write_8bit(file, 0x25)) { // block ID
			return true;
		}
		if (this->next == NULL) {
			return false;
		} else {
			return this->next->save_block(file);
		}
	}

	virtual bool next_bit() {
		return false;
	}

	void reset_counter(uint16_t value) {
		this->counter = value;
	}

	virtual class TapeBlock *next_block() {
		if (this->counter != 0) {
			this->counter--;
			return (this->loop->next_block());
		} else {
			return this->next;
		}
	}

	void set_loop_block(class TapeBlock *block) {
		this->loop = block;
	}
};

class LoopBlock : public TapeBlock {
private:
	uint16_t loop;
	class EndLoopBlock *endblock;

public:
	bool save_block(ofstream *file) {
		if (this->write_8bit(file, 0x24)) { // block ID
			return true;
		}
		if (this->write_16bit(file, this->loop)) {
			return true;
		}
		if (this->next == NULL) {
			return false;
		} else {
			return this->next->save_block(file);
		}
	}

	bool load_block(ifstream *file) {
		// read repetitions
		if (this->read_16bit(file, this->loop)) {
			return true;
		}
		this->endblock = NULL;
		return false;
	}

	/**
	 * Adds a new block to the end of the list
	 * @param block The block to add to the list
	 */
	virtual void add_block(class TapeBlock *new_block) {
		if (this->next == NULL) {
			this->next = new_block;
		} else {
			this->next->add_block(new_block);
		}
		if (this->endblock == NULL) {
			this->endblock = dynamic_cast<EndLoopBlock *>(new_block);
			if (this->endblock != NULL) {
				this->endblock->set_loop_block(this);
			}
		}
	}

	virtual void reset() {
		if (this->endblock != NULL) {
			this->endblock->reset_counter(this->loop);
		}
	}

	/**
	 * Returns the next block in the list
	 * @return The block next to this one
	 */
	virtual class TapeBlock *next_block() {
		return this->next;
	}

	virtual bool next_bit() {
		return false;
	}
};

class FullBlock : public TapeBlock {
protected:
	uint8_t *data = NULL;
	uint32_t data_size;
	uint16_t pilot;
	uint16_t sync0;
	uint16_t sync1;
	uint16_t zero;
	uint16_t one;
	uint16_t lpilot;
	uint8_t bits_at_end;
	uint32_t pause;

	uint8_t bit;
	uint8_t bit_counter;
	uint32_t loop;
	bool allow_fast_load;

	unsigned int pointer;

	enum TURBOBLOCK_states {
		TURBOBLOCK_GUIDE,
		TURBOBLOCK_DATA,
		TURBOBLOCK_PAUSE
	} current_state;

	void set_state(enum TURBOBLOCK_states new_state) {
		this->current_state = new_state;
		switch (new_state) {
		case TURBOBLOCK_GUIDE:
			this->loop = this->lpilot / 2;
			break;

		case TURBOBLOCK_DATA:
			this->pointer     = 0;
			this->bit         = 0x80;
			this->bit_counter = (this->data_size == 1) ? this->bits_at_end : 8;
			break;

		case TURBOBLOCK_PAUSE:
			break;
		}
	}

public:
	void reset() {
		this->counter0 = 0;
		this->counter1 = 0;
		this->set_state(TURBOBLOCK_GUIDE);
	}

	bool next_bit() {
		bool current_bit;

		switch (this->current_state) {
		case TURBOBLOCK_GUIDE:
			if (this->loop > 0) {
				// guide tone loop
				this->counter0 = this->pilot;
				this->counter1 = this->pilot;
				this->loop--;
			} else {
				// sync bit
				this->counter0 = this->sync0;
				this->counter1 = this->sync1;
				this->set_state(TURBOBLOCK_DATA);
			}
			return true;

			break;

		case TURBOBLOCK_DATA:
			if (this->bit_counter == 0) {
				this->bit = 0x80;
				this->pointer++;
				if ((this->data_size - 1) == this->pointer) {
					this->bit_counter = this->bits_at_end;
				} else {
					this->bit_counter = 8;
				}
				if (this->pointer == this->data_size) {
					this->counter0 = this->pause * 3500;
					this->counter1 = 0;
					this->set_state(TURBOBLOCK_PAUSE);
					return true;
				}
			}
			current_bit = ((*(this->data + this->pointer)) & this->bit) == 0;
			if (current_bit) {
				this->counter0 = this->zero;
				this->counter1 = this->zero;
			} else {
				this->counter0 = this->one;
				this->counter1 = this->one;
			}
			this->bit /= 2;
			this->bit_counter--;
			return true;

			break;

		case TURBOBLOCK_PAUSE:
			return false; // end of data

			break;
		}
		return true;
	}

	bool fast_load(uint8_t *data, uint16_t&size, uint8_t&flag) {
		if (this->allow_fast_load) {
			flag = this->data[0];
			size = this->data_size - 2;
			memcpy(data, this->data + 1, size);
			return true;
		} else {
			return false;
		}
	}
};

class TAPBlock : public FullBlock {
public:
	TAPBlock() {
	}

	TAPBlock(uint8_t *data, uint16_t size) : FullBlock() {
		this->next = NULL;

		this->data = new uint8_t[size];
		memcpy(this->data, data, size);

		this->allow_fast_load = true;
		this->data_size       = size;
		this->pilot           = 2168;
		this->sync0           = 667;
		this->sync1           = 735;
		this->zero            = 855;
		this->one             = 1710;
		if (!(0x80 & this->data[0])) {
			this->lpilot = 8063; // 5 seconds (aprox)
		} else {
			this->lpilot = 3223; // 2 seconds (aprox)
		}
		this->bits_at_end = 8;
		this->pause       = 1000;

		this->loop        = this->lpilot;
		this->pointer     = 0;
		this->bit         = 0x80;
		this->bit_counter = 8;
		this->set_state(TURBOBLOCK_GUIDE);
	}

	~TAPBlock() {
		if (this->data != NULL) {
			delete[](this->data);
		}
	}

	bool save_block(ofstream *file) {
		if (this->write_16bit(file, uint16_t(this->data_size))) {
			return true;
		}
		file->write((char *) this->data, this->data_size);
		if (this->next == NULL) {
			return false;
		} else {
			return this->next->save_block(file);
		}
	}

	bool load_block(ifstream *file) {
		uint16_t size;

		if (this->read_16bit(file, size)) {
			return true;
		}
		this->data = new uint8_t[size];
		file->read((char *) this->data, size);
		if (file->eof()) {
			file->close();
			return (true); // end-of-file and error
		}

		this->allow_fast_load = true;
		this->data_size       = size;
		this->pilot           = 2168;
		this->sync0           = 667;
		this->sync1           = 735;
		this->zero            = 855;
		this->one             = 1710;
		if (!(0x80 & this->data[0])) {
			this->lpilot = 8063; // 5 seconds (aprox)
		} else {
			this->lpilot = 3223; // 2 seconds (aprox)
		}
		this->bits_at_end = 8;
		this->pause       = 1000;

		this->loop        = this->lpilot;
		this->pointer     = 0;
		this->bit         = 0x80;
		this->bit_counter = 8;
		this->set_state(TURBOBLOCK_GUIDE);

		return false;
	}
};

class TZXBlock : public FullBlock {
public:
	TZXBlock() {
	}

	TZXBlock(uint8_t *data, uint16_t size) : FullBlock() {
		this->next = NULL;

		this->data = new uint8_t[size];
		memcpy(this->data, data, size);

		this->allow_fast_load = true;
		this->data_size       = size;
		this->pilot           = 2168;
		this->sync0           = 667;
		this->sync1           = 735;
		this->zero            = 855;
		this->one             = 1710;
		if (!(0x80 & this->data[0])) {
			this->lpilot = 8063; // 5 seconds (aprox)
		} else {
			this->lpilot = 3223; // 2 seconds (aprox)
		}
		this->bits_at_end = 8;
		this->pause       = 1000;

		this->loop        = this->lpilot;
		this->pointer     = 0;
		this->bit         = 0x80;
		this->bit_counter = 8;
		this->set_state(TURBOBLOCK_GUIDE);
	}

	~TZXBlock() {
		if (this->data != NULL) {
			delete[](this->data);
		}
	}

	bool load_block(ifstream *file) {
		uint16_t size;
		uint16_t pause;

		// read pause duration
		if (this->read_16bit(file, pause)) {
			return true;
		}
		// read block size
		if (this->read_16bit(file, size)) {
			return true;
		}
		this->data = new uint8_t[size];
		file->read((char *) this->data, size);
		if (file->eof()) {
			file->close();
			return (true); // end-of-file and error
		}
		this->allow_fast_load = true;
		this->data_size       = size;
		this->pilot           = 2168;
		this->sync0           = 667;
		this->sync1           = 735;
		this->zero            = 855;
		this->one             = 1710;
		if (!(0x80 & this->data[0])) {
			this->lpilot = 8063; // 5 seconds (aprox)
		} else {
			this->lpilot = 3223; // 2 seconds (aprox)
		}
		this->bits_at_end = 8;
		this->pause       = (uint32_t) pause;

		this->current_state = TURBOBLOCK_GUIDE;
		this->loop          = lpilot;
		this->pointer       = 0;
		this->bit           = 0x80;
		this->bit_counter   = 8;
		this->set_state(TURBOBLOCK_GUIDE);
		return false;
	}

	bool save_block(ofstream *file) {
		if (this->write_8bit(file, 0x10)) { // block ID
			return true;
		}
		if (this->write_16bit(file, uint16_t(this->pause))) {
			return true;
		}
		if (this->write_16bit(file, uint16_t(this->data_size))) {
			return true;
		}
		file->write((char *) this->data, this->data_size);
		if (this->next == NULL) {
			return false;
		} else {
			return this->next->save_block(file);
		}
	}
};

class TurboBlock : public FullBlock {
public:
	~TurboBlock() {
		if (this->data != NULL) {
			delete[](this->data);
		}
	}

	bool save_block(ofstream *file) {
		if (this->write_8bit(file, 0x11)) { // block ID
			return true;
		}
		if (this->write_16bit(file, this->pilot)) {
			return true;
		}
		if (this->write_16bit(file, this->sync0)) {
			return true;
		}
		if (this->write_16bit(file, this->sync1)) {
			return true;
		}
		if (this->write_16bit(file, this->zero)) {
			return true;
		}
		if (this->write_16bit(file, this->one)) {
			return true;
		}
		if (this->write_16bit(file, this->lpilot)) {
			return true;
		}
		if (this->write_8bit(file, this->bits_at_end)) {
			return true;
		}
		if (this->write_16bit(file, uint16_t(this->pause))) {
			return true;
		}
		if (this->write_24bit(file, this->data_size)) {
			return true;
		}
		file->write((char *) this->data, this->data_size);
		if (this->next == NULL) {
			return false;
		} else {
			return this->next->save_block(file);
		}
	}

	bool load_block(ifstream *file) {
		uint16_t pause;

		// read pilot pulse duration
		if (this->read_16bit(file, this->pilot)) {
			return true;
		}
		// read sync duration
		if (this->read_16bit(file, this->sync0)) {
			return true;
		}
		if (this->read_16bit(file, this->sync1)) {
			return true;
		}
		// read bit duration
		if (this->read_16bit(file, this->zero)) {
			return true;
		}
		if (this->read_16bit(file, this->one)) {
			return true;
		}
		// read pilot duration
		if (this->read_16bit(file, this->lpilot)) {
			return true;
		}
		// read number of bits at end
		if (this->read_8bit(file, this->bits_at_end)) {
			return true;
		}
		// read pause duration
		if (this->read_16bit(file, pause)) {
			return true;
		}
		this->pause = (uint32_t) pause;
		// read block size
		if (this->read_24bit(file, this->data_size)) {
			return true;
		}

		this->allow_fast_load = false;

		this->data = new uint8_t[this->data_size];
		file->read((char *) this->data, this->data_size);
		if (file->eof()) {
			file->close();
			return (true); // end-of-file and error
		}

		this->current_state = TURBOBLOCK_GUIDE;
		this->loop          = lpilot;
		this->pointer       = 0;
		this->bit           = 0x80;
		this->bit_counter   = 8;
		this->set_state(TURBOBLOCK_GUIDE);
		return false;
	}
};

class PureDataBlock : public TapeBlock {
	uint8_t *data = NULL;
	uint32_t data_size;
	uint16_t zero;
	uint16_t one;
	uint8_t bits_at_end;
	uint32_t pause;

	uint8_t bit;
	uint8_t bit_counter;

	unsigned int pointer;

public:
	~PureDataBlock() {
		if (this->data != NULL) {
			delete[](this->data);
		}
	}

	bool save_block(ofstream *file) {
		if (this->write_8bit(file, 0x14)) { // block ID
			return true;
		}
		if (this->write_16bit(file, this->zero)) {
			return true;
		}
		if (this->write_16bit(file, this->one)) {
			return true;
		}
		if (this->write_8bit(file, this->bits_at_end)) {
			return true;
		}
		if (this->write_16bit(file, uint16_t(this->pause))) {
			return true;
		}
		if (this->write_24bit(file, this->data_size)) {
			return true;
		}
		file->write((char *) this->data, this->data_size);
		if (this->next == NULL) {
			return false;
		} else {
			return this->next->save_block(file);
		}
	}

	bool load_block(ifstream *file) {
		unsigned int loop;
		uint16_t     lpause;
		// read pulse lengths
		if (this->read_16bit(file, this->zero)) {
			return true;
		}
		if (this->read_16bit(file, this->one)) {
			return true;
		}
		// read final bit number
		if (this->read_8bit(file, this->bits_at_end)) {
			return true;
		}
		// read pause
		if (this->read_16bit(file, lpause)) {
			return true;
		}
		this->pause = lpause;
		// read repetitions
		if (this->read_24bit(file, this->data_size)) {
			return true;
		}
		this->data = new uint8_t[this->data_size];
		for (loop = 0; loop < this->data_size; loop++) {
			if (this->read_8bit(file, this->data[loop])) {
				return true;
			}
		}
		this->pointer     = 0;
		this->bit         = 0x80;
		this->bit_counter = (this->data_size == 1) ? this->bits_at_end : 8;

		return false;
	}

	void reset() {
		this->counter0    = 0;
		this->counter1    = 0;
		this->bit         = 0x80;
		this->pointer     = 0;
		this->bit_counter = (this->data_size == 1) ? this->bits_at_end : 8;
		this->next_bit();
	}

	bool next_bit() {
		bool current_bit;

		if (this->data_size == this->pointer) {
			return false;
		}

		if (this->bit_counter == 0) {
			this->bit = 0x80;
			this->pointer++;
			if ((this->data_size - 1) == this->pointer) {
				this->bit_counter = this->bits_at_end;
			} else {
				this->bit_counter = 8;
			}
			if (this->pointer == this->data_size) {
				this->counter0 = this->pause * 3500;
				this->counter1 = 0;
				return true;
			}
		}
		current_bit = ((*(this->data + this->pointer)) & this->bit) == 0;
		if (current_bit) {
			this->counter0 = this->zero;
			this->counter1 = this->zero;
		} else {
			this->counter0 = this->one;
			this->counter1 = this->one;
		}
		this->bit /= 2;
		this->bit_counter--;
		return true;
	}
};

class ToneBlock : public TapeBlock {
	uint16_t pilot;
	uint16_t lpilot;
	uint32_t loop;

public:
	bool save_block(ofstream *file) {
		if (this->write_8bit(file, 0x12)) { // block ID
			return true;
		}
		if (this->write_16bit(file, this->pilot)) {
			return true;
		}
		if (this->write_16bit(file, this->lpilot)) {
			return true;
		}
		if (this->next == NULL) {
			return false;
		} else {
			return this->next->save_block(file);
		}
	}

	bool load_block(ifstream *file) {
		// read pilot pulse duration
		if (this->read_16bit(file, this->pilot)) {
			return true;
		}
		// read pilot duration
		if (this->read_16bit(file, this->lpilot)) {
			return true;
		}
		this->loop = this->lpilot;
		return false;
	}

	void reset() {
		this->counter0 = 0;
		this->counter1 = 0;
		this->loop     = lpilot / 2;
		this->next_bit();
		if (lpilot % 2) {
			printf("odd number of pulses in Pure Tone Block\n");
		}
	}

	bool next_bit() {
		if (this->loop > 0) {
			// guide tone loop
			this->counter0 = this->pilot;
			this->counter1 = this->pilot;
			this->loop--;
			return true;
		} else {
			return false;
		}
	}
};

class PulsesBlock : public TapeBlock {
	uint16_t pulses[255];
	uint8_t npulses;
	uint8_t loop;
	uint8_t counter;

public:
	bool save_block(ofstream *file) {
		if (this->write_8bit(file, 0x13)) { // block ID
			return true;
		}
		if (this->write_8bit(file, this->npulses)) {
			return true;
		}
		for (int i = 0; i < this->npulses; i++) {
			this->write_16bit(file, this->pulses[i]);
		}
		if (this->next == NULL) {
			return false;
		} else {
			return this->next->save_block(file);
		}
	}

	bool load_block(ifstream *file) {
		uint8_t loop;

		// read repetitions
		if (this->read_8bit(file, this->npulses)) {
			return true;
		}
		for (loop = 0; loop < this->npulses; loop++) {
			if (this->read_16bit(file, this->pulses[loop])) {
				return true;
			}
		}
		this->counter = 0;
		this->loop    = this->npulses / 2;
		return false;
	}

	void reset() {
		this->counter  = 0;
		this->counter0 = 0;
		this->counter1 = 0;
		this->loop     = this->npulses / 2;
		this->next_bit();
		if (this->npulses % 2) {
			printf("Odd number of pulses in Pulses Block\n");
		}
	}

	bool next_bit() {
		if (this->loop != 0) {
			// guide tone loop
			this->counter0 = this->pulses[this->counter++];
			this->counter1 = this->pulses[this->counter++];
			this->loop--;
			return true;
		} else {
			return false;
		}
	}
};

class PauseBlock : public TapeBlock {
	uint16_t length;
	Tape *tape;

public:
	PauseBlock(Tape *tape) : TapeBlock() {
		this->has_data = false;
		this->length   = 0;
		this->tape     = tape;
	}

	bool save_block(ofstream *file) {
		if (this->write_8bit(file, 0x20)) { // block ID
			return true;
		}
		if (this->write_16bit(file, this->length)) {
			return true;
		}
		if (this->next == NULL) {
			return false;
		} else {
			return this->next->save_block(file);
		}
	}

	bool load_block(ifstream *file) {
		// read pilot pulse duration
		if (this->read_16bit(file, this->length)) {
			return true;
		}
		return false;
	}

	void reset() {
		this->counter0 = this->length;
		this->counter1 = 0;
	}

	bool next_bit() {
		if (this->length == 0) {
			this->tape->send_pause_signal();
		}
		return false;
	}
};

class Pause48Block : public TapeBlock {
	Tape *tape;

public:
	Pause48Block(Tape *tape) : TapeBlock() {
		this->has_data = false;
		this->tape     = tape;
	}

	bool save_block(ofstream *file) {
		if (this->write_8bit(file, 0x2A)) { // block ID
			return true;
		}
		if (this->next == NULL) {
			return false;
		} else {
			return this->next->save_block(file);
		}
	}

	bool next_bit() {
		this->tape->send_pause_signal48k();
		return false;
	}
};

class GroupStartBlock : public TapeBlock {
	string name;

public:
	bool save_block(ofstream *file) {
		if (this->write_8bit(file, 0x21)) { // block ID
			return true;
		}
		if (this->write_string(file, this->name)) {
			return true;
		}
		if (this->next == NULL) {
			return false;
		} else {
			return this->next->save_block(file);
		}
	}

	bool load_block(ifstream *file) {
		if (this->read_string(file, this->name)) {
			return true;
		}
		return false;
	}

	string get_name() {
		return this->name;
	}

	bool next_bit() {
		return false;
	}
};

class GroupEndBlock : public TapeBlock {
public:
	GroupEndBlock() {
	}

	bool save_block(ofstream *file) {
		if (this->write_8bit(file, 0x22)) { // block ID
			return true;
		}
		if (this->next == NULL) {
			return false;
		} else {
			return this->next->save_block(file);
		}
	}

	bool next_bit() {
		return false;
	}
};

class InfoBlock : public TapeBlock {
	uint8_t *data;
	uint16_t size;

public:
	InfoBlock() {
		this->data     = NULL;
		this->size     = 0;
		this->has_data = false;
	}

	~InfoBlock() {
		if (this->data != NULL) {
			delete[](this->data);
		}
	}

	bool save_block(ofstream *file) {
		if (this->write_8bit(file, 0x32)) { // block ID
			return true;
		}
		if (this->write_16bit(file, this->size)) {
			return true;
		}
		file->write((char *) this->data, this->size);
		if (this->next == NULL) {
			return false;
		} else {
			return this->next->save_block(file);
		}
	}

	bool load_block(ifstream *file) {
		if (this->read_16bit(file, this->size)) {
			return true;
		}
		this->data = new uint8_t[this->size];
		file->read((char *) this->data, this->size);
		if (file->eof()) {
			return true;
		}
		return false;
	}

	bool next_bit() {
		return false;
	}
};

class TextBlock : public TapeBlock {
	uint8_t *data;
	uint8_t size;

public:
	TextBlock() {
		this->data     = NULL;
		this->size     = 0;
		this->has_data = false;
	}

	~TextBlock() {
		if (this->data != NULL) {
			delete[](this->data);
		}
	}

	bool save_block(ofstream *file) {
		if (this->write_8bit(file, 0x30)) { // block ID
			return true;
		}
		if (this->write_8bit(file, this->size)) {
			return true;
		}
		file->write((char *) this->data, this->size);
		if (this->next == NULL) {
			return false;
		} else {
			return this->next->save_block(file);
		}
	}

	bool load_block(ifstream *file) {
		if (this->read_8bit(file, this->size)) {
			return true;
		}
		this->data = new uint8_t[this->size];
		file->read((char *) this->data, this->size);
		if (file->eof()) {
			return true;
		}
		return false;
	}

	bool next_bit() {
		return false;
	}
};

Tape::Tape() {
	this->blocks        = NULL;
	this->current_block = NULL;
	this->paused        = true;
	this->block_accesed = false;
	this->tzx           = false;
	this->current_file  = "";
}

Tape::~Tape() {
	this->delete_blocks();
}

void Tape::add_block(class TapeBlock *new_block) {
	if (this->blocks == NULL) {
		this->blocks = new_block;
	} else {
		this->blocks->add_block(new_block);
	}
}

void Tape::delete_blocks() {
	delete(this->blocks);
	this->blocks        = NULL;
	this->current_block = NULL;
	this->block_accesed = false;
}

bool Tape::load_file(string filename) {
	char char_id[10];
	this->delete_blocks();
	bool retval;

	this->paused       = true;
	this->current_file = "";

	ifstream file(filename.c_str(), ios::in | ios::binary);

	if (!file.is_open()) {
		return true;        // error while opening the file
	}
	file.read(char_id, 10); // read the (maybe) TZX header
	if (!file.eof() && ((!strncmp(char_id, "ZXTape!", 7)) && (char_id[7] == 0x1A) && (char_id[8] == 1))) {
		file.close();
		this->tzx = true;
		retval    = this->load_tzx(filename);
		if (!retval) {
			this->current_file = filename;
		}
		return retval;
	} else {
		file.close();
		this->tzx = false;
		retval    = this->load_tap(filename);
		if (!retval) {
			this->current_file = filename;
		}
		return retval;
	}
}

bool Tape::load_tap(string filename) {
	ifstream *      file;
	class TAPBlock *block;

	file = new ifstream(filename.c_str(), ios::in | ios::binary);
	if (!file->is_open()) {
		delete(file);
		return true; // error while opening the file
	}
	do {
		block = new TAPBlock();
		if (block->load_block(file) == false) {
			this->add_block(block);
		} else {
			delete(block);
			break;
		}
	} while (true);

	this->current_block = this->blocks;
	delete file;
	return false;
}

bool Tape::load_tzx(string filename) {
	ifstream *       file;
	uint8_t          block_type;
	uint8_t          tmpdata[10];
	class TapeBlock *block;

	file = new ifstream(filename.c_str(), ios::in | ios::binary);
	if (!file->is_open()) {
		delete(file);
		return true;                  // error while opening the file
	}
	file->read((char *) tmpdata, 10); // read TZX header
	while (true) {
		// read block type
		file->read((char *) &block_type, 1);
		if (file->eof()) {
			break; // end-of-file
		}
		switch (block_type) {
		case 0x10: // standard block
			block = new TZXBlock();
			break;

		case 0x11: // turbo block
			block = new TurboBlock();
			break;

		case 0x12: // tone block
			block = new ToneBlock();
			break;

		case 0x13:
			block = new PulsesBlock();
			break;

		case 0x14:
			block = new PureDataBlock();
			break;

		case 0x20: // Pause block
			block = new PauseBlock(this);
			break;

		case 0x21: // group start
			block = new GroupStartBlock();
			break;

		case 0x22: // group end
			block = new GroupEndBlock();
			break;

		case 0x24: // Loop Start
			block = new LoopBlock();
			break;

		case 0x25: // Loop End
			block = new EndLoopBlock();
			break;

		case 0x30: // Text block
			block = new TextBlock();
			break;

		case 0x32: // Archive Info block
			block = new InfoBlock();
			break;

		default:
			printf("Unknown block number: %X\n", block_type);
			file->close();
			delete(file);
			return true;

			break;
		}
		if (block->load_block(file)) {
			file->close();
			delete(file);
			return true;
		}
		this->add_block(block);
	}

	file->close();
	delete(file);
	this->current_block = this->blocks;
	return false;
}

void Tape::play(uint32_t tstates) {
	uint32_t residue = tstates;

	this->rewinded = false;
	if (this->current_block == NULL) {
		return;
	}

	if (this->paused) {
		return;
	}

	this->block_accesed = true;
	while (true) {
		residue = this->current_block->play(residue);
		if (residue != 0) {
			if (this->current_block->next_bit() == false) {
				this->current_block = this->current_block->next_block();
				if (this->current_block == NULL) {
					this->current_block = this->blocks;
					this->rewinded      = true;
					break;
				}
				this->current_block->reset();
				this->block_accesed = false;
			}
		} else {
			break;
		}
		if (this->paused) {
			break;
		}
	}
}

uint8_t Tape::read_signal() {
	if (this->current_block == NULL) {
		return 0;
	} else {
		return this->current_block->read_signal();
	}
}

void Tape::rewind() {
	this->current_block = this->blocks;
	if (this->current_block != NULL) {
		this->current_block->reset();
	}
	this->block_accesed = false;
	osd->set_message("Tape rewinded", 1000);
}

void Tape::set_pause(bool pause) {
	this->paused = pause;
	if (pause) {
		osd->set_message("Tape paused", 2000);
	}
}

bool Tape::get_pause() {
	return this->paused;
}

enum FastLoadReturn Tape::fast_read(uint8_t *data, uint16_t&length, uint8_t flag) {
	uint8_t block_flag;
	bool    hasdata;

	if (this->blocks == NULL) {
		return FASTLOAD_NO_TAPE;
	}

	if (this->block_accesed) {
		if (this->current_block != NULL) {
			this->current_block = this->current_block->next_block();
		}
	}
	if (this->current_block == NULL) {
		return FASTLOAD_END_TAPE;
	}
	this->block_accesed = false;

	hasdata = this->current_block->has_data;
	if (hasdata) {
		if (!this->current_block->fast_load(data, length, block_flag)) {
			return FASTLOAD_NO_BLOCK;
		}
	}

	this->current_block = this->current_block->next_block();
	if (this->current_block != NULL) {
		this->current_block->reset();
	}

	if (!hasdata) {
		return FASTLOAD_NODATA;
	}

	if (block_flag != flag) {
		return FASTLOAD_NO_FLAG;
	}

	return FASTLOAD_OK;
}

void Tape::send_pause_signal() {
	this->send_signal("pause_tape");
}

void Tape::send_pause_signal48k() {
	this->send_signal("pause_tape_48k");
}

void Tape::save_file(string filename) {
	if (this->blocks != NULL) {
		ofstream *file = new ofstream(filename.c_str(), ios::out | ios::binary | ios::trunc);
		if (this->tzx) {
			file->write("ZXTape!\032\001\024", 10); // TZX header
		}
		this->blocks->save_block(file);
		file->close();
		delete file;
	}
}

bool Tape::add_block(uint8_t *data, uint16_t size) {
	class TapeBlock *block;

	if (this->current_file == "") {
		return true;
	}
	if (this->tzx) {
		block = new TZXBlock(data, size);
	} else {
		block = new TAPBlock(data, size);
	}

	this->add_block(block);
	if (this->current_block == NULL) {
		this->current_block = this->blocks;
		this->block_accesed = false;
	}
	block->reset();
	this->save_file(this->current_file);
	return false;
}
