#include "flv-parser.h"
#include "flv-header.h"
#include "flv-proto.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>

#define N_TAG_SIZE			4	// previous tag size
#define FLV_HEADER_SIZE		9	// DataOffset included
#define FLV_TAG_HEADER_SIZE	11	// StreamID included

#define FLV_VIDEO_CODEC_NAME(codecid) (FLV_VIDEO_H264==(codecid) ? FLV_VIDEO_AVCC : (FLV_VIDEO_H265==(codecid) ? FLV_VIDEO_HVCC : (FLV_VIDEO_H266==(codecid) ? FLV_VIDEO_VVCC : FLV_VIDEO_AV1C)))

static int flv_parser_audio(struct flv_audio_tag_header_t* audio, const uint8_t* data, size_t bytes, uint32_t timestamp, flv_parser_handler handler, void* param)
{
	if (FLV_SEQUENCE_HEADER == audio->avpacket)
		return handler(param, FLV_AUDIO_AAC == audio->codecid ? FLV_AUDIO_ASC : FLV_AUDIO_OPUS_HEAD, data, bytes, timestamp, timestamp, 0);
	else
		return handler(param, audio->codecid, data, bytes, timestamp, timestamp, 0);
}

static int flv_parser_video(struct flv_video_tag_header_t* video, const uint8_t* data, size_t bytes, uint32_t timestamp, flv_parser_handler handler, void* param)
{
	if (FLV_VIDEO_H264 == video->codecid || FLV_VIDEO_H265 == video->codecid || FLV_VIDEO_H266 == video->codecid || FLV_VIDEO_AV1 == video->codecid)
	{
		if (FLV_SEQUENCE_HEADER == video->avpacket)
		{
			return handler(param, FLV_VIDEO_CODEC_NAME(video->codecid), data, bytes, timestamp, timestamp, 0);
		}
		else if (FLV_AVPACKET == video->avpacket)
		{
			return handler(param, video->codecid, data, bytes, timestamp + video->cts, timestamp, (FLV_VIDEO_KEY_FRAME == video->keyframe) ? 1 : 0);
		}
		else if (FLV_END_OF_SEQUENCE == video->avpacket)
		{
			return 0; // AVC end of sequence (lower level NALU sequence ender is not required or supported)
		}
		else
		{
			assert(0);
			return -EINVAL;
		}
	}
	else
	{
		// Video frame data
		return handler(param, video->codecid, data, bytes, timestamp, timestamp, (FLV_VIDEO_KEY_FRAME == video->keyframe) ? 1 : 0);
	}
}

// http://www.cnblogs.com/musicfans/archive/2012/11/07/2819291.html
// metadata keyframes/filepositions
static int flv_parser_script(const uint8_t* data, size_t bytes, uint32_t timestamp, flv_parser_handler handler, void* param)
{
	return handler(param, FLV_SCRIPT_METADATA, data, bytes, timestamp, timestamp, 0);
}

int flv_parser_tag(int type, const void* data, size_t bytes, uint32_t timestamp, flv_parser_handler handler, void* param)
{
	int n;
	struct flv_audio_tag_header_t audio;
	struct flv_video_tag_header_t video;

	if (bytes < 1) return -EINVAL;

	switch (type)
	{
	case FLV_TYPE_AUDIO:
		n = flv_audio_tag_header_read(&audio, data, bytes);
		if (n < 0)
			return n;
		return flv_parser_audio(&audio, (const uint8_t*)data + n, (int)bytes - n, timestamp, handler, param);

	case FLV_TYPE_VIDEO:
		n = flv_video_tag_header_read(&video, data, bytes);
		if (n < 0)
			return n;
		return flv_parser_video(&video, (const uint8_t*)data + n, (int)bytes - n, timestamp, handler, param);

	case FLV_TYPE_SCRIPT:
		n = flv_data_tag_header_read(data, bytes);
		if (n < 0)
			return n;
		return flv_parser_script((const uint8_t*)data + n, (int)bytes - n, timestamp, handler, param);

	default:
		assert(0);
		return -1;
	}
}

static size_t flv_parser_append(struct flv_parser_t* parser, const uint8_t* data, size_t bytes, size_t expect)
{
	size_t n;
	if (parser->bytes > expect || expect > sizeof(parser->ptr))
	{
		// invalid status, consume all
		assert(0);
		parser->bytes = expect;
		return bytes;
	}

	n = parser->bytes + bytes >= expect ? expect - parser->bytes : bytes;
	if (n > 0)
	{
		memcpy(parser->ptr + parser->bytes, data, n);
		parser->bytes += n;
	}
	return n;
}

int flv_parser_input(struct flv_parser_t* parser, const uint8_t* data, size_t bytes, flv_parser_handler handler, void* param)
{
    int r;
	size_t n;
	uint8_t codec;
	uint32_t size;
	enum {FLV_HEADER=0, FLV_HEADER_OFFSET, FLV_PREVIOUS_SIZE, FLV_TAG_HEADER, FLV_AVHEADER_CODEC, FLV_AVHEADER_EXTRA, FLV_TAG_BODY};

	for (n = r = 0; bytes > 0 && n >= 0 && 0 == r; data += n, bytes -= n)
	{
		switch (parser->state)
		{
		case FLV_HEADER:
			n = flv_parser_append(parser, data, bytes, FLV_HEADER_SIZE);
			if (FLV_HEADER_SIZE == parser->bytes)
			{
				flv_header_read(&parser->header, parser->ptr, parser->bytes);
				if (parser->header.offset < 9 || parser->header.offset > sizeof(parser->ptr))
					return -1;
				parser->header.offset -= 9;
				parser->state = parser->header.offset > 0 ? FLV_HEADER_OFFSET : FLV_PREVIOUS_SIZE;
				parser->bytes = 0;
			}
			break;

		case FLV_HEADER_OFFSET:
			n = flv_parser_append(parser, data, bytes, parser->header.offset);
			if (parser->header.offset == (uint32_t)parser->bytes)
			{
				parser->bytes = 0;
				parser->state = FLV_PREVIOUS_SIZE;
			}
			break;

		case FLV_PREVIOUS_SIZE:
			n = flv_parser_append(parser, data, bytes, N_TAG_SIZE);
			if (N_TAG_SIZE == parser->bytes)
			{
				flv_tag_size_read(parser->ptr, parser->bytes, &size);
				assert(size == 0 || size == parser->tag.size + FLV_TAG_HEADER_SIZE);
				parser->bytes = 0;
				parser->state = FLV_TAG_HEADER;
			}
			break;

		case FLV_TAG_HEADER:
			n = flv_parser_append(parser, data, bytes, FLV_TAG_HEADER_SIZE);
			if (FLV_TAG_HEADER_SIZE == parser->bytes)
			{
				flv_tag_header_read(&parser->tag, parser->ptr, parser->bytes);
				parser->bytes = 0;
				parser->expect = 0;
				parser->state = FLV_AVHEADER_CODEC;
			}
			break;
			
		case FLV_AVHEADER_CODEC:
			switch (parser->tag.type)
			{
			case FLV_TYPE_AUDIO:
				parser->expect = 1;
				n = flv_parser_append(parser, data, bytes, 1);
				codec = (parser->ptr[0] & 0xF0) /*>> 4*/;
				if (FLV_AUDIO_AAC == codec || FLV_AUDIO_OPUS == codec)
					parser->expect = 2;
				break;

			case FLV_TYPE_VIDEO:
				parser->expect = 1;
				n = flv_parser_append(parser, data, bytes, 1);
				codec = (parser->ptr[0] & 0x0F);
				if (FLV_VIDEO_H264 == codec || FLV_VIDEO_H265 == codec || FLV_VIDEO_H266 == codec || FLV_VIDEO_AV1 == codec)
					parser->expect = 5;
				break;

			case FLV_TYPE_SCRIPT:
				parser->expect = 0;
				n = 0; // noops
				break;

			default:
				assert(0);
				return -1; // invalid flv file
			}
			parser->state = FLV_AVHEADER_EXTRA;
			break;

		case FLV_AVHEADER_EXTRA:
			n = flv_parser_append(parser, data, bytes, parser->expect);
			if (parser->expect == parser->bytes)
			{
				if(FLV_TYPE_AUDIO == parser->tag.type)
					flv_audio_tag_header_read(&parser->audio, parser->ptr, parser->bytes);
				else if(FLV_TYPE_VIDEO == parser->tag.type)
					flv_video_tag_header_read(&parser->video, parser->ptr, parser->bytes);
				parser->bytes = 0;
				parser->state = FLV_TAG_BODY;

				parser->expect = parser->tag.size - parser->expect;
				parser->body = parser->alloc ? parser->alloc(param, parser->expect) : malloc(parser->expect);
				if (!parser->body)
					return -1;
			}
			break;

		case FLV_TAG_BODY:
			assert(parser->body && parser->bytes <= parser->expect);
			n = parser->bytes + bytes >= parser->expect ? parser->expect - parser->bytes : bytes;
			if(n > 0) {
				memmove(parser->body + parser->bytes, data, n);
				parser->bytes += n;
			}

			if (parser->expect == parser->bytes)
			{
				parser->bytes = 0;
				parser->state = FLV_PREVIOUS_SIZE;
				switch (parser->tag.type)
				{
				case FLV_TYPE_AUDIO:
					r = flv_parser_audio(&parser->audio, parser->body, parser->expect, parser->tag.timestamp, handler, param);
					break;

				case FLV_TYPE_VIDEO:
					r = flv_parser_video(&parser->video, parser->body, parser->expect, parser->tag.timestamp, handler, param);
					break;

				case FLV_TYPE_SCRIPT:
					r = flv_parser_script(parser->body, parser->expect, parser->tag.timestamp, handler, param);
					break;

				default:
					assert(0);
					r = -1;
					break;
				}
				
				parser->free ? parser->free(param, parser->body) : free(parser->body);
			}
			break;

		default:
			assert(0);
			return -1;
		}
	}

	return r;
}
