/* mic.cpp - this file is part of DeSmuME
 *
 * Copyright (C) 2009-2017 DeSmuME Team
 *
 * This file 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, or (at your option)
 * any later version.
 *
 * This file 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; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#if !defined(_WIN32) || defined(__LIBRETRO__)

#include <stdlib.h>
#include "mic.h"
#include "NDSSystem.h"
#include "readwrite.h"
#include "emufile.h"

#define MIC_NULL_SAMPLE_VALUE 0x40
#define MIC_MAX_BUFFER_SAMPLES 320
#define MIC_BUFFER_SIZE (sizeof(u8) * MIC_MAX_BUFFER_SAMPLES)
#define NUM_INTERNAL_NOISE_SAMPLES 32

static u8 *micSampleBuffer = NULL; // Pointer to the internal sample buffer.
static u8 *micReadPosition = NULL; // Pointer to the read position of the internal sample buffer.
static u8 *micWritePosition = NULL; // Pointer to the write position of the internal sample buffer.
static unsigned int micBufferFillCount; // The number of readable samples in the internal sample buffer.

static void Mic_BufferClear(void)
{
	if (micSampleBuffer == NULL) {
		return;
	}

	memset(micSampleBuffer, MIC_NULL_SAMPLE_VALUE, MIC_BUFFER_SIZE);
	micReadPosition = micSampleBuffer;
	micWritePosition = micSampleBuffer;
	micBufferFillCount = 0;
}

BOOL Mic_Init(void)
{
	BOOL result = FALSE;

	u8 *newBuffer = (u8 *)malloc(MIC_BUFFER_SIZE);
	if (newBuffer == NULL) {
		return result;
	}

	micSampleBuffer = newBuffer;
	Mic_BufferClear();
	result = TRUE;

	return result;
}

void Mic_DeInit(void)
{
	free(micSampleBuffer);
	micSampleBuffer = NULL;
}

void Mic_Reset(void)
{
	*micReadPosition = MIC_NULL_SAMPLE_VALUE;
	micWritePosition = micReadPosition;
	micBufferFillCount = 0;
}

static bool Mic_GetActivate(void)
{
	return NDS_getFinalUserInput().mic.micButtonPressed;
}

static bool Mic_IsBufferFull(void)
{
	return (micBufferFillCount >= MIC_MAX_BUFFER_SAMPLES);
}

static bool Mic_IsBufferEmpty(void)
{
	return (micBufferFillCount == 0);
}

static u8 Mic_DefaultBufferRead(void)
{
	u8 theSample = MIC_NULL_SAMPLE_VALUE;

	if (micSampleBuffer == NULL) {
		return theSample;
	}

	theSample = *micReadPosition;

	if (Mic_IsBufferEmpty()) {
		return theSample;
	}

	micReadPosition++;
	micBufferFillCount--;

	// Move the pointer back to start if we reach the end of the memory block.
	if (micReadPosition >= (micSampleBuffer + MIC_BUFFER_SIZE)) {
		micReadPosition = micSampleBuffer;
	}

	return theSample;
}

static void Mic_DefaultBufferWrite(u8 theSample)
{
	if (micSampleBuffer == NULL || Mic_IsBufferFull()) {
		return;
	}

	*micWritePosition = theSample;
	micWritePosition++;
	micBufferFillCount++;

	// Move the pointer back to start if we reach the end of the memory block.
	if (micWritePosition >= (micSampleBuffer + MIC_BUFFER_SIZE)) {
		micWritePosition = micSampleBuffer;
	}
}

static u8 Mic_GenerateInternalNoiseSample(void)
{
	const u8 noiseSample[NUM_INTERNAL_NOISE_SAMPLES] =
	{
		0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0x8E, 0xFF, 
		0xF4, 0xE1, 0xBF, 0x9A, 0x71, 0x58, 0x5B, 0x5F, 0x62, 0xC2, 0x25, 0x05, 0x01, 0x01, 0x01, 0x01
	};
	static unsigned int i = 0;

	if (++i >= NUM_INTERNAL_NOISE_SAMPLES) {
		i = 0;
	}

	return noiseSample[i];
}

static u8 Mic_GenerateWhiteNoiseSample(void)
{
	return (u8)(rand() & 0xFF);
}

static u8 Mic_GenerateNullSample(void)
{
	return MIC_NULL_SAMPLE_VALUE;
}

u8 Mic_ReadSample(void)
{
	// All mic modes other than Physical must have the mic hotkey pressed in order
	// to work.
	if (CommonSettings.micMode == TCommonSettings::Physical) 
        {
		return Mic_DefaultBufferRead();
	}

        if (Mic_GetActivate())
        {
                if (CommonSettings.micMode == TCommonSettings::InternalNoise)
                        return Mic_GenerateInternalNoiseSample();

                if (CommonSettings.micMode == TCommonSettings::Random)
                        return Mic_GenerateWhiteNoiseSample();
        }
        
	return MIC_NULL_SAMPLE_VALUE;
}

void Mic_DoNoise(BOOL noise)
{
	u8 (*generator) (void) = NULL;

	if (micSampleBuffer == NULL) {
		return;
	}

	if (!noise) {
		generator = &Mic_GenerateNullSample;
	} else if (CommonSettings.micMode == TCommonSettings::InternalNoise) {
		generator = &Mic_GenerateInternalNoiseSample;
	} else if (CommonSettings.micMode == TCommonSettings::Random) {
		generator = &Mic_GenerateWhiteNoiseSample;
	}

	if (generator == NULL) {
		return;
	}

	while (micBufferFillCount < MIC_MAX_BUFFER_SAMPLES) {
		Mic_DefaultBufferWrite(generator());
	}
}

void mic_savestate(EMUFILE &os)
{
	os.write_32LE(-1);
}

bool mic_loadstate(EMUFILE &is, int size)
{
	is.fseek(size, SEEK_CUR);
	return TRUE;
}

#endif
