#include "aica.h"
#include <algorithm>
#include <string>

#include "GREEN/Snd/snd.h"
#include "dc\sound\sound.h"
#include "dc\sound\sfxmgr.h"
#include "dc\sound\aica_comm.h"

#include "ORANGE/QuickMath/QuickMath.h"

// todo: ewwww
extern CAicaSoundPack *g_AicaSounds;

CAicaBuffer::CAicaBuffer(snd_effect_t *source, uint32_t d)
{
	buffer = source;
	duration = uint32_t((float(d * 2) / 11025.f) * 1000.0f);
}

CAicaBuffer::~CAicaBuffer()
{	
	snd_sfx_unload(buffer);
}

CAicaSound::CAicaSound(uint8_t *source, int32_t size, uint8_t n, uint32_t s)
{        
    aicaPayload = source;
	bufferSize = size;
	duration = uint32_t((float(size * 2) / 11025.f) * 1000.0f);
	numChannels = n;
	sampleRate = s;
}

CAicaSound::~CAicaSound()
{	
	ClearAdPCMBuffers();    
}

void CAicaSound::Reset()
{
	soundStartTime = 0;
	ResetActiveAicaBuffer();
}

void CAicaSound::ResetActiveAicaBuffer()
{	
	active = ms_listAicaBuffers.GetHead();
}

CAicaBuffer* CAicaSound::GetActiveAicaBuffer(int32_t timeStamp)
{	
	if (timeStamp)
	{		
		int8_t pos = int8_t(MATH_Fast_Floorf(rspSlowDiv(float(timeStamp), ADPCM_MAX_PACKET_DURATION)));						
		CAicaBuffer *head = ms_listAicaBuffers.GetHead();
		if (head)
		{
			active = head;
			if (pos)
			{
				for (int index = 0; index < pos; index++)
				{
					active = ms_listAicaBuffers.GetNext();				
				}			
			}
		}		
	}
		
	return active;
}

bool CAicaSound::UpdateActiveAicaBuffer(int32_t currTime, int32_t offset)
{	
	if (active)
	{
		uint32_t nextSegmentStart = activeSegmentStartTime + offset + active->duration;
		if (currTime >= nextSegmentStart)
		{	
			active = ms_listAicaBuffers.GetNext();			
			if (active)
			{
				activeSegmentStartTime = currTime;
				return true;
			}
			else
			{
				return false;
			}			
		}
	}	
	return false;
}


int8_t CAicaSound::LoadAdPCMBuffers()
{	   	
	uint32_t remainingBytes = bufferSize;
	uint32_t remainingData = 0;
	uint32_t offset = 0;
	
	aicaBufferCount = ceil(float(bufferSize)/ADPCM_MAX_PACKET_SIZE);
			
	for (int index = 0; index < aicaBufferCount; index++)
	{		
    	snd_effect_t *t;
		uint32_t buffSize;
		uint16_t segmentSize = 0;
		uint8_t *startPos = nullptr;
		uint8_t *endPos = nullptr;
    	  
    	t = malloc(sizeof(snd_effect_t));
    	memset(t, 0, sizeof(snd_effect_t));
    	     	    	
		if (remainingBytes >= ADPCM_MAX_PACKET_SIZE)
		{		
			segmentSize = ADPCM_MAX_PACKET_SIZE;
		}
		else
		{
			segmentSize = remainingBytes;
		}
				  		
		startPos = aicaPayload + offset;
		endPos = aicaPayload + offset + segmentSize;				
		buffSize = endPos - startPos;
	
    	// Mono PCM/ADPCM
		t->rate = sampleRate;
    	t->stereo = numChannels;
    	t->len = buffSize * 2; // 4-bit samples    	
		t->fmt = 3;// AICA_SM_ADPCM;
		t->locr = 0;
    	t->stereo = 0;		

    	t->locl = snd_mem_malloc(buffSize);
    	if(t->locl)
			spu_memload(t->locl, (void*)startPos, buffSize);

    	if(t != SFXHND_INVALID) 
		{						
			CAicaBuffer *a = new CAicaBuffer(t, segmentSize);			
			ms_listAicaBuffers.Add(a);					
		}
		        		
		offset += segmentSize;
		remainingBytes -= segmentSize;	
	}	   

	active = ms_listAicaBuffers.GetHead();

	return 1;
}

// todo:
int8_t CAicaSound::ClearAdPCMBuffers()
{		
	CAicaBuffer *buffer = ms_listAicaBuffers.GetHead();	    
	if (buffer != NULL)
    {
        while (buffer != NULL)
		{						
			ms_listAicaBuffers.Remove(buffer);
			delete buffer;			            
		    buffer = ms_listAicaBuffers.GetNext();
		}	
	}
}

void CAicaSoundPack::PollActiveSounds()
{                    	
    // Get head of list of active channels.
    RSnd* psound = ms_listActive.GetHead();    
    if (psound != NULL)
    {
        while (psound != NULL)
		{					
			float scale = rspFastDiv(float(psound->m_sTypeVolume), 229.f);
			int16_t volume = int16_t(float(psound->m_sChannelVolume) * scale);
            if (psound->PlayCallAica(volume) == -1)
            {  			   		
               ms_listActive.Remove(psound);               
            }
		    psound	= ms_listActive.GetNext();
		}	
	}
	
	if (lastUpdate)
	{
		checkDiff = rspGetMilliseconds() - lastUpdate;
	}

	lastUpdate = rspGetMilliseconds();
}

uint8_t CAicaSoundPack::Add(RSnd* sound)
{        
    ms_listActive.Add(sound);    
    return 0;       
}

uint8_t CAicaSoundPack::Delete(RSnd* sound)
{        
    ms_listActive.Remove(sound); 
    return 0;       
}

// todo?
CAicaSound* CAicaSoundPack::Get(const string & index)
{	
	
}

// todo?
CAicaSoundPack::~CAicaSoundPack()
{
    
}

// todo?
CAicaSoundPack::CAicaSoundPack()
{            
    
}