/* 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/debug.h"
#include "vgmcoll.h"
#include "vgminstrset.h"
#include "vgmsamp.h"

using namespace std;

double ConvertLogScaleValToAtten(double percent) {
	if (percent == 0)
		return 100.0;  // assume 0 is -100.0db attenuation
	double atten = 20 * log10(percent) * 2;
	return MIN<double>(-atten, 100.0);
}

// Convert a percent of volume value to it's attenuation in decibels.
//  ex: ConvertPercentVolToAttenDB_SF2(0.5) returns -(-6.02db) = half perceived loudness
double ConvertPercentAmplitudeToAttenDB_SF2(double percent) {
	if (percent == 0)
		return 100.0;  // assume 0 is -100.0db attenuation
	double atten = 20 * log10(percent);
	return MIN<double>(-atten, 100.0);
}

void VGMColl::UnpackSampColl(SynthFile &synthfile, VGMSampColl *sampColl,
							 Common::Array<VGMSamp *> &finalSamps) {
	assert(sampColl != nullptr);

	size_t nSamples = sampColl->_samples.size();
	for (size_t i = 0; i < nSamples; i++) {
		VGMSamp *samp = sampColl->_samples[i];

		uint32 bufSize;
		if (samp->_ulUncompressedSize)
			bufSize = samp->_ulUncompressedSize;
		else
			bufSize = (uint32) ceil((double) samp->_dataLength * samp->GetCompressionRatio());

		uint8 *uncompSampBuf =
				new uint8[bufSize];  // create a new memory space for the uncompressed wave
		samp->ConvertToStdWave(uncompSampBuf);  // and uncompress into that space

		uint16 blockAlign = samp->_bps / 8 * samp->_channels;
		SynthWave *wave =
				synthfile.AddWave(1, samp->_channels, samp->_rate, samp->_rate * blockAlign, blockAlign,
								  samp->_bps, bufSize, uncompSampBuf, (samp->_name));
		finalSamps.push_back(samp);

		// If we don't have any loop information, then don't create a sampInfo structure for the
		// Wave
		if (samp->_loop.loopStatus == -1) {
			debug("No loop information for %s - some parameters might be incorrect",
				  samp->_sampName.c_str());
			return;
		}

		SynthSampInfo *sampInfo = wave->AddSampInfo();
		if (samp->_bPSXLoopInfoPrioritizing) {
			if (samp->_loop.loopStart != 0 || samp->_loop.loopLength != 0)
				sampInfo->SetLoopInfo(samp->_loop, samp);
		} else
			sampInfo->SetLoopInfo(samp->_loop, samp);

		double attenuation = (samp->_volume != -1) ? ConvertLogScaleValToAtten(samp->_volume) : 0;
		uint8 unityKey = (samp->_unityKey != -1) ? samp->_unityKey : 0x3C;
		short fineTune = samp->_fineTune;
		sampInfo->SetPitchInfo(unityKey, fineTune, attenuation);
	}
}

SF2File *VGMColl::CreateSF2File(VGMInstrSet *theInstrSet) {
	SynthFile *synthfile = CreateSynthFile(theInstrSet);
	if (!synthfile) {
		debug("SF2 conversion aborted");
		return nullptr;
	}

	SF2File *sf2file = new SF2File(synthfile);
	delete synthfile;
	return sf2file;
}

SynthFile *VGMColl::CreateSynthFile(VGMInstrSet *theInstrSet) {
	Common::Array<VGMInstrSet *> instrsets;
	instrsets.push_back(theInstrSet);
	if (instrsets.empty()) {
		debug("No instruments found.");
		return nullptr;
	}

	/* FIXME: shared_ptr eventually */
	SynthFile *synthfile = new SynthFile("SynthFile");

	Common::Array<VGMSamp *> finalSamps;
	Common::Array<VGMSampColl *> finalSampColls;

	for (uint32 i = 0; i < instrsets.size(); i++) {
		VGMSampColl *instrset_sampcoll = instrsets[i]->_sampColl;
		if (instrset_sampcoll) {
			finalSampColls.push_back(instrset_sampcoll);
			UnpackSampColl(*synthfile, instrset_sampcoll, finalSamps);
		}
	}

	if (finalSamps.empty()) {
		debug("No sample collection present");
		delete synthfile;
		return nullptr;
	}

	for (size_t inst = 0; inst < instrsets.size(); inst++) {
		VGMInstrSet *set = instrsets[inst];
		size_t nInstrs = set->_aInstrs.size();
		for (size_t i = 0; i < nInstrs; i++) {
			VGMInstr *vgminstr = set->_aInstrs[i];
			size_t nRgns = vgminstr->_aRgns.size();
			if (nRgns == 0)  // do not write an instrument if it has no regions
				continue;
			SynthInstr *newInstr = synthfile->AddInstr(vgminstr->_bank, vgminstr->_instrNum);
			for (uint32 j = 0; j < nRgns; j++) {
				VGMRgn *rgn = vgminstr->_aRgns[j];
				//				if (rgn->sampNum+1 > sampColl->samples.size())
				////does thereferenced sample exist? 					continue;

				// Determine the SampColl associated with this rgn.  If there's an explicit pointer
				// to it, use that.
				VGMSampColl *sampColl = rgn->_sampCollPtr;
				if (!sampColl) {
					// If rgn is of an InstrSet with an embedded SampColl, use that SampColl.
					if (((VGMInstrSet *) rgn->_vgmfile)->_sampColl)
						sampColl = ((VGMInstrSet *) rgn->_vgmfile)->_sampColl;

						// If that does not exist, assume the first SampColl
					else
						sampColl = finalSampColls[0];
				}

				// Determine the sample number within the rgn's associated SampColl
				size_t realSampNum = rgn->_sampNum;

				// Determine the sampCollNum (index into our finalSampColls vector)
				size_t sampCollNum = finalSampColls.size();
				for (size_t k = 0; k < finalSampColls.size(); k++) {
					if (finalSampColls[k] == sampColl)
						sampCollNum = k;
				}
				if (sampCollNum == finalSampColls.size()) {
					debug("SampColl does not exist");
					delete synthfile;
					return nullptr;
				}
				//   now we add the number of samples from the preceding SampColls to the value to
				//   get the real sampNum in the final DLS file.
				for (uint32 k = 0; k < sampCollNum; k++)
					realSampNum += finalSampColls[k]->_samples.size();

				SynthRgn *newRgn = newInstr->AddRgn();
				newRgn->SetRanges(rgn->_keyLow, rgn->_keyHigh, rgn->_velLow, rgn->_velHigh);
				newRgn->SetWaveLinkInfo(0, 0, 1, (uint32) realSampNum);

				if (realSampNum >= finalSamps.size()) {
					debug("Sample %u does not exist", (uint32) realSampNum);
					realSampNum = finalSamps.size() - 1;
				}

				VGMSamp *samp = finalSamps[realSampNum];  // sampColl->samples[rgn->sampNum];
				SynthSampInfo *sampInfo = newRgn->AddSampInfo();

				// This is a really loopy way of determining the loop information, pardon the pun.
				// However, it works. There might be a way to simplify this, but I don't want to
				// test out whether another method breaks anything just yet Use the sample's
				// loopStatus to determine if a loop occurs.  If it does, see if the sample provides
				// loop info (gathered during ADPCM > PCM conversion.  If the sample doesn't provide
				// loop offset info, then use the region's loop info.
				if (samp->_bPSXLoopInfoPrioritizing) {
					if (samp->_loop.loopStatus != -1) {
						if (samp->_loop.loopStart != 0 || samp->_loop.loopLength != 0)
							sampInfo->SetLoopInfo(samp->_loop, samp);
						else {
							rgn->_loop.loopStatus = samp->_loop.loopStatus;
							sampInfo->SetLoopInfo(rgn->_loop, samp);
						}
					} else {
						error("_bPSXLoopInfoPrioritizing: Invalid sample loop status");
					}
				}
					// The normal method: First, we check if the rgn has loop info defined.
					// If it doesn't, then use the sample's loop info.
				else if (rgn->_loop.loopStatus == -1) {
					if (samp->_loop.loopStatus != -1)
						sampInfo->SetLoopInfo(samp->_loop, samp);
					else {
						error("Invalid sample loop status");
					}
				} else
					sampInfo->SetLoopInfo(rgn->_loop, samp);

				int8 realUnityKey = -1;
				if (rgn->_unityKey == -1)
					realUnityKey = samp->_unityKey;
				else
					realUnityKey = rgn->_unityKey;
				if (realUnityKey == -1)
					realUnityKey = 0x3C;

				short realFineTune;
				if (rgn->_fineTune == 0)
					realFineTune = samp->_fineTune;
				else
					realFineTune = rgn->_fineTune;

				double attenuation;
				if (rgn->_volume != -1)
					attenuation = ConvertLogScaleValToAtten(rgn->_volume);
				else if (samp->_volume != -1)
					attenuation = ConvertLogScaleValToAtten(samp->_volume);
				else
					attenuation = 0;

				double sustainLevAttenDb;
				if (rgn->_sustain_level == -1)
					sustainLevAttenDb = 0.0;
				else
					sustainLevAttenDb = ConvertPercentAmplitudeToAttenDB_SF2(rgn->_sustain_level);

				SynthArt *newArt = newRgn->AddArt();
				newArt->AddPan(rgn->_pan);
				newArt->AddADSR(rgn->_attack_time, (Transform) rgn->_attack_transform, rgn->_decay_time,
								sustainLevAttenDb, rgn->_sustain_time, rgn->_release_time,
								(Transform) rgn->_release_transform);

				sampInfo->SetPitchInfo(realUnityKey, realFineTune, attenuation);
			}
		}
	}
	return synthfile;
}
