#include "PlaybackBase.h"
#include <stdlib.h>
#include <assert.h>
#ifdef __ANDROID__
#include <android/log.h> // TODO: replace with generic logging API

#else
#define ANDROID_LOG_INFO 0
#define ANDROID_LOG_ERROR 1
static void __android_log_print(int, const char *, const char *, ...)
{
}
#endif
PlaybackBase::PlaybackBase()
{	
	wake_flags=0;
	last_wake_flags=0;
	playback_thread=0;
	secondary_parameters=0;
	filename=0;
	player=0;
	queued_seek=0;
	output_service=0;
}

int PlaybackBase::Initialize(nx_uri_t filename, ifc_player *player)
{
	this->player = player;
	this->filename = NXURIRetain(filename);
	return NErr_Success;
}

PlaybackBase::~PlaybackBase()
{
	if (secondary_parameters)
		secondary_parameters->Release();
	if (filename)
		NXURIRelease(filename);
	if (queued_seek)
		free(queued_seek);
	if (playback_thread)
		NXThreadJoin(playback_thread, 0);
}

int PlaybackBase::Playback_Play(svc_output *output, ifc_playback_parameters *secondary_parameters)
{
	if (!playback_thread)
		return 1;

	__android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Play");

	output_service = output;
	threadloop_node_t *apc = thread_loop.GetAPC();
	if (apc)
	{
		this->secondary_parameters = secondary_parameters;
		if (secondary_parameters)
			secondary_parameters->Retain();

		apc->func = APC_Play;
		apc->param1 = this;
		thread_loop.Schedule(apc);
		return NErr_Success;
	}
	else
		return NErr_OutOfMemory;
}

int PlaybackBase::Playback_SeekSeconds(double seconds)
{
	__android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Seek (%f seconds)", seconds);
	Agave_Seek *seek = (Agave_Seek *)malloc(sizeof(Agave_Seek));
	if (seek)
	{
		seek->position_type = AGAVE_PLAYPOSITION_SECONDS;
		seek->position.seconds = seconds;
		threadloop_node_t *apc = thread_loop.GetAPC();
		if (apc)
		{
			apc->func = APC_Seek;
			apc->param1 = this;
			apc->param2 = seek;
			thread_loop.Schedule(apc);
			return NErr_Success;
		}
	}
	free(seek);
	return NErr_OutOfMemory;

}

int PlaybackBase::Playback_Pause()
{
	__android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Pause");
	threadloop_node_t *apc = thread_loop.GetAPC();
	if (apc)
	{
		apc->func = APC_Pause;
		apc->param1 = this;
		thread_loop.Schedule(apc);
		return NErr_Success;
	}
	else
		return NErr_OutOfMemory;
}

int PlaybackBase::Playback_Unpause()
{
	__android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Unpause");
	threadloop_node_t *apc = thread_loop.GetAPC();
	if (apc)
	{
		apc->func = APC_Unpause;
		apc->param1 = this;
		thread_loop.Schedule(apc);
		return NErr_Success;
	}
	else
		return NErr_OutOfMemory;
}

int PlaybackBase::Playback_Stop()
{
	__android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Stop");
	threadloop_node_t *apc = thread_loop.GetAPC();
	if (apc)
	{
		apc->func = APC_Stop;
		apc->param1 = this;
		thread_loop.Schedule(apc);
		return NErr_Success;
	}
	else
		return NErr_OutOfMemory;
}

int PlaybackBase::Playback_Close()
{
	__android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Close");
	threadloop_node_t *apc = thread_loop.GetAPC();
	if (apc)
	{
		apc->func = APC_Close;
		apc->param1 = this;
		thread_loop.Schedule(apc);
		return NErr_Success;
	}
	else
		return NErr_OutOfMemory;
}


int PlaybackBase::FileLockCallback_Interrupt()
{
	__android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Interrupt");
	threadloop_node_t *apc = thread_loop.GetAPC();
	if (apc)
	{
		apc->func = APC_Interrupt;
		apc->param1 = this;
		thread_loop.Schedule(apc);
		return NErr_Success;
	}
	else
		return NErr_OutOfMemory;
}

void PlaybackBase::ToggleFlags(int wake_reason)
{
	switch(wake_reason)
	{
	case WAKE_KILL:
		last_wake_flags ^= WAKE_KILL; /* toggle kill flag */
		break;
	case WAKE_STOP:
		last_wake_flags ^= WAKE_STOP; /* toggle stop flag */
		break;
	case WAKE_PLAY:
		last_wake_flags ^= WAKE_PLAY; /* toggle play flag */
		break;
	case WAKE_PAUSE:
	case WAKE_UNPAUSE:
		last_wake_flags ^= WAKE_PAUSE; /* toggle pause flag */
		break;
	case WAKE_INTERRUPT:
		last_wake_flags ^= WAKE_INTERRUPT; /* toggle interrupt flag */
		break;
	}
}

int PlaybackBase::WakeReason(int mask) const
{
	int reason_awoken = last_wake_flags ^ wake_flags;

	reason_awoken = reason_awoken & mask;

	if (reason_awoken & WAKE_INTERRUPT)
	{
		if (wake_flags & WAKE_INTERRUPT)
			return WAKE_INTERRUPT;
		else
			return WAKE_RESUME;
	}

	if (reason_awoken & WAKE_STOP)
	{
		return WAKE_STOP;
	}

	if (reason_awoken & WAKE_KILL)
	{
		return WAKE_KILL;
	}

	if (reason_awoken & WAKE_PLAY)
	{
		if (wake_flags & WAKE_PLAY)
			return WAKE_PLAY;
		else /* if someone cleared the play flag for whatever reason, just treat it as a 0 */
			return 0; 	
	}

	if (reason_awoken & WAKE_PAUSE)
	{
		if (wake_flags & WAKE_PAUSE)
			return WAKE_PAUSE;
		else
			return WAKE_UNPAUSE;
	}

	return 0; 
}

int PlaybackBase::Wake(int mask)
{
	assert(mask != 0); /* make sure they didn't specify a 0 mask (which would make this function potentially never return */
	assert((mask & WAKE_ALL_MASK) != 0); /* make sure it's a valid mask */

	for (;;)
	{
		int reason_awoken = last_wake_flags ^ wake_flags;
		reason_awoken = reason_awoken & mask;

		if (reason_awoken)
		{
			int ret = WakeReason(mask);
			ToggleFlags(ret); // mark the last-known-state of the wake flags 

			return ret;

		}

		if (((mask & WAKE_PLAY) && !(wake_flags & WAKE_PLAY))/* if we're stopped and they asked to be woken up for play */
			|| ((mask & WAKE_PAUSE) && (wake_flags & WAKE_PAUSE)) /* or waiting to be woken up for unpause */
			|| ((mask & WAKE_INTERRUPT) && (wake_flags & WAKE_INTERRUPT))) /* or waiting to be woken up for resume */
		{
			thread_loop.Step();

			int ret = WakeReason(mask);
			if (ret) /* if ret is !0, it means we gotten woken up for a reason we care about */
			{
				ToggleFlags(ret); // mark the last-known-state of the wake flags 
				return ret;
			}
		}
		else /* no reason to sleep, so just return 0 (no change) */
		{
			return 0;
		}
	}
}

int PlaybackBase::Check(int mask)
{
	assert(mask != 0); /* make sure they didn't specify a 0 mask (which would make this function potentially never return */
	assert((mask & WAKE_ALL_MASK) != 0); /* make sure it's a valid mask */

	int reason_awoken = last_wake_flags ^ wake_flags;
	reason_awoken = reason_awoken & mask;

	int ret = 0;
	if (reason_awoken)
	{
		ret = WakeReason(mask);
		ToggleFlags(ret); // mark the last-known-state of the wake flags 
	}
	return ret;
}


int PlaybackBase::Wait(unsigned int milliseconds, int mask)
{
	int reason_awoken = last_wake_flags ^ wake_flags;
	reason_awoken = reason_awoken & mask;

	if (reason_awoken)
	{
		int ret = WakeReason(mask);
		ToggleFlags(ret); // mark the last-known-state of the wake flags 

		return ret;
	}

	thread_loop.Step(milliseconds);

	int ret = WakeReason(mask);
	ToggleFlags(ret); // mark the last-known-state of the wake flags 
	return ret;
}

int PlaybackBase::Sleep(unsigned int milliseconds, int mask)
{
	int reason_awoken = last_wake_flags ^ wake_flags;
	reason_awoken = reason_awoken & mask;

	if (reason_awoken)
	{
		int ret = WakeReason(mask);
		assert(ret != 0);

		return ret;

	}

	thread_loop.Step(milliseconds);

	int ret = WakeReason(mask);	
	return ret;

}

void PlaybackBase::OnStopPlaying()
{
	// turn off the play flag (also adjust old wake flags so we don't trigger a WAKE_STOP)
	__android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] OnStopPlaying");

	wake_flags &= ~WAKE_PLAY;
	last_wake_flags &= ~WAKE_PLAY;	
}

void PlaybackBase::OnInterrupted()
{
	__android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] OnInterrupted");

	wake_flags &= ~WAKE_INTERRUPT;
	last_wake_flags &= ~WAKE_INTERRUPT;
}

Agave_Seek *PlaybackBase::GetSeek()
{
	Agave_Seek *seek = queued_seek;
	queued_seek=0;
	return seek;
}

void PlaybackBase::FreeSeek(Agave_Seek *seek)
{
	free(seek);
}

bool PlaybackBase::PendingSeek()
{
	return !!queued_seek;
}

void PlaybackBase::APC_Play(void *_playback_base, void *param2, double real_value)
{
	PlaybackBase *playback_base = (PlaybackBase *)_playback_base;
	playback_base->wake_flags |= WAKE_PLAY;
}

void PlaybackBase::APC_Seek(void *_playback_base, void *_seek, double real_value)
{
	PlaybackBase *playback_base = (PlaybackBase *)_playback_base;
	Agave_Seek *seek = (Agave_Seek *)_seek;
	free(playback_base->queued_seek);
	playback_base->queued_seek = seek;

}

void PlaybackBase::APC_Pause(void *_playback_base, void *param2, double real_value)
{
	PlaybackBase *playback_base = (PlaybackBase *)_playback_base;
	playback_base->wake_flags |= WAKE_PAUSE;
}

void PlaybackBase::APC_Unpause(void *_playback_base, void *param2, double real_value)
{
	PlaybackBase *playback_base = (PlaybackBase *)_playback_base;
	playback_base->wake_flags &= ~WAKE_PAUSE;
}

void PlaybackBase::APC_Stop(void *_playback_base, void *param2, double real_value)
{
	PlaybackBase *playback_base = (PlaybackBase *)_playback_base;
	playback_base->wake_flags |= WAKE_STOP;
}

void PlaybackBase::APC_Close(void *_playback_base, void *param2, double real_value)
{
	PlaybackBase *playback_base = (PlaybackBase *)_playback_base;
	playback_base->wake_flags |= WAKE_KILL;
}

void PlaybackBase::APC_Interrupt(void *_playback_base, void *param2, double real_value)
{
	PlaybackBase *playback_base = (PlaybackBase *)_playback_base;
	playback_base->wake_flags |= WAKE_INTERRUPT;
}