/*
    opcodes.c
    Copyright (C) 2025 Richard Knight

    This program 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 3 of the License, or (at your option) any later version.

    This program 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 program; if not, write to the Free Software Foundation,
    Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
*/
#include <plugin.h>
#include "handling.h"
#include "shout.h"
#include "lame/lame.h"
#include "limits.h"

#define ARGT static constexpr char const*
#define ARGO ARGT otypes
#define ARGI ARGT itypes

char* shout_strerror(csnd::Csound* csound, int val) {
	const char* output;
	switch (val) {
		case SHOUTERR_INSANE:
			output = "bad parameters";
			break;
		case SHOUTERR_MALLOC:
			output = "could not allocate memory";
			break;
		case SHOUTERR_NOCONNECT:
			output = "could not establish connection";
			break;
		case SHOUTERR_NOLOGIN:
			output = "login refused";
			break;
		case SHOUTERR_SOCKET:
			output = "socket error";
			break;
		case SHOUTERR_METADATA:
			output = "metadata update error";
			break;
		case SHOUTERR_CONNECTED:
			output = "connected, but disconnected state required";
			break;
		case SHOUTERR_UNCONNECTED:
			output = "disconnected, but connected state required";
			break;
		case SHOUTERR_UNSUPPORTED:
			output = "unsupported operation";
			break;
	}
	return csound->strdup((char*) output);
}

const char* handleName = "shout";

struct ShoutSession {
	shout_t* shout;
	shout_metadata_t* meta;
	bool open;
};

struct shoutinit : csnd::Plugin<1, 5> {
	ARGO = "i";
	ARGI = "SiSSS";

	int init() {
		ShoutSession* session = (ShoutSession*) csound->malloc(sizeof(ShoutSession));
		outargs[0] = createHandle<ShoutSession>(csound, &session, handleName);
		STRINGDAT &host = inargs.str_data(0);
		int port = (int) inargs[1];
		STRINGDAT &user = inargs.str_data(2);
		STRINGDAT &password = inargs.str_data(3);
		STRINGDAT &mount = inargs.str_data(4);
	
		if (!(session->shout = shout_new())) {
			return csound->init_error("Could not allocate shout");
		}

		if (shout_set_host(session->shout, host.data) != SHOUTERR_SUCCESS) {
			return csound->init_error("Could not set host");
		}

		if (shout_set_protocol(session->shout, SHOUT_PROTOCOL_HTTP) != SHOUTERR_SUCCESS) {
			return csound->init_error("Could not set protocol");
		}

		if (shout_set_port(session->shout, port) != SHOUTERR_SUCCESS) {
			return csound->init_error("Could not set port");
		}

		if (strcmp(password.data, "") != 0 && shout_set_password(session->shout, password.data) != SHOUTERR_SUCCESS) {
			return csound->init_error("Could not set password");
		}

		if (strcmp(mount.data, "") != 0 && shout_set_mount(session->shout, mount.data) != SHOUTERR_SUCCESS) {
			return csound->init_error("Could not set mount");
		}

		if (strcmp(user.data, "") != 0 && shout_set_user(session->shout, user.data) != SHOUTERR_SUCCESS) {
			return csound->init_error("Could not set user");
		}
		
		if (shout_set_format(session->shout, SHOUT_FORMAT_MP3) != SHOUTERR_SUCCESS) {
			return csound->init_error("Could not set format");
		}

		session->meta = NULL;
		session->open = false;
		return OK;
	}
};

void close_session(ShoutSession* session) {
	if (session->open) {
		shout_close(session->shout);
		session->open = false;
	}
	if (session->meta != NULL) {
		shout_metadata_free(session->meta);
		session->meta = NULL;
	}
}

struct shoutopen : csnd::InPlug<1> {
	ARGO = "";
	ARGI = "i";
	ShoutSession* session;
	int init() {
		csound->plugin_deinit(this);
		if (!(session = getHandle<ShoutSession>(csound, args[0], handleName))) {
			return csound->init_error("Invalid shout session handle");
		}
		int res = shout_open(session->shout);
		if (res != SHOUTERR_SUCCESS) {
			shout_close(session->shout);
			char* err = csound->strdup((char*) shout_get_error(session->shout));
			return csound->init_error(err);
		}
		session->meta = NULL;
		session->open = true;
		return OK;
	}

	int deinit() {
		close_session(session);
		return OK;
	}
};

struct shoutclose : csnd::InPlug<1> {
	ARGO = "";
	ARGI = "i";
	int init() {
		ShoutSession* session;
		if (!(session = getHandle<ShoutSession>(csound, args[0], handleName))) {
			return csound->init_error("Invalid shout session handle");
		}
		close_session(session);
		return OK;
	}
};

int setdata(csnd::Csound* csound, MYFLT id, int type, char* value) {
	ShoutSession* session;
	if (!(session = getHandle<ShoutSession>(csound, id, handleName))) {
		return csound->init_error("Invalid shout session handle");
	}
	shout_t* s = session->shout;
	int res;
	switch (type) {
		case 0:
			res = shout_set_name(s, value);
			break;
		case 1:
			res = shout_set_public(s, !strcmp(value, "public"));
			break;
		case 2:
			res = shout_set_url(s, value);
			break;
		case 3:
			res = shout_set_genre(s, value);
			break;
		case 4:
			res = shout_set_description(s, value);
			break;
	}
	if (res != SHOUTERR_SUCCESS) {
		return csound->init_error((char*) shout_get_error(session->shout));
	} else {
		return OK;
	}
}

#define SHOUTSET(settype)\
	ARGO = "";\
	ARGI = "iS";\
	int init() {\
		STRINGDAT &value = args.str_data(1);\
		return setdata(csound, args[0], settype, value.data);\
	}\


struct shoutsetname : csnd::InPlug<2> {
	SHOUTSET(0)
};

struct shoutseturl : csnd::InPlug<2> {
	SHOUTSET(2);
};

struct shoutsetgenre : csnd::InPlug<2> {
	SHOUTSET(3);
};

struct shoutsetdescription : csnd::InPlug<2> {
	SHOUTSET(4);
};

struct shoutsetpublic : csnd::InPlug<2> {
	ARGO = "";
	ARGI = "ii";
	int init() {
		return setdata(csound, args[0], 1, (char*) ((args[1] == 0) ? "private" : "public"));
	}
};

struct shoutsetmeta : csnd::InPlug<3> {
	ARGO = "";
	ARGI = "iSS";
	int init() {
		ShoutSession* session;
		if (!(session = getHandle<ShoutSession>(csound, args[0], handleName))) {
			return csound->init_error("Invalid shout session handle");
		}
		if (!session->open) {
			return csound->init_error("Connection is not open");
		}

		if (session->meta == NULL) {
			if (!(session->meta = shout_metadata_new())) {
				return csound->init_error("Could not allocate metadata");
			} else {
				shout_set_metadata(session->shout, session->meta);
			}
		}

		STRINGDAT &key = args.str_data(1);
		STRINGDAT &value = args.str_data(2);

		int mres = shout_metadata_add(session->meta, key.data, value.data);
		return OK;
	}
};

/*
class SendThread : public csnd::Thread { // in progress...
	MYFLT* bufferL;
	MYFLT* bufferR;
	ShoutSession* session;
	lame_global_flags* lame;
	int wpos;
	
	SendThread(csnd::Csound *csound, ShoutSession* shoutSession, lame_global_flags* lameFlags) : 
		Thread(csound), session(shoutSession), lame(lameFlags) { 
		wpos = 0;
	};
	
	uintptr_t run() {
		if (inbufferPos >= inbufferSize) {
			outSize += lame_encode_buffer_int(lame, inbufferL, inbufferR, inbufferSize, outbuffer, outMaxSize);
			//std::cout << shout_delay(session->shout) << "\n";
			shout_sync(session->shout);
			shout_send(session->shout, outbuffer, outSize);
			inbufferPos = 0;
			outSize = 0;
		}

	}

	void send() {

	}

	int addbuffer(MYFLT* left, MYFLT* right, int size) {
		memcpy(bufferL + wpos, left, size);
		memcpy(bufferR + wpos, right, size);
		wpos += size;
	}
}
*/

struct shoutsend : csnd::InPlug<4> {
	ARGO = "";
	ARGI = "iaaj";
	ShoutSession* session;
	lame_global_flags* lame;
	unsigned char* outbuffer;
	int* inbufferL;
	int* inbufferR;
	int inbufferPos;
	int inbufferSize;
	int outSize;
	int outMaxSize;

	int init() {
		if (!(session = getHandle<ShoutSession>(csound, args[0], handleName))) {
			return csound->init_error("Invalid shout session handle");
		}
		lame = lame_init();
		if (!lame) {
			return csound->init_error("Could not setup LAME");
		}
		csound->plugin_deinit(this);
		lame_set_num_channels(lame, 2);
		lame_set_in_samplerate(lame, csound->sr());
		lame_set_quality(lame, (args[3] == FL(-1)) ? 4 : (int) args[3]);
		lame_set_out_samplerate(lame, csound->sr());
		lame_init_params(lame);
		shout_set_audio_info(session->shout, SHOUT_AI_BITRATE, "128");
		outSize = 0;
		inbufferPos = 0;
		inbufferSize = 2048; // ksmps derived?
		outMaxSize = inbufferSize; // * 2; //8192;
		outbuffer = (unsigned char*) csound->malloc(sizeof(unsigned char) * outMaxSize);
		inbufferL = (int*) csound->malloc(sizeof(int) * inbufferSize);
		inbufferR = (int*) csound->malloc(sizeof(int) * inbufferSize);
		return OK;
	}

	int deinit() {
		lame_close(lame);
		return OK;
	}

	int aperf() {
		for (int i = 0; i < nsmps; i ++) {
			inbufferL[inbufferPos] = (int) (args(1)[i] * INT_MAX);
			inbufferR[inbufferPos ++] = (int) (args(2)[i] * INT_MAX);
			if (inbufferPos >= inbufferSize) {
				outSize += lame_encode_buffer_int(lame, inbufferL, inbufferR, inbufferSize, outbuffer, outMaxSize);
				//std::cout << shout_delay(session->shout) << "\n";
				shout_send(session->shout, outbuffer, outSize);
				inbufferPos = 0;
				outSize = 0;
			}
		}
		return OK;
	}
};

#include <modload.h>
void csnd::on_load(csnd::Csound *csound) {
	csnd::plugin<shoutinit>(csound, "shoutinit", csnd::thread::i);
	csnd::plugin<shoutopen>(csound, "shoutopen", csnd::thread::i);
	csnd::plugin<shoutclose>(csound, "shoutclose", csnd::thread::i);
	csnd::plugin<shoutsend>(csound, "shoutsend", csnd::thread::ia);
	csnd::plugin<shoutsetname>(csound, "shoutsetname", csnd::thread::i);
	csnd::plugin<shoutseturl>(csound, "shoutseturl", csnd::thread::i);
	csnd::plugin<shoutsetgenre>(csound, "shoutsetgenre", csnd::thread::i);
	csnd::plugin<shoutsetdescription>(csound, "shoutsetdescription", csnd::thread::i);
	csnd::plugin<shoutsetpublic>(csound, "shoutsetpublic", csnd::thread::i);
	csnd::plugin<shoutsetmeta>(csound, "shoutsetmeta", csnd::thread::i);
}