/*
 * 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 "MelodyFile.h"

#include "melody.h"

UInt32 MelodyFile::currentOffset = 0;
UInt32 CompositeMelody::currentOffset = 0;
std::map<std::string, MelodyFile*> MelodyFile::fragments;


MelodyFile::MelodyFile() {
	wav = NULL;
	mel = NULL;
}


MelodyFile::~MelodyFile() {
	if ( wav != NULL )
		delete wav;
	if ( mel != NULL )
		delete mel;
}


// загружает мелодию из файла
// выводит информацию на экран
bool MelodyFile::Open(const char *filename) {
	fileName = StrLower(filename);
	std::string ext = ExtractFileExt(fileName);
	if ( ext == "wav" ) {
		type = Wav;
		wav = new WavFile();
		if ( !wav->Open(fileName.c_str()) ) {
			printf("FATAL ERROR! Can't load file: %s\n", fileName.c_str());
			printf("\t%s\n", wav->GetErrorStr().c_str());
			return false;
		}
	} else if ( ext == "mel" ) {
		type = Mel;
		TrFileStream f;
		if ( !f.Open(fileName.c_str(), TrFileStream::EOpenRead) ) {
			printf("FATAL ERROR! can't open melody file: %s\n", fileName.c_str());
			return false;
		}
		
		// читаем файл мелодии
		while ( !f.Eof() ) {
			char s[0xfff];
			f.ReadLine(s, 0xffe);
			melody += s;
			melody += " ";
		}
	} else {
		printf("FATAL ERROR! invalid file type: %s\n", filename);
		return false;
	}
	return true;
}


// компилирует фрагмент
// выводит ошибки и предупреждения
bool MelodyFile::Compile() {
	switch ( type ) {
		case Wav:
			if ( wav->GetChannels() != 1 ) {
				printf("ERROR! no mono sound in %s\n", fileName.c_str());
				return false;
			}
			if ( wav->GetBitsPerSample() != 8 ) {
				printf("ERROR! invalid bitrate (%i) in sound %s\n", wav->GetBitsPerSample(), fileName.c_str());
				return false;
			}
			break;

		case Mel:
			mel = new TrMemStream((UInt32)(0), 2);
			int pos = Melody::CompileMelody(melody, *mel, Melody::FormatMotorolaC3xx);
			if ( pos >= 0) {
				printf("ERROR in melody file %s in position: %i\n", fileName.c_str(), pos+1);
				return false;
			};
			break;
	}
	offset = currentOffset;
	currentOffset += GetSize();
	return true;
}


// сохраняет скомпилированный фрагмент в поток
bool MelodyFile::Write(TrStream &out) {
/*
int avg = 0;
int cnt = 0;
int max = 0;
int min = 0;
*/
	switch ( type ) {
		case Wav:
			out.WriteByte(1);
			out.WriteDword((UInt32)wav->GetData().GetSize());
			out.WriteWord(wav->GetSampleRate());
			wav->GetData().Seek(0);
			out << wav->GetData();
/*
wav->GetData().Seek(0);
printf("\n\n\n\n");
for (int i = 0; i < wav->GetData().GetSize(); i++) {
	unsigned int c = wav->GetData().ReadByte();
	if ( c <= 0x80 )  {
		cnt++;
		int cc = 0x80 - c;
		printf("%i\t", cc);
		if ( cnt % 32 == 0 )
			printf("\n");
		if ( cc > max )
			max = cc;
		if ( cc < min )
			min = cc;
		avg += cc;
	}
}
printf("%s   min = %i    max = %i   average = %i\n", fileName.c_str(), min, max, avg/cnt);
*/
			break;

		case Mel:
			out.WriteByte(2);
			out.WriteWord((UInt32)mel->GetSize()/2);
			mel->Seek(0);
			out << *mel;
			break;
	}
	return true;
}


// возвращает размер бинарности для этой мелодии
UInt32 MelodyFile::GetSize() {
	switch ( type ) {
		case Wav:
			return 1 + 4 + 2 + (UInt32)wav->GetData().GetSize();

		case Mel:
			return 1 + 2 + (UInt32)mel->GetSize();
	}
	return 0;
}




// возвращает фрагмент мелодии по имени файла. если фрагмент получается впервые, то
// загружает файл, иначе - возвращает уже загруженный
MelodyFile* MelodyFile::Get(const char *filename) {
	std::string fn = StrLower(filename);
	if ( fragments.find(fn) != fragments.end() )
		return fragments[fn];
	else {
		MelodyFile *result = new MelodyFile();
		fragments[fn] = result;
		if ( result->Open(filename) )
			return result;
		else
			return NULL;
	}
}


// компилирует все мелодии
bool MelodyFile::CompileAll() {
	for ( std::map<std::string, MelodyFile*>::const_iterator it = fragments.begin(); it != fragments.end(); it++ )
		if ( !it->second->Compile() )
			return false;
	return true;
}


// выводит все фрагменты в поток
bool MelodyFile::WriteAll(TrStream &out) {
	for ( std::map<std::string, MelodyFile*>::const_iterator it = fragments.begin(); it != fragments.end(); it++ )
		if ( !it->second->Write(out) )
			return false;
	return true;
}

// выводит информацию всех мелодииях на экран
void MelodyFile::PrintAll() {
	for ( std::map<std::string, MelodyFile*>::const_iterator it = fragments.begin(); it != fragments.end(); it++ )
		it->second->PrintInfo();
}


// возвращает смещение фрагмента относительно начала блока фрагментов
UInt32 MelodyFile::GetOffset() {
	return offset;
}


// выводит информацию о мелодии на экран
void MelodyFile::PrintInfo() {
	switch ( type ) {
		case Wav:
			printf("FILE: %s\t[%s]\n", fileName.c_str(), wav->GetType().c_str());
			printf("\tchannels: %i,  durability: %.2f sec,  sample rate: %i Hz,  samples: %i,  bps: %i bytes/sec,  bitrate: %i\n", wav->GetChannels(), wav->GetDurability(), wav->GetSampleRate(), wav->GetTotalSamples(), wav->GetBytesPerSec(), wav->GetBitsPerSample());
			break;

		case Mel:
			printf("FILE: %s\t[melody]\n", fileName.c_str());
			printf("\tnotes: %i\n", mel->GetSize()/2);
			break;
	}
}





CompositeMelody::CompositeMelody() {
}


CompositeMelody::~CompositeMelody() {
}


// загружает из строки, содержащей список файов-фрагментов
bool CompositeMelody::Load(std::string &s) {
	int cnt = WordCount(s, ",");
	for ( int i = 1; i <= cnt; i++ ) {
		std::string fn = Trim(ExtractWord(i, s, ","));
		MelodyFile *mf = MelodyFile::Get(fn.c_str());
		if ( mf == NULL )
			return false;
		fileNames.push_back(fn);
	}
	offset = currentOffset;
	currentOffset += GetSize();
	return true;
}


// сохраняет мелодию в поток
bool CompositeMelody::Write(TrStream &out, UInt32 fragmentsOffset) {
	for ( UInt32 i = 0; i < GetCount(); i++ )
		out.WriteDword(fragmentsOffset + Get(i)->GetOffset());
	out.WriteDword(0);
	return true;
}