/*
Copyright (C) 2005 The Pentagram team
Copyright (C) 2010-2022 The Exult Team

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 2
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, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include "pent_include.h"
#include "VocAudioSample.h"
#include <new>

#define	TRAILING_VOC_SLOP 32
#define	LEADING_VOC_SLOP 32

#ifndef min
using std::min;
#endif
#ifndef max
using std::max;
#endif

namespace Pentagram {

VocAudioSample::VocAudioSample(std::unique_ptr<uint8[]> buffer_, uint32 size_)
	: AudioSample(std::move(buffer_), size_)
{
	bool	last_chunk=false;

	size_t	data_offset=0x1a;
	size_t  l = 0;
	int		compression = 0;
	int		adpcm_reference = -1;
	//int		adpcm_scale = 0;

	// Parse the file, get the 'important' details
	sample_rate = 0;
	frame_size = 0;
	length = 0;
	while (!last_chunk && data_offset < size_)
	{
		int chunk_length;
		switch(buffer[data_offset++])
		{
		case 0:
			COUT("Terminator");
			last_chunk = true;
			continue;

		case 1:
			COUT("Sound data");
			l = (buffer[2+data_offset])<<16;
			l |= (buffer[1+data_offset])<<8;
			l |= (buffer[0+data_offset]);
			data_offset+=3;
			COUT("Chunk length appears to be " << l);
			if (!sample_rate)
			{
				sample_rate=1000000/(256-(buffer[data_offset]));
#ifdef FUDGE_SAMPLE_RATES
				if (sample_rate == 11111) sample_rate = 11025;
				else if (sample_rate == 22222) sample_rate = 22050;
#endif
				COUT("Sample rate ("<< sample_rate<<") = _real_rate");
			}
			compression = buffer[1+data_offset];
			COUT("compression type " << compression);
			if (compression) {
				adpcm_reference = -1;
			}

			l -= 2;
			chunk_length = l;
			data_offset += 2;

			break;
		case 2:
			COUT("Sound continues");
			l=(buffer[2+data_offset])<<16;
			l|=(buffer[1+data_offset])<<8;
			l|=(buffer[0+data_offset]);
			data_offset+=3;
			COUT("Chunk length appears to be " << l);
			chunk_length = l;
			break;
		case 3:
			COUT("Silence");
			l=(buffer[1+data_offset])<<8;
			l|=(buffer[0+data_offset]);
			l++;
			if (!sample_rate)
			{
				sample_rate=1000000/(256-(buffer[2+data_offset]));
#ifdef FUDGE_SAMPLE_RATES
				if (sample_rate == 11111) sample_rate = 11025;
				else if (sample_rate == 22222) sample_rate = 22050;
#endif
				COUT("Sample rate ("<< sample_rate<<") = _real_rate");
			}
			data_offset+=3;
			chunk_length=0;
			break;

		default:
			COUT("Other chunk type");
			l=(buffer[2+data_offset])<<16;
			l|=(buffer[1+data_offset])<<8;
			l|=(buffer[0+data_offset]);
			data_offset+=3;
			COUT("Chunk length appears to be " << l);
			chunk_length = l;
			l = 0;
			break;
		}

		if(chunk_length==0)
			break;

		if (l)
		{
			size_t dec_len = l;

			// Decompress data
			if (compression == 1) {
				if (adpcm_reference == -1) dec_len = (dec_len-1)*2;
				else dec_len *= 2;
				adpcm_reference = 0;
			}
			else if (compression != 0) {
				CERR("Can't handle VOC compression type"); 
			}

			if (dec_len > static_cast<size_t>(frame_size)) frame_size = dec_len;
			length += dec_len;
		}

		data_offset += chunk_length;
	}

	// Limit frame size to only 512
	if (frame_size > 512) frame_size = 512;

	bits = 8;
	stereo = false;
	decompressor_size = sizeof(VocDecompData);
	decompressor_align = alignof(VocDecompData);
	length = size_;
}

void VocAudioSample::initDecompressor(void *DecompData) const
{
	auto *decomp = new (DecompData) VocDecompData;
	decomp->pos = 0x1a;
	decomp->compression = 0;
	decomp->adpcm_reference = -1;
	decomp->adpcm_scale = 0;
	decomp->chunk_remain = 0;
	decomp->cur_type = 0;
}

void VocAudioSample::freeDecompressor(void *DecompData) const
{
	auto *decomp = static_cast<VocDecompData *>(DecompData);
	decomp->~VocDecompData();
}

//
// Decode 4bit ADPCM vocs (thunder in SI intro)
//
// Code grabbed from VDMS
//

inline int VocAudioSample::decode_ADPCM_4_sample(uint8 sample,
	int& reference,
	int& scale)
{
	static int scaleMap[8] = { -2, -1, 0, 0, 1, 1, 1, 1 };

	if (sample & 0x08) {
		reference = max(0x00, reference - ((sample & 0x07) << scale));
	} else {
		reference = min(0xff, reference + ((sample & 0x07) << scale));
	}

	scale = max(2, min(6, scaleMap[sample & 0x07]));

	return reference;
}

//
// Performs 4-bit ADPCM decoding in-place.
//
void VocAudioSample::decode_ADPCM_4(uint8* inBuf,	
	int bufSize,				// Size of inbuf
	uint8* outBuf,			// Size is 2x bufsize
	int& reference,			// ADPCM reference value
	int& scale)
{
	if (reference < 0) {
		reference = inBuf[0] & 0xff;   // use the first byte in the buffer as the reference byte
		bufSize--;                     // remember to skip the reference byte
		inBuf++;
	}

	for (int i = 0; i < bufSize; i++) {
		outBuf[i * 2 + 0] = decode_ADPCM_4_sample(inBuf[i] >> 4, reference, scale);
		outBuf[i * 2 + 1] = decode_ADPCM_4_sample(inBuf[i] >> 0, reference, scale);
	}
}

bool VocAudioSample::advanceChunk(void *DecompData) const
{
	auto *decomp = static_cast<VocDecompData *>(DecompData);

	if (decomp->pos == buffer_size) return false;

	size_t  l = 0;
	size_t chunk_length = 0;

	// Look at the chunk type
	switch (buffer[decomp->pos++])
	{
	case 0:
		return false;

	case 1: // Sound data
		l = (buffer[2+decomp->pos])<<16;
		l |= (buffer[1+decomp->pos])<<8;
		l |= (buffer[0+decomp->pos]);
		decomp->pos += 3;
		decomp->compression = buffer[1+decomp->pos];

		if (decomp->compression) decomp->adpcm_reference = -1;

		l -= 2;
		chunk_length = l;
		decomp->pos += 2;
		break;

	case 2: // Sound continue
		l=(buffer[2+decomp->pos])<<16;
		l|=(buffer[1+decomp->pos])<<8;
		l|=(buffer[0+decomp->pos]);
		decomp->pos += 3;
		chunk_length = l;
		break;

	case 3:	// Silence
		l  =(buffer[1+decomp->pos])<<8;
		l |=(buffer[0+decomp->pos]);
		l++;
		decomp->pos += 3;
		chunk_length=0;
		break;

		// Skip all other chunk types
	default:
		l=(buffer[2+decomp->pos])<<16;
		l|=(buffer[1+decomp->pos])<<8;
		l|=(buffer[0+decomp->pos]);
		decomp->pos += 3 + l;
		return advanceChunk(decomp); 
	};

	if (!chunk_length)
	{
		decomp->cur_type = 1;
		decomp->chunk_remain = l;
	}
	else
	{
		decomp->cur_type = 0;
		decomp->chunk_remain = chunk_length;
	}

	return true;
}

uint32 VocAudioSample::decompressFrame(void *DecompData, void *samples) const
{
	auto *decomp = static_cast<VocDecompData *>(DecompData);

	// At end of stream??
	if (!decomp->chunk_remain && !advanceChunk(decomp)) return 0; 

	int num_samples = decomp->chunk_remain;

	if (decomp->cur_type == 0 && decomp->compression == 1)
	{
		if (decomp->adpcm_reference == -1) num_samples = (num_samples-1)*2;
		else num_samples *= 2;		
	}

	// Limit number of samples produced
	if (num_samples > frame_size) num_samples = frame_size;

	int bytes_used = 0;

	// This is just silence
	if (decomp->cur_type == 1)
	{
		bytes_used = 0;		
		std::memset(samples,0,num_samples);
	}
	else if (decomp->compression == 0)
	{
		bytes_used = num_samples;
		std::memcpy(samples, buffer.get()+decomp->pos, num_samples);
	}
	else if (decomp->compression == 1)
	{
		bytes_used = num_samples/2;
		if (decomp->adpcm_reference == -1) bytes_used++;
		decode_ADPCM_4(buffer.get()+decomp->pos, bytes_used, static_cast<uint8*>(samples), decomp->adpcm_reference, decomp->adpcm_scale);
	}
	else
	{
		bytes_used = num_samples;
		// Unhandled chunk types set to silence
		std::memset(samples,0,num_samples);
	}

	decomp->pos += bytes_used;
	decomp->chunk_remain -= bytes_used;
	return num_samples;
}


bool VocAudioSample::isThis(IDataSource *ds)
{
	char buffer[19];
	ds->read(buffer,19);

	return strncmp(buffer,"Creative Voice File",19) == 0;
}


}
