#include "rtmp-internal.h"
#include "amf0.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#if defined(_WIN32) || defined(_WIN64)
#define strcasecmp _stricmp
#endif

struct rtmp_result_t
{
	char code[64]; // NetStream.Play.Start
	char level[8]; // warning/status/error
	char description[256];
};

//static const char* s_rtmp_command_code[] = {
//	"NetConnection.Connect.Success",
//	"NetConnection.Connect.Closed",
//	"NetConnection.Connect.Failed",
//	"NetConnection.Connect.AppShutdown",
//	"NetConnection.Connect.InvalidApp",
//	"NetConnection.Connect.Rejected",
//
//	"NetConnection.Connect.ReconnectRequest" // enhanced rtmp v2
// 
//	"NetStream.Failed",
//	"NetStream.Play.Failed",
//	"NetStream.Play.StreamNotFound",
//	"NetStream.Play.Start",
//	"NetStream.Play.Stop",
//	"NetStream.Play.Complete",
//	"NetStream.Play.PublishNotify",
//	"NetStream.Play.UnpublishNotify",
//	"NetStream.Seek.Notify",
//	"NetStream.Seek.Failed",
//	"NetStream.Pause.Notify",
//	"NetStream.Unpause.Notify",
//	"NetStream.Publish.Start",
//	"NetStream.Publish.BadName",
//	"NetStream.Unpublish.Success",
//	"NetStream.Record.Failed",
//	"NetStream.Record.NoAccess",
//	"NetStream.Record.Start",
//	"NetStream.Record.Stop",
//};

#define AMF_OBJECT_ITEM_VALUE(v, amf_type, amf_name, amf_value, amf_size) { v.type=amf_type; v.name=amf_name; v.value=amf_value; v.size=amf_size; }

// s -> c
static int rtmp_command_onconnect_reply(struct rtmp_result_t* result, const uint8_t* data, uint32_t bytes)
{
	char fmsver[64] = { 0 };
	double capabilities = 0;
	struct amf_object_item_t prop[2];
	struct amf_object_item_t info[3]; 
	struct amf_object_item_t items[2];

	AMF_OBJECT_ITEM_VALUE(prop[0], AMF_STRING, "fmsVer", fmsver, sizeof(fmsver));
	AMF_OBJECT_ITEM_VALUE(prop[1], AMF_NUMBER, "capabilities", &capabilities, sizeof(capabilities));

	AMF_OBJECT_ITEM_VALUE(info[0], AMF_STRING, "code", result->code, sizeof(result->code));
	AMF_OBJECT_ITEM_VALUE(info[1], AMF_STRING, "level", result->level, sizeof(result->level));
	AMF_OBJECT_ITEM_VALUE(info[2], AMF_STRING, "description", result->description, sizeof(result->description));

	AMF_OBJECT_ITEM_VALUE(items[0], AMF_OBJECT, "Properties", prop, sizeof(prop) / sizeof(prop[0]));
	AMF_OBJECT_ITEM_VALUE(items[1], AMF_OBJECT, "Information", info, sizeof(info) / sizeof(info[0]));

	//rtmp->onstatus();
	return amf_read_items(data, data + bytes, items, sizeof(items) / sizeof(items[0])) ? 0 : EINVAL;
}

// s -> c
static int rtmp_command_oncreate_stream_reply(const uint8_t* data, uint32_t bytes, double *stream_id)
{
	struct amf_object_item_t items[2];
	AMF_OBJECT_ITEM_VALUE(items[0], AMF_OBJECT, "command", NULL, 0);
	AMF_OBJECT_ITEM_VALUE(items[1], AMF_NUMBER, "streamId", stream_id, 8);

	//rtmp->onstatus();
	return amf_read_items(data, data + bytes, items, sizeof(items) / sizeof(items[0])) ? 0 : EINVAL;
}

// s -> c
static int rtmp_command_onresult(struct rtmp_t* rtmp, double transaction, const uint8_t* data, uint32_t bytes)
{
	int r;
	double stream_id = 0;
	double duration = 0;
	struct rtmp_result_t result;

	memset(&result, 0, sizeof(result));
	switch ((uint32_t)transaction)
	{
	case RTMP_TRANSACTION_CONNECT:
		// next:
		// 1. releaseStream/FCPublish or serverBW/user control message event buffer time
		// 2. createStream
		// 3. FCSubscribe
		r = rtmp_command_onconnect_reply(&result, data, bytes);
		return 0 == r ? (rtmp->client.onconnect ? rtmp->client.onconnect(rtmp->param) : -1) : r;

	case RTMP_TRANSACTION_CREATE_STREAM:
		// next: 
		// publish 
		// or play/user control message event buffer time
		r = rtmp_command_oncreate_stream_reply(data, bytes, &stream_id);
		return 0 == r ? (rtmp->client.oncreate_stream ? rtmp->client.oncreate_stream(rtmp->param, stream_id) : -1) : r;

	case RTMP_TRANSACTION_GET_STREAM_LENGTH:
		return rtmp_command_oncreate_stream_reply(data, bytes, &duration);

	default:
		return 0;
	}
}

// s -> c
//static int rtmp_command_onerror(struct rtmp_t* rtmp, double transaction, const uint8_t* data, uint32_t bytes)
//{
//	struct rtmp_result_t result;
//	struct amf_object_item_t info[3];
//	struct amf_object_item_t items[2];
//
//	memset(&result, 0, sizeof(result));
//	AMF_OBJECT_ITEM_VALUE(info[0], AMF_STRING, "code", result.code, sizeof(result.code));
//	AMF_OBJECT_ITEM_VALUE(info[1], AMF_STRING, "level", result.level, sizeof(result.level));
//	AMF_OBJECT_ITEM_VALUE(info[2], AMF_STRING, "description", result.description, sizeof(result.description));
//
//	AMF_OBJECT_ITEM_VALUE(items[0], AMF_OBJECT, "command", NULL, 0);
//	AMF_OBJECT_ITEM_VALUE(items[1], AMF_OBJECT, "Information", info, sizeof(info) / sizeof(info[0]));
//
//	if (NULL == amf_read_items(data, data + bytes, items, sizeof(items) / sizeof(items[0])))
//	{
//		return -EINVAL; // format error
//	}
//
//	//rtmp->onerror(rtmp->param, -1, result.code);
//	(void)transaction;
//	(void)rtmp;
//	return -1;
//}

// s -> c
static int rtmp_command_onstatus(struct rtmp_t* rtmp, double transaction, const uint8_t* data, uint32_t bytes)
{
	double code = 0.0;
	char tcurl[256] = { 0 };
	struct rtmp_result_t result;
	struct amf_object_item_t info[5];
	struct amf_object_item_t items[2];
	struct amf_object_item_t redirect[2];

	memset(&result, 0, sizeof(result));
	AMF_OBJECT_ITEM_VALUE(redirect[0], AMF_NUMBER, "code", &code, sizeof(code));
	AMF_OBJECT_ITEM_VALUE(redirect[1], AMF_STRING, "redirect", tcurl, sizeof(tcurl));

	AMF_OBJECT_ITEM_VALUE(info[0], AMF_STRING, "code", result.code, sizeof(result.code));
	AMF_OBJECT_ITEM_VALUE(info[1], AMF_STRING, "level", result.level, sizeof(result.level));
	AMF_OBJECT_ITEM_VALUE(info[2], AMF_STRING, "description", result.description, sizeof(result.description));
	AMF_OBJECT_ITEM_VALUE(info[3], AMF_STRING, "tcUrl", tcurl, sizeof(tcurl)); // enhanced rtmp v2
	AMF_OBJECT_ITEM_VALUE(info[4], AMF_OBJECT, "ex", redirect, sizeof(redirect)); // AMS(adoble media server) serverside redirect

	AMF_OBJECT_ITEM_VALUE(items[0], AMF_OBJECT, "command", NULL, 0); // Command object
	AMF_OBJECT_ITEM_VALUE(items[1], AMF_OBJECT, "information", info, sizeof(info) / sizeof(info[0])); // Information object

	if (NULL == amf_read_items(data, data + bytes, items, sizeof(items) / sizeof(items[0])))
	{
		return -EINVAL;
	}

	assert(0 == strcmp(RTMP_LEVEL_ERROR, result.level)
		|| 0 == strcmp(RTMP_LEVEL_STATUS, result.level)
		|| 0 == strcmp(RTMP_LEVEL_WARNING, result.level)
		|| 0 == strcmp(RTMP_LEVEL_FINISH, result.level));

	if (0 == strcasecmp(result.code, "NetStream.Play.Start") 
		|| 0 == strcasecmp(result.code, "NetStream.Record.Start")
		|| 0 == strcasecmp(result.code, "NetStream.Publish.Start"))
	{
		rtmp->client.onnotify ? rtmp->client.onnotify(rtmp->param, RTMP_NOTIFY_START) : 0;
	}
	else if (0 == strcasecmp(result.code, "NetStream.Seek.Notify"))
	{
		rtmp->client.onnotify ? rtmp->client.onnotify(rtmp->param, RTMP_NOTIFY_SEEK) : 0;
	}
	else if (0 == strcasecmp(result.code, "NetStream.Pause.Notify"))
	{
		rtmp->client.onnotify ? rtmp->client.onnotify(rtmp->param, RTMP_NOTIFY_PAUSE) : 0;
	}
	else if (0 == strcasecmp(result.code, "NetStream.Unpause.Notify"))
	{
		rtmp->client.onnotify ? rtmp->client.onnotify(rtmp->param, RTMP_NOTIFY_START) : 0;
	}
	else if (0 == strcasecmp(result.code, "NetStream.Play.Reset"))
	{
		//rtmp->u.client.onnotify(rtmp->param, RTMP_NOTIFY_RESET);
	}
	else if (0 == strcasecmp(result.code, "NetStream.Play.Stop") 
			|| 0 == strcasecmp(result.code, "NetStream.Record.Stop")
			|| 0 == strcasecmp(result.code, "NetStream.Play.Complete"))
	{
		rtmp->client.onnotify ? rtmp->client.onnotify(rtmp->param, RTMP_NOTIFY_STOP) : 0;
	}
	else if (0 == strcasecmp(result.code, "NetStream.Play.PublishNotify") 
		|| 0 == strcasecmp(result.code, "NetStream.Play.UnpublishNotify"))
	{
	}
	else if (0 == strcasecmp(result.code, "NetConnection.Connect.InvalidApp")
		|| 0 == strcasecmp(result.code, "NetStream.Connect.IllegalApplication") // ksyun cdn: level finish, auth failed
		|| 0 == strcasecmp(result.code, "NetStream.Publish.AlreadyExistStream") // ksyun cdn: level finish, description Already exist stream!
		|| 0 == strcasecmp(result.code, "NetStream.Failed")
		|| 0 == strcasecmp(result.code, "NetStream.Play.Failed")
		|| 0 == strcasecmp(result.code, "NetStream.Play.StreamNotFound"))
	{
		//rtmp->onerror(rtmp->param, -1, result.code);
		return -1;
	}
	else if (0 == strcasecmp(result.code, "NetConnection.Connect.ReconnectRequest") // enhanced rtmp v2
		|| (302 == (int)code && 0 == strcasecmp(result.code, "NetConnection.Connect.Rejected"))) // AMS(adoble media server) server-side redirect
	{
		return rtmp->client.onreconnect ? rtmp->client.onreconnect(rtmp->param, tcurl, result.description) : 0;
	}
	else
	{
		assert(0);
		printf("%s: level: %s, code: %s, description: %s\n", __FUNCTION__, result.level, result.code, result.description);
		return 0;
	}

	(void)transaction;
	return strcmp(RTMP_LEVEL_ERROR, result.level) ? 0 : -1;
}

/*
static int rtmp_command_onbwdone(struct rtmp_t* rtmp, double transaction, const uint8_t* data, uint32_t bytes)
{
	struct amf_object_item_t items[1];
	AMF_OBJECT_ITEM_VALUE(items[0], AMF_OBJECT, "command", NULL, 0);

	return amf_read_items(data, data + bytes, items, sizeof(items) / sizeof(items[0])) ? 0 : -1;
}

static int rtmp_command_onbwcheck(struct rtmp_t* rtmp, double transaction, const uint8_t* data, uint32_t bytes)
{
	struct amf_object_item_t items[1];
	AMF_OBJECT_ITEM_VALUE(items[0], AMF_OBJECT, "command", NULL, 0);

	return amf_read_items(data, data + bytes, items, sizeof(items) / sizeof(items[0])) ? 0 : -1;
}
*/
