/*
 * Copyright (c) 2009 by Oleg Trifonov <otrifonow@gmail.com>
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of either the GNU General Public License version 2
 * or the GNU Lesser General Public License version 2.1, both as
 * published by the Free Software Foundation.
 */

#include "trRS232.h"
#include "trStrUtils.h"

#include <iostream>
#include <string>
#include <vector>

#include "wav.h"
#include "melody.h"
#include "MelodyFile.h"

using namespace std;

char *SIGNATURE = "1488";				// 4х-байтовая сигнатура начала данных на карте
const char *DATA_FILE_NAME = "data.bin";	// имя файла данных на карте

#define RS232_BAUDRATE	115200

#define CMD_INIT					1				// иннициализация карты памяти
#define CMD_UPLOAD				2				// загрузка мелодий на карту
#define CMD_DEBUG					3				// отладка звонка
#define CMD_PRINT_MELODY		4				// скомпилировать мелодию и вывести ее данные на экран

#define MELODY_WAV				1				// фрагмент мелодии - аудио файл
#define MELODY_NOTES				2				// фрагмент мелодии - набор нот

// параметры запуска
Byte com_port_number = 0;			// номер порта RS232 для загрузки прошивки
char memcard_drive_letter = ' ';	// буква диска карты памяти
UInt32 cluster_size = 4096;		// размер кластера (байт)
int command = 0;						// команда, которую надо выполнить
char filename[0xff];					// имя исходного файла

UInt32 mainFileSize;					// размерглавного файла на диске
UInt32 fullDataSize;					// доступное число байт (mainFileSize-8)

/*
struct TMelodyFragment {
	int type;							// MELODY_xxx
	string fileName;					// имя файла мелодии
	WavFile wav;						// данные о wav-файле
	UInt32 offset;						// смещение в файле прошивки относительно начала данных (т.е., после сигнатуры и размера)
};

vector<TMelodyFragment*> melodies;			// данные о мелодиях
*/
vector<CompositeMelody*> melodies;

void usage() {
	printf("MemoryFlasher. Version 1.0. Coded by Oleg Trifonov, 2009.\n");
	printf("usage: mf command [options]\n");
	printf("Commands:\n");
	printf("  i   init memory card\n");
	printf("  u   upload melodies to memory card\n");
	printf("  d   debug device\n");
	printf("  m   compile melody and print code array\n");
	printf("Options:\n");
	printf("  -d [drive]     set memory card drive letter\n");
	printf("  -c [number]    set com-port number\n");
	printf("  -f [filename]  set source file name\n");
	printf("  -s [size]      set size of cluster in bytes (default: %i)\n", cluster_size);
}


bool parseCommandStr(int argc, char **argv) {
	filename[0] = 0;
	if ( argc < 2 ) {
		usage();
		return false;
	}
	printf("\n");
	if ( strcmp(argv[1], "i") == 0 )
		command = CMD_INIT;
	else if ( strcmp(argv[1], "u") == 0 )
		command = CMD_UPLOAD;
	else if ( strcmp(argv[1], "d") == 0 )
		command = CMD_DEBUG;
	else if ( strcmp(argv[1], "m") == 0 )
		command = CMD_PRINT_MELODY;
	else {
		printf("Invalid command: %s\n", argv[1]);
		return false;
	}

	int arg_i = 2;
	while ( arg_i < argc ) {
		char *arg = argv[arg_i++];
		if ( strcmp(arg, "-d") == 0 ) {
			if ( arg_i == argc || strlen(argv[arg_i]) > 1 ) {
				printf("invalid parametr: -d\n");
				return false;
			}
			memcard_drive_letter = argv[arg_i++][0];
		} else if ( strcmp(arg, "-c") == 0 ) {
			if ( arg_i == argc || strlen(argv[arg_i]) > 1 ) {
				printf("invalid parametr: -c\n");
				return false;
			}
			com_port_number = argv[arg_i++][0] - '0';
			if ( com_port_number < 1 || com_port_number > 9 ) {
				printf("invalid com-port number: %i\n", com_port_number);
				return false;
			}
		} else if ( strcmp(arg, "-f") == 0 ) {
			if ( arg_i == argc ) {
				printf("invalid parametr: -f\n");
				return false;
			}
			strcpy_s(filename, argv[arg_i++]);
		} else if ( strcmp(arg, "-s") == 0 ) {
			if ( arg_i == argc ) {
				printf("invalid parametr: -s\n");
				return false;
			}
			if ( !VerifyInt(argv[arg_i+1]) ) {
				printf("invalid number: -s %s\n", argv[arg_i+1]);
				return false;
			}
			cluster_size = StrToInt(argv[arg_i++]);
			if ( cluster_size <= 0 || (cluster_size % 512 != 0) ) {
				printf("invalid size of cluster: %i\n", cluster_size);
				return false;
			}			
		} else {
			printf("invalid option: %s\n", arg);
			return false;
		}
	} // while

	// проверяем, что для указанной команды заданы все аргументы
	if ( command == CMD_INIT || command == CMD_UPLOAD ) {
		if ( memcard_drive_letter == ' ' ) {
			printf("-d [drive] option expected\n");
			return false;
		}
	}
	if ( command == CMD_UPLOAD || command == CMD_PRINT_MELODY ) {
		if ( filename[0] == 0 ) {
			printf("-f [filename] option expected\n");
			return false;
		}
	}
	if ( command == CMD_DEBUG ) {
		if ( com_port_number == 0 ) {
			printf("-c [number] option expected\n");
			return false;
		}
	}
	return true;	
}


// получить количество свободных байт на диске (-1 в случае ошибки)
long getDiskFree(char *disk) {
	DWORD sectorPerCluster, bytesPerSector, numberOfFreeClusters, totalNumberOfClusters;

	if ( !GetDiskFreeSpaceA(disk, &sectorPerCluster, &bytesPerSector, &numberOfFreeClusters, &totalNumberOfClusters) )
		return -1;

	return sectorPerCluster*bytesPerSector*numberOfFreeClusters;
}


// получить количество свободных байт на карте 
long getCardFree() {
	char driveStr[3];

	sprintf_s(driveStr, "%c:", memcard_drive_letter);
	return getDiskFree(driveStr);
}


// возвращает полное имя файла данных на карте памяти
string getFullMainFileName() {
	char fileName[0xff];
	sprintf_s(fileName, "%c:\\%s", memcard_drive_letter, DATA_FILE_NAME);
	return string(fileName);
}

// Выполняет иннициализацию карты. Форматирует ее и создает файл с маркером начала и
// размером оставшегося свободного пространства
bool initMMC() {
	char formatStr[0xff];	

	sprintf_s(formatStr, "format %c: /FS:FAT /A:%i", memcard_drive_letter, cluster_size);
	if ( getCardFree() < 0 ) {
		printf("FATAL ERROR! drive not found %c:\n", memcard_drive_letter);
		return false;
	}
	if ( system(formatStr) != 0 ) {
		printf("FATAL ERROR! can't format drive\n");
		return false;
	}

	
	TrFileStream f;
	if ( !f.Open(getFullMainFileName().c_str(), TrFileStream::ECreateWrite) ) {
		printf("FATAL ERROR! can't format drive\n");
		return false;
	}
	mainFileSize = getCardFree();
	fullDataSize = mainFileSize - 8;
	// пишем сигнатуру
	f.Write(SIGNATURE, 4);
	// пишем размер блока данных
	f.WriteDword(fullDataSize);
	printf("Write file: [");
	
	int indicatorPos = 0;

	char *clusterData = new char[cluster_size];
	memset(clusterData, 0, cluster_size);
	// пишем первй кластер, в нем будет на 8 байт меньше
	f.Write(clusterData, cluster_size-8);
	int clustersCount = mainFileSize/cluster_size;

	for (long cluster = 1; cluster < clustersCount; cluster++) {
		f.Write(clusterData, cluster_size);
		if ( cluster > indicatorPos * clustersCount / 60 ) {
			indicatorPos++;
			printf(".");
		}
	}
	f.Flush();
	printf("]\n");
	delete[] clusterData;
	
	
	return true;
}


// читает файл конфигурации мелодий, загружает все упомянутые в нем звуковые файлы и 
// заполняет массив melodies
bool readMelodyFile() {
	TrFileStream f;
	if ( !f.Open(filename, TrFileStream::EOpenRead) ) {
		printf("FATAL ERROR! can't open melody file: %s\n", filename);
		return false;
	}

	// читаем файл списка и грузим мелодии
	while ( !f.Eof() ) {
		char s[0xfff];
		f.ReadLine(s, 0xffe);
		string str = Trim(s);
		if ( str.length() == 0 || str[0] == '#' )
			continue;
		CompositeMelody *m = new CompositeMelody();
		if ( !m->Load(str) )
			return false;
		melodies.push_back(m);
	} // while (!EOF)
	
	// компилируем мелодии
	MelodyFile::CompileAll();
	
	// выводим информацию
	MelodyFile::PrintAll();

	return true;
}



// выполняет операцию загрузки мелодии на карту памяти
bool upload() {
	if ( !readMelodyFile() )
		return false;
	
	// открываем файл данных, проверяем сигнатуру и читаем размер
	TrFileStream f;
	if ( !f.Open(getFullMainFileName().c_str(), TrFileStream::EOpenReadWrite) ) {
		printf("FATAL ERROR! can't open data file: %s\n", getFullMainFileName().c_str());
		return false;
	}
	mainFileSize = (long)f.GetSize();
	char sign[5];
	sign[4] = 0;
	f.Read(sign, 4);
	for (UInt32 i = 0; i < 4; i++)
		if ( sign[i] != SIGNATURE[i] ) {
			printf("ERROR! invalid datafile signature: %s\n", sign);
			return false;
		}
	UInt32 v = 0;
	f.Read(&v, 4);
	fullDataSize = v;
	if ( fullDataSize != mainFileSize - 8 ) {
		printf("ERROR! invalid datablock size: filesize = %i, blocksize=%i\n", mainFileSize, fullDataSize);
		return false;
	}

	UInt32 melodiesBlockSize = 2 + melodies.size()*4;
	UInt32 length = melodiesBlockSize + CompositeMelody::GetFullSize() + MelodyFile::GetTotalSize();
	// если длина слишком большая, сообщаем об ошибке и выходим
	if ( length > fullDataSize ) {
		printf("ERROR! data to big, no enough space in memory card to save\n");
		return false;
	}

	// сохраняем таблицу мелодий
	f.Seek(8);
	f.WriteWord(melodies.size());
	for ( UInt32 i = 0; i < melodies.size(); i++ )
		f.WriteDword(melodiesBlockSize + melodies[i]->GetOffset());

	// сохраняем мелодии
	for ( UInt32 i = 0; i < melodies.size(); i++ ) 
		melodies[i]->Write(f, melodiesBlockSize + CompositeMelody::GetFullSize());

	// сохраняем фрагменты
	if ( !MelodyFile::WriteAll(f) )
		return false;

	printf("DONE!\n");
	printf("Used: %i bytes (%.1f %%)\n", length, 100.0*length/fullDataSize);

	f.Flush();

	return true;
}


bool debugDevice() {
	TrRS232 com;
	
	if ( !com.Open(com_port_number, RS232_BAUDRATE) )
		return false;

	int quitIndex = 0;
	char *quitStr = "QUIT\n";
	while ( true ) {
		char ch;
		if ( com.Read(&ch, 1) == 1) {			
				cout << ch;
			if ( ch == quitStr[quitIndex] ) {
				quitIndex++;
				if ( quitIndex == sizeof(quitStr) )
					break;
			} else {
				quitIndex = 0;
			}
				
		}
	}
	return true;
}


// компилирует мелодию и выводит на экран массив данных
bool compileAndPrintMelody() {
	TrFileStream f;
	if ( !f.Open(filename, TrFileStream::EOpenRead) ) {
		printf("FATAL ERROR! can't open melody file: %s\n", filename);
		return false;
	}
	string melody;

	// читаем файл мелодии
	while ( !f.Eof() ) {
		char s[0xfff];
		f.ReadLine(s, 0xffe);
		melody += s;
		melody += " ";
	}

	TrMemStream stream((UInt32)(0), 2);
	int pos = Melody::CompileMelody(melody, stream, Melody::FormatMotorolaC3xx);
	if ( pos >= 0) {
		printf("error in melody file (%s) in pos: %i\n", filename, pos+1);
		return false;
	};
	Melody::PrintCompiledMelody(stream);

	return true;
}


int main(int argc, char **argv) {
	if ( !parseCommandStr(argc, argv) )
		return 1;

	switch ( command ) {
		case CMD_INIT:
			return initMMC() ? 0 : 2;			

		case CMD_UPLOAD:
			return upload() ? 0 : 2;

		case CMD_DEBUG:			
			return debugDevice() ? 0 : 2;

		case CMD_PRINT_MELODY:
			return compileAndPrintMelody() ? 0 : 2;
	}	
}