/*
  Copyright (C) 2005 Remko Troncon
  										
  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library 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
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include <stdio.h>
#include <portaudio.h>
#include <stdlib.h>

#include "portaudiocard.h"
#include "msossread.h"
#include "msosswrite.h"

// Settings
#define BUFFER_SIZE 2048

// PortAudio settings
#define FRAMES_PER_BUFFER 256


// -----------------------------------------------------------------------------

int readBuffer(char* buffer, char** buffer_read_p, char*buffer_write, char* buffer_end, char* target_buffer, int target_len)
{
	char *end, *tmp, *buffer_read = *buffer_read_p;
	size_t remaining, len;
	int read = 0;
	
	// First phase
	tmp = buffer_read + target_len;
	if (buffer_write < buffer_read) {
		if (tmp > buffer_end) {
			end = buffer_end;
			remaining = tmp - buffer_end;
		}
		else {
			end = tmp;
			remaining = 0;
		}
	}
	else {
		end = (tmp >= buffer_write ? buffer_write : tmp);
		remaining = 0;
	}
	//printf("end: %p\n",end);

	// Copy the data
	len = end - buffer_read;
	memcpy(target_buffer, buffer_read, len);
	buffer_read += len;
	target_buffer += len;
	read += len;

	// Second phase
	if (remaining > 0) {
		buffer_read = buffer;
		tmp = buffer_read + remaining;
		len = (tmp > buffer_write ? buffer_write : tmp) - buffer_read;
		memcpy(target_buffer, buffer_read, len);
		buffer_read += len;
		read += len;
	}

	// Finish up
	*buffer_read_p = buffer_read;

	return read;
}

int writeBuffer(char* buffer, char* buffer_read, char** buffer_write_p, char* buffer_end, char* source_buffer, int source_len)
{
	char *end, *tmp, *buffer_write = *buffer_write_p;
	size_t remaining, len;
	int written = 0;

	// First phase
	tmp = buffer_write + source_len;
	if (buffer_write >= buffer_read) {
		if (tmp > buffer_end) {
			end = buffer_end;
			remaining = tmp - buffer_end;
		}
		else {
			end = tmp;
			remaining = 0;
		}
	}
	else {
		if (tmp > buffer_read) {
			printf("Warning: Dropping frame(s) %p %p\n", tmp, buffer_read);
			end = buffer_read;
			remaining = 0;
		}
		else {
			end = tmp;
			remaining = 0;
		}
	}
	
	len = end - buffer_write;
	memcpy(buffer_write, source_buffer, len);
	buffer_write += len;
	source_buffer += len;
	written += len;
	
	// Second phase
	if (remaining > 0) {
		buffer_write = buffer;
		tmp = buffer_write + remaining;
		if (tmp > buffer_read) {
			printf("Warning: Dropping frame(s) %p %p\n", tmp, buffer_read);
			end = buffer_read;
		}
		else {
			end = tmp;
		}
		
		len = end - buffer_write;
		memcpy(buffer_write, source_buffer, len);
		buffer_write += len;
		written += len;
	}

	// Finish up
	*buffer_write_p = buffer_write;
	return written;
}

// -----------------------------------------------------------------------------

static int portAudioCallback( void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, PaTimestamp outTime, void *card_p )
{
	PortAudioCard* card = (PortAudioCard*) card_p;

	size_t len = framesPerBuffer * Pa_GetSampleSize(paInt16); 
	//printf("PA::readBuffer begin %p %p %p %p %d\n",card->out_buffer,card->out_buffer_read,card->out_buffer_write,card->out_buffer_end, len);
	readBuffer(card->out_buffer,&card->out_buffer_read,card->out_buffer_write,card->out_buffer_end, outputBuffer, len);
	//printf("PA::readBuffer end %p %p %p %p %d\n",card->out_buffer,card->out_buffer_read,card->out_buffer_write,card->out_buffer_end, len);
	writeBuffer(card->in_buffer,card->in_buffer_read,&card->in_buffer_write,card->in_buffer_end, inputBuffer, len);
	return 0;
}

// -----------------------------------------------------------------------------

int portaudio_card_probe(PortAudioCard *obj, int bits, int stereo, int rate)
{
	return FRAMES_PER_BUFFER * (SND_CARD(obj)->stereo ? 2 : 1) * Pa_GetSampleSize(paInt16);
}


int portaudio_card_open_r(PortAudioCard *obj,int bits,int stereo,int rate)
{
	fprintf(stderr,"Opening PortAudio card\n");

	int err;
	err = Pa_OpenDefaultStream(&obj->stream, 1, 1, paInt16, rate, FRAMES_PER_BUFFER, 0, portAudioCallback, obj);
	if (err != paNoError) {
		fprintf(stderr, "Error creating a PortAudio stream: %s\n", Pa_GetErrorText(err));
		return -1;
	}

	err = Pa_StartStream(obj->stream);
	if (err != paNoError) {
		fprintf(stderr, "Error starting PortAudio stream: %s\n", Pa_GetErrorText(err));
		Pa_CloseStream(obj->stream);
		obj->stream = NULL;
		return -1;
	}

	SND_CARD(obj)->bits = 16;
	SND_CARD(obj)->stereo = 0;
	SND_CARD(obj)->rate = rate;
	// Should this be multiplied by Pa_GetMinNumBuffers(FRAMES_PER_BUFFER,sampleRate) ?
	SND_CARD(obj)->bsize = FRAMES_PER_BUFFER * (SND_CARD(obj)->stereo ? 2 : 1) * Pa_GetSampleSize(paInt16);

	return 0;

}

void portaudio_card_close_r(PortAudioCard *obj)
{
	fprintf(stderr, "Closing PortAudio card\n");
	if (obj->stream) {
		Pa_StopStream(obj->stream);
		Pa_CloseStream(obj->stream);
		obj->stream = NULL;
	}
}

int portaudio_card_open_w(PortAudioCard *obj,int bits,int stereo,int rate)
{
}

void portaudio_card_close_w(PortAudioCard *obj)
{
}

void portaudio_card_destroy(PortAudioCard *obj)
{
	snd_card_uninit(SND_CARD(obj));
	free(obj->in_buffer);
	free(obj->out_buffer);
}

gboolean portaudio_card_can_read(PortAudioCard *obj)
{
	return obj->in_buffer_read != obj->in_buffer_write;
}

int portaudio_card_read(PortAudioCard *obj,char *buf,int size)
{
	//printf("read begin %p %p %p %p %d\n",obj->in_buffer,obj->in_buffer_read,obj->in_buffer_write,obj->in_buffer_end, size);
	return readBuffer(obj->in_buffer,&obj->in_buffer_read,obj->in_buffer_write,obj->in_buffer_end, buf, size);
	//printf("read end %p %p %p %p %d\n",obj->in_buffer,obj->in_buffer_read,obj->in_buffer_write,obj->in_buffer_end, size);
}

int portaudio_card_write(PortAudioCard *obj,char *buf,int size)
{
	//printf("writeBuffer begin %p %p %p %p %d\n",obj->out_buffer,obj->out_buffer_read,obj->out_buffer_write,obj->out_buffer_end, size);
	return writeBuffer(obj->out_buffer,obj->out_buffer_read,&obj->out_buffer_write,obj->out_buffer_end, buf, size);
	//printf("writeBuffer end %p %p %p %p %d\n",obj->out_buffer,obj->out_buffer_read,obj->out_buffer_write,obj->out_buffer_end, size);
}

void portaudio_card_set_level(PortAudioCard *obj,gint way,gint a)
{
}

gint portaudio_card_get_level(PortAudioCard *obj,gint way)
{
	return 0;
}

void portaudio_card_set_source(PortAudioCard *obj,int source)
{
}

MSFilter *portaudio_card_create_read_filter(PortAudioCard *card)
{
	MSFilter *f=ms_oss_read_new();
	ms_oss_read_set_device(MS_OSS_READ(f),SND_CARD(card)->index);
	return f;
}

MSFilter *portaudio_card_create_write_filter(PortAudioCard *card)
{
	MSFilter *f=ms_oss_write_new();
	ms_oss_write_set_device(MS_OSS_WRITE(f),SND_CARD(card)->index);
	return f;
}


SndCard* portaudio_card_new()
{
	// Basic stuff
	PortAudioCard* obj= g_new0(PortAudioCard,1);
	SndCard* base= SND_CARD(obj);
	snd_card_init(base);
	base->card_name=g_strdup_printf("PortAudio Card");
	base->_probe=(SndCardOpenFunc)portaudio_card_probe;
	base->_open_r=(SndCardOpenFunc)portaudio_card_open_r;
	base->_open_w=(SndCardOpenFunc)portaudio_card_open_w;
	base->_can_read=(SndCardPollFunc)portaudio_card_can_read;
	base->_read=(SndCardIOFunc)portaudio_card_read;
	base->_write=(SndCardIOFunc)portaudio_card_write;
	base->_close_r=(SndCardCloseFunc)portaudio_card_close_r;
	base->_close_w=(SndCardCloseFunc)portaudio_card_close_w;
	base->_set_rec_source=(SndCardMixerSetRecSourceFunc)portaudio_card_set_source;
	base->_set_level=(SndCardMixerSetLevelFunc)portaudio_card_set_level;
	base->_get_level=(SndCardMixerGetLevelFunc)portaudio_card_get_level;
	base->_destroy=(SndCardDestroyFunc)portaudio_card_destroy;
	base->_create_read_filter=(SndCardCreateFilterFunc)portaudio_card_create_read_filter;
	base->_create_write_filter=(SndCardCreateFilterFunc)portaudio_card_create_write_filter;

	// Initialize stream
	obj->stream = NULL;
	
	// Initialize buffers
	obj->out_buffer = (char*) malloc(sizeof(char)*BUFFER_SIZE);
	obj->out_buffer_read = obj->out_buffer_write = obj->out_buffer;
	obj->out_buffer_end = obj->out_buffer + BUFFER_SIZE;
	obj->in_buffer = (char*) malloc(sizeof(char)*BUFFER_SIZE);
	obj->in_buffer_read = obj->in_buffer_write = obj->in_buffer;
	obj->in_buffer_end = obj->in_buffer + BUFFER_SIZE;

	return base;
}

gint portaudio_card_manager_init(SndCardManager *manager, gint tabindex)
{
	// Initialize portaudio lib
	int err = Pa_Initialize();
	if (err != paNoError) {
		fprintf(stderr,"Error initializing PortAudio: %s\n",Pa_GetErrorText(err));
		return 0;
	}
	
	// Create new card
	manager->cards[0]=portaudio_card_new();
	manager->cards[0]->index=0;
	
	return 1;
}
