//
// Object_Audio
//

#include "ascript/Object_Audio.h"

namespace AScript {

//-----------------------------------------------------------------------------
// Object_Audio
//-----------------------------------------------------------------------------
Object_Audio::~Object_Audio()
{
	FreeBuffer();
}

Object *Object_Audio::Clone() const
{
	return NULL; //new Object_Audio(*this);
}

void Object_Audio::ReferenceBuffer(Object *pObjRef, unsigned char *buff, size_t len)
{
	_memory.Free();
	_pObjRef = pObjRef;
	_buff = buff;
	_len = len;
}

bool Object_Audio::AllocBuffer(Signal sig, size_t len)
{
	FreeBuffer();
	_pObjRef = NULL;
	_buff = reinterpret_cast<unsigned char *>(
					_memory.Allocate(_nChannels * len * GetBytesPerData()));
	if (_buff == 0) {
		sig.SetError(ERR_MemoryError, "failed to allocate audio buffer");
		return false;
	}
	_len = len;
	return true;
}

void Object_Audio::FreeBuffer()
{
	_memory.Free();
	Object::Delete(_pObjRef);
	_pObjRef = NULL;
	_buff = NULL;
	_len = 0;
}

bool Object_Audio::SetSineWave(Signal sig, size_t iChannel,
			size_t pitch, int phase, int amplitude, size_t offset, size_t len)
{
	const double PI2 = 3.14159265358979323846 * 2;
	int amplitudeMax =
		(_format == FORMAT_U8 || _format == FORMAT_S8)? 0x7f :
		(_format == FORMAT_U16LE || _format == FORMAT_S16LE ||
		 _format == FORMAT_U16BE || _format == FORMAT_S16BE)? 0x7fff : 0;
	if (iChannel >= _nChannels) {
		sig.SetError(ERR_ValueError, "channel is out of range");
		return false;
	}
	if (amplitude < 0) {
		amplitude = amplitudeMax;
	} else if (amplitude > amplitudeMax) {
		sig.SetError(ERR_ValueError, "amplitude is out of range");
		return false;
	}
	if (len > _len) len = _len;
	if (offset >= _len) return true;
	if (offset + len > _len) len = _len - offset;
	unsigned char *buffp = _buff + (offset * _nChannels + iChannel) * GetBytesPerData();
	size_t bytesPerUnit = GetBytesPerData() * _nChannels;
	for (size_t i = 0; i < len; i++) {
		int data = static_cast<int>(::sin(PI2 * (i + phase) / pitch) * amplitude);
		StoreData(buffp, data);
		buffp += bytesPerUnit;
	}
	return true;
}

Object_Audio::Format Object_Audio::SymbolToFormat(Signal sig, const Symbol *pSymbol)
{
	if (pSymbol->IsIdentical(AScript_Symbol(u8))) {
		return FORMAT_U8;
	} else if (pSymbol->IsIdentical(AScript_Symbol(s8))) {
		return FORMAT_S8;
	} else if (pSymbol->IsIdentical(AScript_Symbol(u16le))) {
		return FORMAT_U16LE;
	} else if (pSymbol->IsIdentical(AScript_Symbol(s16le))) {
		return FORMAT_S16LE;
	} else if (pSymbol->IsIdentical(AScript_Symbol(u16be))) {
		return FORMAT_U16BE;
	} else if (pSymbol->IsIdentical(AScript_Symbol(s16be))) {
		return FORMAT_S16BE;
	} else {
		sig.SetError(ERR_ValueError, "unsupported audio format: %s", pSymbol->GetName());
		return FORMAT_None;
	}
}

const Symbol *Object_Audio::FormatToSymbol(Format format)
{
	if (format == FORMAT_U8) {
		return AScript_Symbol(u8);
	} else if (format == FORMAT_S8) {
		return AScript_Symbol(s8);
	} else if (format == FORMAT_U16LE) {
		return AScript_Symbol(u16le);
	} else if (format == FORMAT_S16LE) {
		return AScript_Symbol(s16le);
	} else if (format == FORMAT_U16BE) {
		return AScript_Symbol(u16be);
	} else if (format == FORMAT_S16BE) {
		return AScript_Symbol(s16be);
	} else {
		return AScript_Symbol(nil);
	}
}

Value Object_Audio::DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag)
{
	evaluatedFlag = true;
	if (pSymbol->IsIdentical(AScript_Symbol(format))) {
		return Value(FormatToSymbol(_format));
	} else if (pSymbol->IsIdentical(AScript_Symbol(channels))) {
		return Value(static_cast<unsigned int>(_nChannels));
	} else if (pSymbol->IsIdentical(AScript_Symbol(len))) {
		return Value(static_cast<unsigned int>(_len));
	}
	evaluatedFlag = false;
	return Value::Null;
}

Value Object_Audio::DoPropSet(Signal sig, const Symbol *pSymbol,
									const Value &value, bool &evaluatedFlag)
{
	return DoPropGet(sig, pSymbol, evaluatedFlag);
}

String Object_Audio::ToString(Signal sig, bool exprFlag)
{
	String rtn;
	rtn += "<audio:";
	rtn += FormatToSymbol(_format)->GetName();
	rtn += ":";
	if (IsValid()) {
		char buff[32];
		::sprintf(buff, "%d", GetLength());
		rtn += buff;
	} else {
		rtn += "invalid";
	}
	rtn += ":";
	do {
		char buff[32];
		::sprintf(buff, "%dch", _nChannels);
		rtn += buff;
	} while (0);
	rtn += ">";
	return rtn;
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Audio
//-----------------------------------------------------------------------------
// audio#sinewave(channel:number, pitch:number, phase?:number,
//                amplitude?:number, offset?:number, len?:number):reduce
AScript_DeclareMethod(Audio, sinewave)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "channel", VTYPE_Number);
	DeclareArg(env, "pitch", VTYPE_Number);
	DeclareArg(env, "phase", VTYPE_Number, OCCUR_ZeroOrOnce);
	DeclareArg(env, "amplitude", VTYPE_Number, OCCUR_ZeroOrOnce);
	DeclareArg(env, "offset", VTYPE_Number, OCCUR_ZeroOrOnce);
	DeclareArg(env, "len", VTYPE_Number, OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(Audio, sinewave)
{
	Object_Audio *pSelf = Object_Audio::GetSelfObj(args);
	size_t iChannel = args.GetSizeT(0);
	size_t pitch = args.GetSizeT(1);
	int phase = args.IsNumber(2)? args.GetInt(2) : 0;
	int amplitude = args.IsNumber(3)? args.GetInt(3) : -1;
	size_t offset = args.IsNumber(4)? args.GetSizeT(4) : 0;
	size_t len = args.IsNumber(5)? args.GetSizeT(5) : InvalidSize;
	if (!pSelf->SetSineWave(sig, iChannel, pitch, phase, amplitude, offset, len)) {
		return Value::Null;
	}
	//for (size_t i = 0; i < pSelf->GetLength() * pSelf->GetBytesPerData(); i++) {
	//	::printf(" %02x", pSelf->GetBuffer()[i]);
	//}
	return args.GetSelf();
}

// audio#put(channel:number, offset:number, data:number):reduce
AScript_DeclareMethod(Audio, put)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "channel", VTYPE_Number);
	DeclareArg(env, "offset", VTYPE_Number);
	DeclareArg(env, "data", VTYPE_Number);
}

AScript_ImplementMethod(Audio, put)
{
	Object_Audio *pSelf = Object_Audio::GetSelfObj(args);
	return args.GetSelf();
}

// audio#get(channel:number, offset:number)
AScript_DeclareMethod(Audio, get)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "channel", VTYPE_Number);
	DeclareArg(env, "offset", VTYPE_Number);
}

AScript_ImplementMethod(Audio, get)
{
	Object_Audio *pSelf = Object_Audio::GetSelfObj(args);
	return Value::Null;
}

// audio#store(channel:number, offset:number, len:number, src):reduce
AScript_DeclareMethod(Audio, store)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "channel", VTYPE_Number);
	DeclareArg(env, "offset", VTYPE_Number);
	DeclareArg(env, "len", VTYPE_Number);
	DeclareArg(env, "src", VTYPE_Any);
}

AScript_ImplementMethod(Audio, store)
{
	Object_Audio *pSelf = Object_Audio::GetSelfObj(args);
	return args.GetSelf();
}

// audio#extract(channel:number, offset:number, len:number, dst)
AScript_DeclareMethod(Audio, extract)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "channel", VTYPE_Number);
	DeclareArg(env, "offset", VTYPE_Number);
	DeclareArg(env, "len", VTYPE_Number);
	DeclareArg(env, "dst", VTYPE_Any);
}

AScript_ImplementMethod(Audio, extract)
{
	Object_Audio *pSelf = Object_Audio::GetSelfObj(args);
	return Value::Null;
}

// audio#fill(data:number):reduce
AScript_DeclareMethod(Audio, fill)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "data", VTYPE_Number);
}

AScript_ImplementMethod(Audio, fill)
{
	Object_Audio *pSelf = Object_Audio::GetSelfObj(args);
	return args.GetSelf();
}

// audio#fillrange(channel:number, offset:number, len:number, data:number):reduce
AScript_DeclareMethod(Audio, fillrange)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "channel", VTYPE_Number);
	DeclareArg(env, "offset", VTYPE_Number);
	DeclareArg(env, "len", VTYPE_Number);
	DeclareArg(env, "data", VTYPE_Number);
}

AScript_ImplementMethod(Audio, fillrange)
{
	Object_Audio *pSelf = Object_Audio::GetSelfObj(args);
	return args.GetSelf();
}

// assignment
AScript_ImplementClass(Audio)
{
	AScript_AssignMethod(Audio, sinewave);
	AScript_AssignMethod(Audio, put);
	AScript_AssignMethod(Audio, get);
	AScript_AssignMethod(Audio, store);
	AScript_AssignMethod(Audio, extract);
	AScript_AssignMethod(Audio, fill);
	AScript_AssignMethod(Audio, fillrange);
}

Object *Class_Audio::CreateDescendant(Environment &env, Signal sig, Class *pClass)
{
	ERROREND(env, "this function must not be called");
	return NULL;
}

//-----------------------------------------------------------------------------
// AudioStreamer
//-----------------------------------------------------------------------------
AudioStreamer::List *AudioStreamer::_pList = NULL;
void AudioStreamer::Register(AudioStreamer *pAudioStreamer)
{
	if (_pList == NULL) _pList = new List();
	_pList->push_back(pAudioStreamer);
}

AudioStreamer *AudioStreamer::FindResponsible(Signal sig, Stream &stream, const char *audioType)
{
	if (_pList == NULL) return NULL;
	if (audioType != NULL) return FindByAudioType(audioType);
	foreach (List, ppAudioStreamer, *_pList) {
		AudioStreamer *pAudioStreamer = *ppAudioStreamer;
		if (pAudioStreamer->IsResponsible(sig, stream)) return pAudioStreamer;
		if (sig.IsSignalled()) break;
	}
	return NULL;
}

AudioStreamer *AudioStreamer::FindByAudioType(const char *audioType)
{
	if (_pList == NULL) return NULL;
	foreach (List, ppAudioStreamer, *_pList) {
		AudioStreamer *pAudioStreamer = *ppAudioStreamer;
		if (::strcasecmp(pAudioStreamer->GetAudioType(), audioType) == 0) {
			return pAudioStreamer;
		}
	}
	return NULL;
}

}
