#include "famitext.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>

bool debug = false;
#define MESSAGE(...) {}

static const char* NOTE_NAMES[12] = {
	"C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-"
};

// extracts whitespace separated token from line, places in token[]
static char token[1024];
static int read_token(const char* line_start) // returns characters read, 0 for end of line
{
	token[0] = 0;

	const char* line = line_start;
	while (*line == ' ' || *line == 9) ++line; // eat whitespace
	if (*line == 0) return 0; // no token

	token[1023] = 0;
	for (int i=0; i<1023; ++i)
	{
		token[i] = *line;
		if (*line == ' ' || *line == 9)
		{
			token[i] = 0;
			break;
		}
		++line;
	}
	return line - line_start;
}

void merge_cell(FamiText::Cell& dest, const FamiText::Cell& src)
{
	if (src.note != 0) dest.note = src.note;
	if (src.inst != 0) dest.inst = src.inst;
	if (src.vol  != 0) dest.vol  = src.vol;
	for (int e=0; e < 4; ++e)
	{
		if (src.etype[e] != 0)
		{
			dest.etype[e]  = src.etype[e];
			dest.eparam[e] = src.eparam[e];
		}
	}
}

// FamiText

FamiText::FamiText()
{
	clear();
}

FamiText::~FamiText()
{
}

void FamiText::clear()
{
	song_channels = 5;
	song_speed = 6;
	song_pattern_len = 64;
	song_frames = 0;
	for (int i=0; i<MAX_CHANNELS; ++i) song_columns[i] = 1;

	order.clear();
	patterns.clear();

	play();
}

const char* FamiText::read(const char* filename)
{
	FILE* f = ::fopen(filename, "rt");
	if (f == NULL)
	{
		return "Could not open file.";
	}

	char line[1024];
	int write_pattern = 0;
	
	while ( ::fgets(line, sizeof(line), f) ) // read line by line
	{
		// strip newline from end of line
		for (int i=::strlen(line)-1; i >= 0; --i)
		{
			if (line[i] == 13 || line[i] == 10)
				line[i] = 0;
			else
				break;
		}

		int tr = read_token(line);
		char* l = line + tr;
		if (!tr) continue; // nothing on this line

		if (!::strcmp("EXPANSION", token))
		{
			tr = read_token(l);
			int chips = ::strtol(token,NULL,10);
			switch (chips)
			{
				case 1:  song_channels = 5 + 3; break; // VRC6
				case 2:  song_channels = 5 + 6; break; // VRC7
				case 4:  song_channels = 5 + 1; break; // FDS
				case 8:  song_channels = 5 + 2; break; // MMC5
				//case 16: song_channels = 5 + 4; break; // N163
				//case 32: song_channels = 5 + 3; break; // 5B
				default:
					::fclose(f);
					return "Unsupported expansion.";
			}
		}
		else if (!::strcmp("TRACK", token))
		{
			tr = read_token(l); l = l + tr;
			song_pattern_len = ::strtol(token,NULL,10);

			tr = read_token(l); l = l + tr;
			song_speed = ::strtol(token,NULL,10);

			tr = read_token(l); l = l + tr;
			int tempo = ::strtol(token,NULL,10);
			if (tempo != 150)
			{
				::fclose(f);
				return "150bpm is only supported tempo.";
			}
		}
		else if (!::strcmp("COLUMNS", token))
		{
			tr = read_token(l); l = l + tr; // skip :
			for (int c=0; c < song_channels; ++c)
			{
				tr = read_token(l); l = l + tr;
				song_columns[c] = ::strtol(token,NULL,10);
			}
		}
		else if (!::strcmp("ORDER", token))
		{
			tr = read_token(l); l = l + tr;
			int write_order = ::strtol(token,NULL,16);

			tr = read_token(l); l = l + tr; // skip :

			if (debug) MESSAGE("ORDER %02X:",write_order);
			for (int c=0; c < song_channels; ++c)
			{
				tr = read_token(l); l = l + tr;
				unsigned char o = (unsigned char)::strtol(token,NULL,16);
				set_order(write_order,c,o);
				if (debug) MESSAGE(" %02X",o);
			}
			if (debug) MESSAGE("\n");
		}
		else if (!::strcmp("PATTERN", token))
		{
			tr = read_token(l);
			write_pattern = ::strtol(token,NULL,16);
		}
		else if (!::strcmp("ROW", token))
		{
			tr = read_token(l); l = l + tr;
			int write_row = ::strtol(token,NULL,16);

			if (debug) MESSAGE("ROW %02X:%02X", write_pattern, write_row);
			for (int c=0; c < song_channels; ++c)
			{
				Cell cell;

				tr = read_token(l); l = l + tr; // skip :

				tr = read_token(l); l = l + tr;
				if (token[0] == '.') {} // nothing
				else if (token[0] == '-') cell.note = NOTE_CUT;
				else if (token[0] == '=') cell.note = NOTE_OFF;
				else if (::strlen(token) >= 3)
				{
					int octave = ::strtol(token+2,NULL,10);
					token[2] = 0;

					int note = 0;
					for (;note < 12; ++note)
					{
						if (!strcmp(NOTE_NAMES[note], token))
							break;
					}

					if (c == 3) // noise
					{
						token[1] = 0;
						note = ::strtol(token,NULL,16);
					}

					cell.note = NOTE_C0 + (12 * octave) + note;
				}

				tr = read_token(l); l = l + tr;
				if (token[0] != '.') cell.inst = (unsigned char)::strtol(token,NULL,16) + 1;

				tr = read_token(l); l = l + tr;
				if (token[0] != '.') cell.vol  = (unsigned char)::strtol(token,NULL,16) + 1;

				if (debug) MESSAGE(" : %3d %02X %02X", cell.note, cell.inst, cell.vol);

				for (int e=0; e < song_columns[c]; ++e)
				{
					tr = read_token(l); l = l + tr;
					if (token[0] != '.' && token[1] != 0)
					{
						cell.etype[e] = token[0];
						cell.eparam[e] = (unsigned char)::strtol(token+1,NULL,16);
					}
					if (debug) MESSAGE(" %c%02X", cell.etype[e] == 0 ? '.' : cell.etype[e], cell.eparam[e]);
				}

				set_cell(write_pattern,write_row,c,cell);
			}
			if (debug) MESSAGE("\n");
		}
	}
	::fclose(f);
	return 0;
}

int FamiText::get_channels() const
{
	return song_channels;
}

int FamiText::get_frames() const
{
	return song_frames;
}

// playback

void FamiText::play()
{
	play_frame = 0;
	play_row = 0;
	play_row_sub = 0;
	play_speed = song_speed;
	::memset(play_gxx,0,sizeof(play_gxx));
	::memset(play_gxx_hold,0,sizeof(play_gxx_hold));
	::memset(play_read,0,sizeof(play_read));
	play_read_frame = 0;
}

void FamiText::tick(int ticks)
{
	const Cell empty;
	for (int i=0; i<song_channels; ++i)
		play_read[i] = empty;

	for (int t=0; t < ticks; ++t)
	{
		// resolve any mid-row gxx
		for (int c=0; c<song_channels; ++c)
		{
			if (play_gxx[c] == 1)
			{
				merge_cell(play_read[c],play_gxx_hold[c]);
				play_gxx[c] = 0;
			}
			if (play_gxx[c] > 0) --play_gxx[c];
		}

		if (play_row_sub <= 0)
		{
			int pattern_skip = -1;

			// check for speed/pattern effects
			for (int c=0; c<song_channels; ++c)
			{
				int pattern = get_order(play_frame,c);
				Cell cell = get_cell(pattern,play_row,c);
				for (int e=0; e<song_columns[c]; ++e)
				{
					if (cell.etype[e] == 'F') // change speed
						play_speed = cell.eparam[e];
					else if (cell.etype[e] == 'D') // break to next frame
						pattern_skip = play_frame + 1;
					else if (cell.etype[e] == 'B') // jump to specified frame
						pattern_skip = cell.eparam[e];
				}
			}
			play_row_sub = play_speed;

			play_read_frame = play_frame;

			// play each channel
			for (int c=0; c<song_channels; ++c)
			{
				// hanging gxx always plays on first tick of row
				if (play_gxx[c] > 0)
				{
					merge_cell(play_read[c],play_gxx_hold[c]);
					play_gxx[c] = 0;
				}

				int pattern = get_order(play_frame,c);
				Cell cell = get_cell(pattern,play_row,c);

				// check for gxx
				for (int e=0; e<song_columns[c]; ++e)
				{
					if (cell.etype[e] == 'G')
					{
						play_gxx[c] = cell.eparam[c];
						play_gxx_hold[c] = cell;
					}
				}

				if (play_gxx[c] <= 0) // if not delayed, play the note
					merge_cell(play_read[c],cell);
			}

			++play_row;
			if (play_row >= song_pattern_len || pattern_skip != -1)
			{
				++play_frame;
				if (pattern_skip != -1)
					play_frame = pattern_skip;
				play_row = 0;
			}
		}
		--play_row_sub;
	}
}

FamiText::Cell FamiText::read_tick(int channel) const
{
	return play_read[channel];
}

int FamiText::read_frame() const
{
	return play_read_frame;
}

// internal data read/write

void FamiText::set_cell(int pattern, int row, int channel, Cell c)
{
	unsigned int pos = (pattern * song_channels * song_pattern_len) + (row * song_channels) + channel;
	while (pos >= patterns.size()) // expand to fit data
	{
		const Cell empty;
		patterns.push_back(empty);
	}
	patterns[pos] = c;
}

FamiText::Cell FamiText::get_cell(int pattern, int row, int channel) const
{
	unsigned int pos = (pattern * song_channels * song_pattern_len) + (row * song_channels) + channel;
	if (pos < patterns.size())
		return patterns[pos];

	const Cell empty;
	return empty;
}

void FamiText::set_order(int frame, int channel, unsigned char o)
{
	unsigned int pos = (frame * song_channels) + channel;
	while (pos >= order.size()) // expand to fit data
		order.push_back(0);
	order[pos] = o;
}

unsigned char FamiText::get_order(int frame, int channel) const
{
	unsigned int pos = (frame * song_channels) + channel;
	if (pos < order.size())
		return order[pos];
	return 0;
}

// end of file
