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

#define MIN(x, y) ((x) < (y) ? (x) : (y))

static struct rtmp_packet_t* rtmp_packet_find(struct rtmp_t* rtmp, uint32_t cid)
{
	uint32_t i;
	struct rtmp_packet_t* pkt;

	// The protocol supports up to 65597 streams with IDs 3-65599
	assert(cid <= 65535 + 64 && cid >= 2 /* Protocol Control Messages */);
	for (i = 0; i < N_CHUNK_STREAM; i++)
	{
		pkt = rtmp->in_packets + ((i + cid) % N_CHUNK_STREAM);
		if (pkt->header.cid == cid)
			return pkt;
	}
	return NULL;
}

static struct rtmp_packet_t* rtmp_packet_create(struct rtmp_t* rtmp, uint32_t cid)
{
	uint32_t i;
	struct rtmp_packet_t* pkt;

	// The protocol supports up to 65597 streams with IDs 3-65599
	assert(cid <= 65535 + 64 && cid >= 2 /* Protocol Control Messages */);
	assert(NULL == rtmp_packet_find(rtmp, cid));
	for (i = 0; i < N_CHUNK_STREAM; i++)
	{
		pkt = rtmp->in_packets + ((i + cid) % N_CHUNK_STREAM);
		if (0 == pkt->header.cid)
			return pkt;
	}
	return NULL;
}

static struct rtmp_packet_t* rtmp_packet_parse(struct rtmp_t* rtmp, const uint8_t* buffer)
{
	uint8_t fmt = 0;
	uint32_t cid = 0;
	struct rtmp_packet_t* packet;
	
	// chunk base header
	buffer += rtmp_chunk_basic_header_read(buffer, &fmt, &cid);

	// load previous header
	packet = rtmp_packet_find(rtmp, cid);
	if (NULL == packet)
	{
		if (RTMP_CHUNK_TYPE_0 != fmt && RTMP_CHUNK_TYPE_1 != fmt)
			return NULL; // don't know stream length

		packet = rtmp_packet_create(rtmp, cid);
		if (NULL == packet)
			return NULL;
	}

	// chunk message header
	packet->header.cid = cid;
	packet->header.fmt = fmt;
	rtmp_chunk_message_header_read(buffer, &packet->header);

	return packet;
}

static int rtmp_packet_alloc(struct rtmp_t* rtmp, struct rtmp_packet_t* packet)
{
	void* p;
	(void)rtmp;

	// 24-bytes length
	assert(0 == packet->bytes);
	assert(packet->header.length < (1 << 24));
	// fixed SMS (Chinacache Smart Media Server) packet->header.length = 0
	if (0 == packet->capacity || packet->capacity < packet->header.length)
	{
		p = realloc(packet->payload, packet->header.length + 1024);
		if (NULL == p)
			return -ENOMEM;
		packet->payload = p;
		packet->capacity = packet->header.length + 1024;
	}

	return 0;
}

int rtmp_chunk_read(struct rtmp_t* rtmp, const uint8_t* data, size_t bytes)
{
	const static uint32_t s_header_size[] = { 11, 7, 3, 0 };

	int r, invalid_extended_timestamp;
	size_t size, offset = 0;
	uint8_t extended_timestamp_buffer[4];
	uint32_t extended_timestamp = 0;
	struct rtmp_parser_t* parser = &rtmp->parser;
	struct rtmp_chunk_header_t header;

	while (offset < bytes)
	{
		switch (parser->state)
		{
		case RTMP_PARSE_INIT:
			parser->pkt = NULL;
			parser->bytes = 1;
			parser->buffer[0] = data[offset++];

			if (0 == (parser->buffer[0] & 0x3F))
				parser->basic_bytes = 2;
			else if (1 == (parser->buffer[0] & 0x3F))
				parser->basic_bytes = 3;
			else
				parser->basic_bytes = 1;

			parser->state = RTMP_PARSE_BASIC_HEADER;
			break;

		case RTMP_PARSE_BASIC_HEADER:
			assert(parser->bytes <= parser->basic_bytes);
			while (parser->bytes < parser->basic_bytes && offset < bytes)
			{
				parser->buffer[parser->bytes++] = data[offset++];
			}

			assert(parser->bytes <= parser->basic_bytes);
			if (parser->bytes >= parser->basic_bytes)
			{
				parser->state = RTMP_PARSE_MESSAGE_HEADER;
			}
			break;

		case RTMP_PARSE_MESSAGE_HEADER:
			size = s_header_size[parser->buffer[0] >> 6] + parser->basic_bytes;
			assert(parser->bytes <= size);
			while (parser->bytes < size && offset < bytes)
			{
				parser->buffer[parser->bytes++] = data[offset++];
			}

			assert(parser->bytes <= size);
			if (parser->bytes >= size)
			{
				parser->pkt = rtmp_packet_parse(rtmp, parser->buffer);
				parser->state = RTMP_PARSE_EXTENDED_TIMESTAMP;
			}
			break;

		case RTMP_PARSE_EXTENDED_TIMESTAMP:
			if (NULL == parser->pkt) return -ENOMEM;

			assert(parser->pkt->header.timestamp <= 0xFFFFFF);
			size = s_header_size[parser->pkt->header.fmt] + parser->basic_bytes;
			if (parser->pkt->header.timestamp == 0xFFFFFF) size += 4; // extended timestamp

			assert(parser->bytes <= size);
			while (parser->bytes < size && offset < bytes)
			{
				parser->buffer[parser->bytes++] = data[offset++];
			}

			assert(parser->bytes <= size);
			if (parser->bytes >= size)
			{
				invalid_extended_timestamp = 0;
				extended_timestamp = parser->pkt->header.timestamp;
				if (parser->pkt->header.timestamp == 0xFFFFFF)
				{
					// parse extended timestamp
					rtmp_chunk_extended_timestamp_read(parser->buffer + s_header_size[parser->buffer[0] >> 6] + parser->basic_bytes, &extended_timestamp);
					if (RTMP_CHUNK_TYPE_3 == parser->pkt->header.fmt && extended_timestamp != parser->pkt->delta) 
					{
						extended_timestamp = parser->pkt->delta; // use previous delta

						// fix code offset -= 4 on offset < 4;
						invalid_extended_timestamp = 1;
						memcpy(extended_timestamp_buffer, parser->buffer + s_header_size[parser->buffer[0] >> 6] + parser->basic_bytes, 4);
					}
				}

				// first chunk
				if (0 == parser->pkt->bytes)
				{
					parser->pkt->delta = extended_timestamp;

					// handle timestamp/delta
					if (RTMP_CHUNK_TYPE_0 == parser->pkt->header.fmt)
						parser->pkt->clock = parser->pkt->delta;
					else
						parser->pkt->clock += parser->pkt->delta;

					if (0 != rtmp_packet_alloc(rtmp, parser->pkt))
						return -ENOMEM;
				}
				parser->state = RTMP_PARSE_PAYLOAD;

				// rewind extended_timestamp_buffer
				if (invalid_extended_timestamp)
				{
					r = rtmp_chunk_read(rtmp, extended_timestamp_buffer, 4);
					if (0 != r) return r;
				}	
			}
			break;

		case RTMP_PARSE_PAYLOAD:
			if (NULL == parser->pkt || NULL == parser->pkt->payload 
				|| parser->pkt->bytes > parser->pkt->capacity 
				|| parser->pkt->bytes > parser->pkt->header.length 
				|| parser->pkt->header.length > parser->pkt->capacity)
			{
				assert(0);
				return -ENOMEM;
			}
			//assert(parser->pkt->bytes <= parser->pkt->header.length);
			//assert(parser->pkt->capacity >= parser->pkt->header.length);
			size = MIN(rtmp->in_chunk_size - (parser->pkt->bytes % rtmp->in_chunk_size), parser->pkt->header.length - parser->pkt->bytes);
			size = MIN(size, bytes - offset);
			if(size > 0) memcpy(parser->pkt->payload + parser->pkt->bytes, data + offset, size);
			parser->pkt->bytes += size;
			offset += size;

			if (parser->pkt->bytes >= parser->pkt->header.length)
			{
				assert(parser->pkt->bytes == parser->pkt->header.length);
				parser->state = RTMP_PARSE_INIT; // reset parser state
				parser->pkt->bytes = 0; // clear bytes

				memcpy(&header, &parser->pkt->header, sizeof(header));
				header.timestamp = parser->pkt->clock;
				r = rtmp_handler(rtmp, &header, parser->pkt->payload);
				if(0 != r) return r;
			}
			else if (0 == (parser->pkt->bytes % rtmp->in_chunk_size))
			{
				// next chunk
				parser->state = RTMP_PARSE_INIT;
			}
			else
			{
				// need more data
				assert(offset == bytes);
			}
			break;

		default:
			assert(0);
			break;
		}
	}

	return 0;
}
