#include "demuxer.h"
#include "read.h"
#include "avi_reader.h"

static int GetStreamNumber(uint32_t id)
{
	char *stream_data = (char *)(&id); 
	if (!isxdigit(stream_data[0]) || !isxdigit(stream_data[1]))
		return -1;

	stream_data[2] = 0;
	int stream_number = strtoul(stream_data, 0, 16);
	return stream_number;
}

nsavi::Demuxer::Demuxer(nsavi::avi_reader *_reader) : ParserBase(_reader)
{
	movie_found = NOT_READ;
	idx1_found = NOT_READ;
	info_found = NOT_READ;
	movie_start = 0;
	index = 0;
	info = 0;
}

// reads a chunk and updates parse state variable on error
static int ReadChunk(nsavi::avi_reader *reader, nsavi::riff_chunk *chunk, nsavi::ParseState &state, uint32_t *bytes_read)
{
	int ret = nsavi::read_riff_chunk(reader, chunk, bytes_read);
	if (ret == nsavi::READ_EOF)
	{
		state = nsavi::NOT_FOUND;
		return nsavi::READ_NOT_FOUND;
	}
	else if (ret > nsavi::READ_OK)
	{
		state = nsavi::PARSE_ERROR;
		return ret;
	}
	else if (ret < nsavi::READ_OK)
	{ // pass-thru return value from avi_reader
		state = nsavi::PARSE_RESYNC;
		return ret;
	}

	return nsavi::READ_OK;
}

// skips a chunk and updates a parser state variable on error
static int SkipChunk(nsavi::avi_reader *reader, const nsavi::riff_chunk *chunk, nsavi::ParseState &state, uint32_t *bytes_read)
{
	int ret = nsavi::skip_chunk(reader, chunk, bytes_read);
	if (ret == nsavi::READ_EOF)
	{
		state = nsavi::NOT_FOUND;
		return nsavi::READ_NOT_FOUND;
	}
	else if (ret > nsavi::READ_OK)
	{
		state = nsavi::PARSE_ERROR;
		return ret;
	}
	else if (ret < nsavi::READ_OK)
	{ // pass-thru return value from avi_reader
		state = nsavi::PARSE_RESYNC;
		return ret;
	}

	return nsavi::READ_OK;
}

static int Read(nsavi::avi_reader *reader, void *buffer, uint32_t size, nsavi::ParseState &state, uint32_t *out_bytes_read)
{
	uint32_t bytes_read;
	int ret = reader->Read(buffer, size, &bytes_read);
	if (ret > nsavi::READ_OK)
	{
		state = nsavi::PARSE_ERROR;
		return ret;
	}
	else if (ret < nsavi::READ_OK)
	{ // pass-thru return value from avi_reader
		state = nsavi::PARSE_RESYNC;
		return ret;
	}
	else if (bytes_read != size)
	{
		state = nsavi::PARSE_ERROR;
		return nsavi::READ_EOF;
	}
	*out_bytes_read = bytes_read;
	return nsavi::READ_OK;
}

int nsavi::Demuxer::GetHeaderList(HeaderList *header_list)
{
	if (riff_parsed != PARSED)
		return READ_INVALID_CALL;

	if (riff_parsed == PARSE_RESYNC)
		reader->Seek(riff_start);

	if (header_list_parsed == NOT_READ)
	{
		// first, see how far we are into the file to properly bound our reads
		uint64_t start = reader->Tell();
		uint32_t bytes_available = riff_header.size;
		bytes_available -= (uint32_t)(start - riff_start);

		while (bytes_available)
		{
			if (bytes_available < 8)
			{
				header_list_parsed = NOT_FOUND;
				return READ_NOT_FOUND;
			}
			uint32_t bytes_read;
			riff_chunk chunk;
			int ret = ReadChunk(reader, &chunk, header_list_parsed, &bytes_read);
			if (ret)
				return ret;

			bytes_available -= bytes_read;
			if (bytes_available < chunk.size)
			{
				header_list_parsed = PARSE_ERROR;
				return READ_INVALID_DATA;
			}
			switch(chunk.id)
			{
			case 'TSIL': // list chunk
				switch(chunk.type)
				{
				case 'lrdh': // this is what we're looking for
					ret = ParseHeaderList(chunk.size, &bytes_read);
					if (ret == READ_OK)
					{
						header_list->avi_header = avi_header;
						header_list->stream_list = stream_list;
						header_list->stream_list_size = stream_list_size;
						header_list->odml_header = odml_header;
					}
					return ret;
				case 'OFNI': // INFO
					if (!info)
					{
						info = new nsavi::Info();
						if (!info)
						{
							header_list_parsed = PARSE_ERROR;
							return READ_OUT_OF_MEMORY;
						}
						ret = info->Read(reader, chunk.size);
						if (ret)
						{
							header_list_parsed = PARSE_ERROR;
							return ret;
						}
						break;
					}
					// fall through
				default: // skip anything we don't understand
					ret = SkipChunk(reader, &chunk, header_list_parsed, &bytes_read);
					if (ret)
						return ret;
					bytes_available -= bytes_read;
					break;
				}

				break;
			default: // skip anything we don't understand
			case 'KNUJ': // skip junk chunks
				ret = SkipChunk(reader, &chunk, header_list_parsed, &bytes_read);
				if (ret)
					return ret;
				bytes_available -= bytes_read;
				break;
				// TODO; case '1xdi': break;
			}
		}
	}


	if (header_list_parsed == PARSED)
	{
		header_list->avi_header = avi_header;
		header_list->stream_list = stream_list;
		header_list->stream_list_size = stream_list_size;
		header_list->odml_header = odml_header;
		return READ_OK;
	}

	return READ_INVALID_CALL;
}

int nsavi::Demuxer::FindMovieChunk()
{
	if (riff_parsed != PARSED)
		return READ_INVALID_CALL;

	if (header_list_parsed != READ_OK)
		return READ_INVALID_CALL;

	if (movie_found == PARSED)
		return READ_OK;

	if (movie_found == NOT_READ)
	{
		// first, see how far we are into the file to properly bound our reads
		uint64_t start = reader->Tell();
		uint32_t bytes_available = riff_header.size;
		bytes_available -= (uint32_t)(start - riff_start);
		while (movie_found == NOT_READ)
		{
			if (bytes_available < 8)
			{
				header_list_parsed = NOT_FOUND;
				return READ_NOT_FOUND;
			}
			uint32_t bytes_read;
			int ret = ReadChunk(reader, &movi_header, movie_found, &bytes_read);
			if (ret)
				return ret;

			bytes_available -= bytes_read;
			if (bytes_available < movi_header.size)
			{
				movie_found = PARSE_ERROR;
				return READ_INVALID_DATA;
			}
			switch(movi_header.id)
			{
				// TODO: parse any other interesting chunks along the way
			case 'TSIL': // list chunk
				switch(movi_header.type)
				{
				case 'ivom':
					{
						movie_found = PARSED;
						movie_start = reader->Tell();
						return READ_OK;
					}
					break;
				case '1xdi': // index v1 chunk
					if (!index)
					{
						index = (nsavi::IDX1 *)malloc(idx1_header.size + sizeof(uint32_t));
						if (index)
						{
							ret = Read(reader, ((uint8_t *)index) + sizeof(uint32_t), idx1_header.size, idx1_found, &bytes_read);
							if (ret)
								return ret;

							bytes_available-=bytes_read;
							index->index_count = idx1_header.size / sizeof(IDX1_INDEX);
							if ((idx1_header.size & 1) && bytes_available)
							{
								bytes_available--;
								reader->Skip(1);
							}
							idx1_found = PARSED;
						}
						else
						{
							return READ_OUT_OF_MEMORY;
						}
					}
					else
					{
						ret = SkipChunk(reader, &movi_header, movie_found, &bytes_read);
						if (ret)
							return ret;
						bytes_available -= bytes_read;
					}
					break;
				case 'OFNI': // INFO
					if (!info)
					{
									info = new nsavi::Info();
						if (!info)
						{
							movie_found = PARSE_ERROR;
							return READ_OUT_OF_MEMORY;
						}

						ret = info->Read(reader, movi_header.size);
						if (ret)
						{
							movie_found = PARSE_ERROR;
							return ret;
						}
						break;
					}
					// fall through
				default: // skip anything we don't understand
					ret = SkipChunk(reader, &movi_header, movie_found, &bytes_read);
					if (ret)
						return ret;
					bytes_available -= bytes_read;
					break;
				}
				break;

			default: // skip anything we don't understand
			case 'KNUJ': // skip junk chunks
				ret = SkipChunk(reader, &movi_header, movie_found, &bytes_read);
				if (ret)
					return ret;
				bytes_available -= bytes_read;
				break;
			}
		}
	}
	return nsavi::READ_NOT_FOUND; // TODO: not sure about this
}
int nsavi::Demuxer::SeekToMovieChunk(nsavi::avi_reader *reader)
{
	return reader->Seek(movie_start);
}

int nsavi::Demuxer::GetNextMovieChunk(nsavi::avi_reader *reader, void **data, uint32_t *chunk_size, uint32_t *chunk_type, int limit_stream_num)
{
		ParseState no_state;
	if (movie_found == PARSED)
	{
		uint64_t start = reader->Tell();
		uint32_t bytes_available = movi_header.size;
		bytes_available -= (uint32_t)(start - movie_start);

		uint32_t bytes_read;
		riff_chunk chunk;
again:
		int ret = ReadChunk(reader, &chunk, no_state, &bytes_read);
		if (ret)
			return ret;

		if (chunk.id == 'TSIL' || chunk.id == 'FFIR')
		{
			goto again; // skip 'rec' chunk headers
		}
		if (chunk.id == 'KNUJ' || chunk.id == '1xdi') 
		{
			SkipChunk(reader, &chunk, no_state, &bytes_read);
			goto again;

		}
		if (limit_stream_num != 65536)
		{
			if (limit_stream_num != GetStreamNumber(chunk.id))
			{
				SkipChunk(reader, &chunk, no_state, &bytes_read);
				goto again;
			}
		}

		*data = malloc(chunk.size);
		if (!*data)
			return READ_OUT_OF_MEMORY;
		*chunk_size = chunk.size;
		*chunk_type = chunk.id;


		ret = Read(reader, *data, chunk.size, no_state, &bytes_read);
		if (ret)
			return ret;

		if ((chunk.size & 1))
		{
			bytes_available--;
			reader->Skip(1);
		}
		return READ_OK;
	}
	else
		return READ_FAILED;

}

int nsavi::Demuxer::GetSeekTable(nsavi::IDX1 **out_index)
{
	if (idx1_found == PARSED)
	{
		*out_index = index;
		return READ_OK;
	}

	if (idx1_found == NOT_FOUND)
	{
		return READ_NOT_FOUND;
	}

	if (idx1_found != NOT_READ)
		return READ_FAILED;

	uint64_t old_position = reader->Tell();

	if (movie_found == PARSED)
		reader->Seek(movie_start+movi_header.size);
	else
		reader->Seek(riff_start);

	uint64_t start = reader->Tell();
	uint32_t bytes_available = riff_header.size;
	bytes_available -= (uint32_t)(start - riff_start);

	while (idx1_found == NOT_READ)
	{
		if (bytes_available < 8)
		{
			idx1_found = NOT_FOUND;
			reader->Seek(old_position);
			return READ_NOT_FOUND;
		}
		uint32_t bytes_read;
		int ret = ReadChunk(reader, &idx1_header, idx1_found, &bytes_read);
		if (ret)
			return ret;

		bytes_available -= bytes_read;
		if (bytes_available == (idx1_header.size - 12)) // some stupid program has this bug
		{
			idx1_header.size-=12;
		}
		if (bytes_available < idx1_header.size)
		{
			idx1_found = PARSE_ERROR;
			reader->Seek(old_position);
			return READ_INVALID_DATA;
		}
		switch(idx1_header.id)
		{
			// TODO: parse any other interesting chunks along the way
		case '1xdi': // index v1 chunk
			index = (nsavi::IDX1 *)malloc(idx1_header.size + sizeof(uint32_t));
			if (index)
			{
				ret = Read(reader, ((uint8_t *)index) + sizeof(uint32_t), idx1_header.size, idx1_found, &bytes_read);
				if (ret)
				{
					reader->Seek(old_position);
					return ret;
				}

				bytes_available-=bytes_read;
				index->index_count = idx1_header.size / sizeof(IDX1_INDEX);
				if ((idx1_header.size & 1) && bytes_available)
				{
					bytes_available--;
					reader->Skip(1);
				}
				idx1_found = PARSED;
			}
			else
			{
				reader->Seek(old_position);
				return READ_OUT_OF_MEMORY;
			}

			break;
		default: // skip anything we don't understand
		case 'KNUJ': // skip junk chunks
			ret = SkipChunk(reader, &idx1_header, idx1_found, &bytes_read);
			if (ret)
				return ret;
			bytes_available -= bytes_read;
			break;
		}
	}

	*out_index = index;
	reader->Seek(old_position);
	return READ_OK;
}

int nsavi::Demuxer::GetIndexChunk(nsavi::INDX **out_index, uint64_t offset)
{
	nsavi::INDX *index = 0;
	uint64_t old_position = reader->Tell();
	reader->Seek(offset);
	ParseState dummy;
	uint32_t bytes_read;
	riff_chunk chunk;
	int ret = ReadChunk(reader, &chunk, dummy, &bytes_read);
	if (ret)
		return ret;
	index = (nsavi::INDX *)malloc(sizeof(uint32_t) + chunk.size);
	if (index)
	{
		ret = Read(reader, ((uint8_t *)index) + sizeof(uint32_t), chunk.size, dummy, &bytes_read);
		if (ret)
		{
			reader->Seek(old_position);
			return ret;
		}
		index->size_bytes=chunk.size;
	}
	else
	{
		reader->Seek(old_position);
		return READ_OUT_OF_MEMORY;
	}

	*out_index = index;
	reader->Seek(old_position);
	return READ_OK;
}

static bool IsCodecChunk(uint32_t header)
{
	char *blah = (char *)&header;
	if (blah[0] != 'i' && !isxdigit(blah[0]))
		return false;
	if (blah[1] != 'x' && !isxdigit(blah[1]))
		return false;

	return true;
}

int nsavi::Demuxer::Seek(uint64_t offset, bool absolute, nsavi::avi_reader *reader)
{
	/* verify index by reading the riff chunk and comparing position->chunk_id and position->size with the read chunk
	if it fails, we'll try the two following things
	1) try again without the -4
	2) try from the start of the file
	3) try from riff_start
	*/
	uint32_t bytes_read;
	uint32_t chunk_header=0;
	if (!reader)
		reader = this->reader;
	if (absolute)
	{
		reader->Seek(offset - 8);
		reader->Peek(&chunk_header, 4, &bytes_read);
		if (!IsCodecChunk(chunk_header))
		{
			reader->Skip(4);
			reader->Peek(&chunk_header, 4, &bytes_read);
			if (!IsCodecChunk(chunk_header))
			{
				reader->Skip(4);
			}
		}
	}
	else
	{
		reader->Seek(movie_start+offset - 4);
		reader->Peek(&chunk_header, 4, &bytes_read);
		if (!IsCodecChunk(chunk_header))
		{
			reader->Seek(offset);
		}
	}


	
	/*
	riff_chunk test;
	ParseState blah;
	uint32_t bytes_read;
	ReadChunk(f, &test, blah, &bytes_read);
	fseek64(f, movie_start+position->offset - 4, SEEK_SET);
	*/
	return READ_OK;
}