/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program 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.
 *
 * This program 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/>.
 *
 */
/*
 * VGMTrans (c) 2002-2019
 * Licensed under the zlib license,
 * refer to the included VGMTrans_LICENSE.txt file
 */

#include "common/scummsys.h"
#include "common/str.h"
#include "audio/soundfont/sf2file.h"
#include "audio/soundfont/synthfile.h"

using namespace std;

// Convert a pan value where 0 = left 0.5 = center and 1 = right to
// 0.1% units where -50% = left 0 = center 50% = right (shared by DLS and SF2)
long ConvertPercentPanTo10thPercentUnits(double percentPan) {
	return round(percentPan * 1000) - 500;
}

double SecondsToTimecents(double secs) {
	return log(secs) / log((double) 2) * 1200;
}

SF2InfoListChunk::SF2InfoListChunk(Common::String name) : LISTChunk("INFO") {
	// Add the child info chunks
	Chunk *ifilCk = new Chunk("ifil");
	ifilCk->_size = sizeof(sfVersionTag);
	ifilCk->_data = new uint8[ifilCk->_size];
	sfVersionTag versionTag;  // soundfont version 2.01
	versionTag.wMajor = 2;
	versionTag.wMinor = 1;
	versionTag.write(ifilCk->_data);
	AddChildChunk(ifilCk);
	AddChildChunk(new SF2StringChunk("isng", "EMU8000"));
	AddChildChunk(new SF2StringChunk("INAM", name));
	AddChildChunk(new SF2StringChunk("ISFT", Common::String("ScummVM")));
}

//  *******
//  SF2File
//  *******

SF2File::SF2File(SynthFile *synthfile) : RiffFile(synthfile->_name, "sfbk") {
	//***********
	// INFO chunk
	//***********
	AddChildChunk(new SF2InfoListChunk(_name));

	// sdta chunk and its child smpl chunk containing all samples
	LISTChunk *sdtaCk = new LISTChunk("sdta");
	Chunk *smplCk = new Chunk("smpl");

	// Concatanate all of the samples together and add the result to the smpl chunk data
	size_t numWaves = synthfile->_vWaves.size();
	smplCk->_size = 0;
	for (size_t i = 0; i < numWaves; i++) {
		SynthWave *wave = synthfile->_vWaves[i];
		wave->ConvertTo16bitSigned();
		smplCk->_size +=
				wave->_dataSize + (46 * 2);  // plus the 46 padding samples required by sf2 spec
	}
	smplCk->_data = new uint8[smplCk->_size];
	uint32 bufPtr = 0;
	for (size_t i = 0; i < numWaves; i++) {
		SynthWave *wave = synthfile->_vWaves[i];

		memcpy(smplCk->_data + bufPtr, wave->_data, wave->_dataSize);
		memset(smplCk->_data + bufPtr + wave->_dataSize, 0, 46 * 2);
		bufPtr += wave->_dataSize + (46 * 2);  // plus the 46 padding samples required by sf2 spec
	}

	sdtaCk->AddChildChunk(smplCk);
	this->AddChildChunk(sdtaCk);

	//***********
	// pdta chunk
	//***********

	LISTChunk *pdtaCk = new LISTChunk("pdta");

	//***********
	// phdr chunk
	//***********
	Chunk *phdrCk = new Chunk("phdr");
	size_t numInstrs = synthfile->_vInstrs.size();
	phdrCk->_size = (uint32) ((numInstrs + 1) * sizeof(sfPresetHeader));
	phdrCk->_data = new uint8[phdrCk->_size];

	for (size_t i = 0; i < numInstrs; i++) {
		SynthInstr *instr = synthfile->_vInstrs[i];

		sfPresetHeader presetHdr;
		memset(&presetHdr, 0, sizeof(sfPresetHeader));
		memcpy(presetHdr.achPresetName, instr->_name.c_str(),
			   MIN((unsigned long) instr->_name.size(), (unsigned long) 20));
		presetHdr.wPreset = (uint16) instr->_ulInstrument;

		// Despite being a 16-bit value, SF2 only supports banks up to 127. Since
		// it's pretty common to have either MSB or LSB be 0, we'll use whatever
		// one is not zero, with preference for MSB.
		uint16 bank16 = (uint16) instr->_ulBank;

		if ((bank16 & 0xFF00) == 0) {
			presetHdr.wBank = bank16 & 0x7F;
		} else {
			presetHdr.wBank = (bank16 >> 8) & 0x7F;
		}
		presetHdr.wPresetBagNdx = (uint16) i;
		presetHdr.dwLibrary = 0;
		presetHdr.dwGenre = 0;
		presetHdr.dwMorphology = 0;

		presetHdr.write(phdrCk->_data + (i * sizeof(sfPresetHeader)));
	}
	//  add terminal sfPresetBag
	sfPresetHeader presetHdr;
	memset(&presetHdr, 0, sizeof(sfPresetHeader));
	presetHdr.wPresetBagNdx = (uint16) numInstrs;
	presetHdr.write(phdrCk->_data + (numInstrs * sizeof(sfPresetHeader)));
	pdtaCk->AddChildChunk(phdrCk);

	//***********
	// pbag chunk
	//***********
	Chunk *pbagCk = new Chunk("pbag");
	const size_t ITEMS_IN_PGEN = 2;
	pbagCk->_size = (uint32) ((numInstrs + 1) * sizeof(sfPresetBag));
	pbagCk->_data = new uint8[pbagCk->_size];
	for (size_t i = 0; i < numInstrs; i++) {
		sfPresetBag presetBag;
		memset(&presetBag, 0, sizeof(sfPresetBag));
		presetBag.wGenNdx = (uint16) (i * ITEMS_IN_PGEN);
		presetBag.wModNdx = 0;

		presetBag.write(pbagCk->_data + (i * sizeof(sfPresetBag)));
	}
	//  add terminal sfPresetBag
	sfPresetBag presetBag;
	memset(&presetBag, 0, sizeof(sfPresetBag));
	presetBag.wGenNdx = (uint16) (numInstrs * ITEMS_IN_PGEN);
	presetBag.write(pbagCk->_data + (numInstrs * sizeof(sfPresetBag)));
	pdtaCk->AddChildChunk(pbagCk);

	//***********
	// pmod chunk
	//***********
	Chunk *pmodCk = new Chunk("pmod");
	//  create the terminal field
	sfModList modList;
	memset(&modList, 0, sizeof(sfModList));
	pmodCk->SetData(&modList, sizeof(sfModList));
	// modList.sfModSrcOper = cc1_Mod;
	// modList.sfModDestOper = startAddrsOffset;
	// modList.modAmount = 0;
	// modList.sfModAmtSrcOper = cc1_Mod;
	// modList.sfModTransOper = linear;
	pdtaCk->AddChildChunk(pmodCk);

	//***********
	// pgen chunk
	//***********
	Chunk *pgenCk = new Chunk("pgen");
	// pgenCk->size = (synthfile->vInstrs.size()+1) * sizeof(sfGenList);
	pgenCk->_size = (uint32) ((synthfile->_vInstrs.size() * sizeof(sfGenList) * ITEMS_IN_PGEN) +
							  sizeof(sfGenList));
	pgenCk->_data = new uint8[pgenCk->_size];
	uint32 dataPtr = 0;
	for (size_t i = 0; i < numInstrs; i++) {
		sfGenList genList;
		memset(&genList, 0, sizeof(sfGenList));

		// reverbEffectsSend
		genList.sfGenOper = reverbEffectsSend;
		genList.genAmount.setShAmount(250);
		genList.write(pgenCk->_data + dataPtr);
		dataPtr += sizeof(sfGenList);

		genList.sfGenOper = instrument;
		genList.genAmount.setwAmount((uint16) i);
		genList.write(pgenCk->_data + dataPtr);
		dataPtr += sizeof(sfGenList);
	}
	//  add terminal sfGenList
	sfGenList genList;
	memset(&genList, 0, sizeof(sfGenList));
	genList.write(pgenCk->_data + dataPtr);

	pdtaCk->AddChildChunk(pgenCk);

	//***********
	// inst chunk
	//***********
	Chunk *instCk = new Chunk("inst");
	instCk->_size = (uint32) ((synthfile->_vInstrs.size() + 1) * sizeof(sfInst));
	instCk->_data = new uint8[instCk->_size];
	size_t rgnCounter = 0;
	for (size_t i = 0; i < numInstrs; i++) {
		SynthInstr *instr = synthfile->_vInstrs[i];

		sfInst inst;
		memset(&inst, 0, sizeof(sfInst));
		memcpy(inst.achInstName, instr->_name.c_str(),
			   MIN((unsigned long) instr->_name.size(), (unsigned long) 20));
		inst.wInstBagNdx = (uint16) rgnCounter;
		rgnCounter += instr->_vRgns.size();

		inst.write(instCk->_data + (i * sizeof(sfInst)));
	}
	//  add terminal sfInst
	sfInst inst;
	memset(&inst, 0, sizeof(sfInst));
	inst.wInstBagNdx = (uint16) rgnCounter;
	inst.write(instCk->_data + (numInstrs * sizeof(sfInst)));
	pdtaCk->AddChildChunk(instCk);

	//***********
	// ibag chunk - stores all zones (regions) for instruments
	//***********
	Chunk *ibagCk = new Chunk("ibag");

	size_t totalNumRgns = 0;
	for (size_t i = 0; i < numInstrs; i++)
		totalNumRgns += synthfile->_vInstrs[i]->_vRgns.size();

	ibagCk->_size = (uint32) ((totalNumRgns + 1) * sizeof(sfInstBag));
	ibagCk->_data = new uint8[ibagCk->_size];

	rgnCounter = 0;
	int instGenCounter = 0;
	for (size_t i = 0; i < numInstrs; i++) {
		SynthInstr *instr = synthfile->_vInstrs[i];

		size_t numRgns = instr->_vRgns.size();
		for (size_t j = 0; j < numRgns; j++) {
			sfInstBag instBag;
			memset(&instBag, 0, sizeof(sfInstBag));
			instBag.wInstGenNdx = instGenCounter;
			instGenCounter += 11;
			instBag.wInstModNdx = 0;

			instBag.write(ibagCk->_data + (rgnCounter++ * sizeof(sfInstBag)));
		}
	}
	//  add terminal sfInstBag
	sfInstBag instBag;
	memset(&instBag, 0, sizeof(sfInstBag));
	instBag.wInstGenNdx = instGenCounter;
	instBag.wInstModNdx = 0;
	instBag.write(ibagCk->_data + (rgnCounter * sizeof(sfInstBag)));
	pdtaCk->AddChildChunk(ibagCk);

	//***********
	// imod chunk
	//***********
	Chunk *imodCk = new Chunk("imod");
	//  create the terminal field
	memset(&modList, 0, sizeof(sfModList));
	imodCk->SetData(&modList, sizeof(sfModList));
	pdtaCk->AddChildChunk(imodCk);

	//***********
	// igen chunk
	//***********
	Chunk *igenCk = new Chunk("igen");
	igenCk->_size = (uint32) ((totalNumRgns * sizeof(sfInstGenList) * 11) + sizeof(sfInstGenList));
	igenCk->_data = new uint8[igenCk->_size];
	dataPtr = 0;
	for (size_t i = 0; i < numInstrs; i++) {
		SynthInstr *instr = synthfile->_vInstrs[i];

		size_t numRgns = instr->_vRgns.size();
		for (size_t j = 0; j < numRgns; j++) {
			SynthRgn *rgn = instr->_vRgns[j];

			sfInstGenList instGenList;
			// Key range - (if exists) this must be the first chunk
			instGenList.sfGenOper = keyRange;
			instGenList.genAmount.setRangeLo((uint8) rgn->_usKeyLow);
			instGenList.genAmount.setRangeHi((uint8) rgn->_usKeyHigh);
			instGenList.write(igenCk->_data + dataPtr);
			dataPtr += sizeof(sfInstGenList);

			if (rgn->_usVelHigh)  // 0 means 'not set', fixes TriAce instruments
			{
				// Velocity range (if exists) this must be the next chunk
				instGenList.sfGenOper = velRange;
				instGenList.genAmount.setRangeLo((uint8) rgn->_usVelLow);
				instGenList.genAmount.setRangeHi((uint8) rgn->_usVelHigh);
				instGenList.write(igenCk->_data + dataPtr);
				dataPtr += sizeof(sfInstGenList);
			}

			// initialAttenuation
			instGenList.sfGenOper = initialAttenuation;
			instGenList.genAmount.setShAmount((int16) (rgn->_sampinfo->_attenuation * 10));
			instGenList.write(igenCk->_data + dataPtr);
			dataPtr += sizeof(sfInstGenList);

			// pan
			instGenList.sfGenOper = pan;
			instGenList.genAmount.setShAmount(
					(int16) ConvertPercentPanTo10thPercentUnits(rgn->_art->_pan));
			instGenList.write(igenCk->_data + dataPtr);
			dataPtr += sizeof(sfInstGenList);

			// sampleModes
			instGenList.sfGenOper = sampleModes;
			instGenList.genAmount.setwAmount(rgn->_sampinfo->_cSampleLoops);
			instGenList.write(igenCk->_data + dataPtr);
			dataPtr += sizeof(sfInstGenList);

			// overridingRootKey
			instGenList.sfGenOper = overridingRootKey;
			instGenList.genAmount.setwAmount(rgn->_sampinfo->_usUnityNote);
			instGenList.write(igenCk->_data + dataPtr);
			dataPtr += sizeof(sfInstGenList);

			// attackVolEnv
			instGenList.sfGenOper = attackVolEnv;
			instGenList.genAmount.setShAmount(
					(rgn->_art->_attack_time == 0)
					? -32768
					: round(SecondsToTimecents(rgn->_art->_attack_time)));
			instGenList.write(igenCk->_data + dataPtr);
			dataPtr += sizeof(sfInstGenList);

			// decayVolEnv
			instGenList.sfGenOper = decayVolEnv;
			instGenList.genAmount.setShAmount(
					(rgn->_art->_decay_time == 0) ? -32768
												  : round(SecondsToTimecents(rgn->_art->_decay_time)));
			instGenList.write(igenCk->_data + dataPtr);
			dataPtr += sizeof(sfInstGenList);

			// sustainVolEnv
			instGenList.sfGenOper = sustainVolEnv;
			if (rgn->_art->_sustain_lev > 100.0)
				rgn->_art->_sustain_lev = 100.0;
			instGenList.genAmount.setShAmount((int16) (rgn->_art->_sustain_lev * 10));
			instGenList.write(igenCk->_data + dataPtr);
			dataPtr += sizeof(sfInstGenList);

			// releaseVolEnv
			instGenList.sfGenOper = releaseVolEnv;
			instGenList.genAmount.setShAmount(
					(rgn->_art->_release_time == 0)
					? -32768
					: round(SecondsToTimecents(rgn->_art->_release_time)));
			instGenList.write(igenCk->_data + dataPtr);
			dataPtr += sizeof(sfInstGenList);

			// reverbEffectsSend
			// instGenList.sfGenOper = reverbEffectsSend;
			// instGenList.genAmount.setShAmount(800);
			// memcpy(pgenCk->data + dataPtr, &instGenList, sizeof(sfInstGenList));
			// dataPtr += sizeof(sfInstGenList);

			// sampleID - this is the terminal chunk
			instGenList.sfGenOper = sampleID;
			instGenList.genAmount.setwAmount((uint16) (rgn->_tableIndex));
			instGenList.write(igenCk->_data + dataPtr);
			dataPtr += sizeof(sfInstGenList);

			// int numConnBlocks = rgn->art->vConnBlocks.size();
			// for (int k = 0; k < numConnBlocks; k++)
			//{
			//	SynthConnectionBlock* connBlock = rgn->art->vConnBlocks[k];
			//	connBlock->
			//}
		}
	}
	//  add terminal sfInstBag
	sfInstGenList instGenList;
	memset(&instGenList, 0, sizeof(sfInstGenList));
	instGenList.write(igenCk->_data + dataPtr);
	// memset(ibagCk->data + (totalNumRgns*sizeof(sfInstBag)), 0, sizeof(sfInstBag));
	// igenCk->SetData(&genList, sizeof(sfGenList));
	pdtaCk->AddChildChunk(igenCk);

	//***********
	// shdr chunk
	//***********
	Chunk *shdrCk = new Chunk("shdr");

	size_t numSamps = synthfile->_vWaves.size();
	shdrCk->_size = (uint32) ((numSamps + 1) * sizeof(sfSample));
	shdrCk->_data = new uint8[shdrCk->_size];

	uint32 sampOffset = 0;
	for (size_t i = 0; i < numSamps; i++) {
		SynthWave *wave = synthfile->_vWaves[i];

		sfSample samp;
		memset(&samp, 0, sizeof(sfSample));
		memcpy(samp.achSampleName, wave->_name.c_str(),
			   MIN((unsigned long) wave->_name.size(), (unsigned long) 20));
		samp.dwStart = sampOffset;
		samp.dwEnd = samp.dwStart + (wave->_dataSize / sizeof(uint16));
		sampOffset = samp.dwEnd + 46;  // plus the 46 padding samples required by sf2 spec

		// Search through all regions for an associated sampInfo structure with this sample
		SynthSampInfo *sampInfo = nullptr;
		for (size_t j = 0; j < numInstrs; j++) {
			SynthInstr *instr = synthfile->_vInstrs[j];

			size_t numRgns = instr->_vRgns.size();
			for (size_t k = 0; k < numRgns; k++) {
				SynthRgn *rgn = instr->_vRgns[k];
				if (rgn->_tableIndex == i && rgn->_sampinfo != nullptr) {
					sampInfo = rgn->_sampinfo;
					break;
				}
			}
			if (sampInfo != nullptr)
				break;
		}
		//  If we didn't find a rgn association, then it should be in the SynthWave structure.
		if (sampInfo == nullptr)
			sampInfo = wave->_sampinfo;
		assert(sampInfo != nullptr);

		samp.dwStartloop = samp.dwStart + sampInfo->_ulLoopStart;
		samp.dwEndloop = samp.dwStartloop + sampInfo->_ulLoopLength;
		samp.dwSampleRate = wave->_dwSamplesPerSec;
		samp.byOriginalKey = (uint8) (sampInfo->_usUnityNote);
		samp.chCorrection = (char) (sampInfo->_sFineTune);
		samp.wSampleLink = 0;
		samp.sfSampleType = monoSample;

		samp.write(shdrCk->_data + (i * sizeof(sfSample)));
	}

	//  add terminal sfSample
	memset(shdrCk->_data + (numSamps * sizeof(sfSample)), 0, sizeof(sfSample));
	pdtaCk->AddChildChunk(shdrCk);

	this->AddChildChunk(pdtaCk);
}

SF2File::~SF2File() {}

const void *SF2File::SaveToMem() {
	uint8 *buf = new uint8[this->GetSize()];
	this->Write(buf);
	return buf;
}
