/*
 *
 *	FFMpeg codec for audio / video
 *
 *	Honza P.
 *
 */

#pragma comment(lib, "lib/avcodec-51.lib")
#pragma comment(lib, "lib/avformat-51.lib")
#pragma comment(lib, "lib/avutil-49.lib")
#pragma comment(lib, "lib/swscale-0.lib")

#include "../UberLame_src/Integer.h" // uint8_t is here

extern "C" {
#include "include/ffmpeg/avformat.h"
#include "include/ffmpeg/avcodec.h"
}
#include "Codec.h"

static int ffmpeg_initialized = 0;
static int file_opened = 0;
// status bits

static AVFormatContext *pFormatCtx = NULL;
static AVCodec *pCodec = NULL;
static AVCodecContext *pCodecCtx = NULL;
static AVFrame *pFrame = NULL, *pFrameRGB = NULL;
static int videoStream = -1;
static int numBytes = 0;
static uint8_t *buffer = NULL;
// ffmpeg variables

int videoLoaded = 0;
int videoWidth = 0;
int videoHeight = 0;
float videoFrameTime = 0; //in seconds
float videoLength = 0; //in seconds
float playTime = 0; //in seconds
// video properties

/**
 *	opens video file filename and fills the above global variables
 *	with it's properties and returns true if successful
 */
int OpenVideoFile(const char *filename)
{
	if(!ffmpeg_initialized) {
		av_register_all();
		ffmpeg_initialized = 1;
	}
	// register ffmpeg

	if(file_opened)
		CloseVideoFile();
	// close file if opened

	videoLoaded = 0;

	if(av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL) != 0)
		return 0;
	if(av_find_stream_info(pFormatCtx) < 0) {
		av_close_input_file(pFormatCtx);
		return 0;
	}
	dump_format(pFormatCtx, 0, filename, 0); // to stderr
	// open video file

	videoStream = -1;
	for(unsigned int i = 0; i < pFormatCtx->nb_streams; ++ i) {
		if(pFormatCtx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO) {
			videoStream=i;
			break;
		}
	}
	if(videoStream == -1) {
		av_close_input_file(pFormatCtx);
		return 0;
	}
	// Find the first video stream

	pCodecCtx = pFormatCtx->streams[videoStream]->codec;
	// Get a pointer to the codec context for the video stream

	if((pCodec = avcodec_find_decoder(pCodecCtx->codec_id)) == NULL) {
		fprintf(stderr, "Unsupported codec!\n");
		av_close_input_file(pFormatCtx);
		return 0; // Codec not found
	}
	// Find the decoder for the video stream

	if(avcodec_open(pCodecCtx, pCodec) < 0) {
		av_close_input_file(pFormatCtx);
		return 0; // Could not open codec
	}
	// Open codec

	videoLoaded = 1;
	videoWidth = pCodecCtx->width;
	videoHeight = pCodecCtx->height;
	if(pFormatCtx->streams[videoStream]->r_frame_rate.den &&
	   pFormatCtx->streams[videoStream]->r_frame_rate.num)
		videoFrameTime = float(1/av_q2d(pFormatCtx->streams[videoStream]->r_frame_rate));
	else
		videoFrameTime = float(av_q2d(pCodecCtx->time_base));
	videoLength = pFormatCtx->duration / (float)AV_TIME_BASE;
	// set video parameters

	pFrame = avcodec_alloc_frame();
	pFrameRGB = avcodec_alloc_frame();
	if(pFrame == NULL || pFrameRGB == NULL) {
		avcodec_close(pCodecCtx);
		av_close_input_file(pFormatCtx);
		return 0;
	}
	// Allocate video frame

	numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
								pCodecCtx->height);
	if((buffer = (uint8_t*)av_malloc(numBytes * sizeof(uint8_t))) == NULL) {
		avcodec_close(pCodecCtx);
		av_close_input_file(pFormatCtx);
		return 0;
	}
	// Determine required buffer size and allocate buffer

	avpicture_fill((AVPicture*)pFrameRGB, buffer, PIX_FMT_RGB24,
		pCodecCtx->width, pCodecCtx->height);
	// Assign appropriate parts of buffer to image planes in pFrameRGB
	// Note that pFrameRGB is an AVFrame, but AVFrame is a superset
	// of AVPicture

	file_opened = 1;
	playTime = 0;
	return 1;
}

/**
 *	decodes next video frame and returns it
 *	if there are no more frames, returns frames with NULL buffer and zero size
 */
TImage GetVideoFrame()
{
	if(!file_opened) {
		TImage noimg = {NULL, 0, 0, 0, 0};
		return noimg; // no frames
	}

	int frameFinished;
	AVPacket packet;
	while(av_read_frame(pFormatCtx, &packet) >= 0) {
		// Is this a packet from the video stream?
		if(packet.stream_index == videoStream) {
			// Decode video frame
			avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,
				packet.data, packet.size);

			// Did we get a video frame?
			if(frameFinished) {
				// Convert the image from its native format to RGB
				img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24, 
					(AVPicture*)pFrame, pCodecCtx->pix_fmt, 
					pCodecCtx->width, pCodecCtx->height);

				TImage img = {pFrameRGB->data[0], pCodecCtx->width,
					pCodecCtx->height, 3 * sizeof(uint8_t), pFrameRGB->linesize[0]};
				// fill image struct

				playTime += videoFrameTime;
				// maintain playTime

				// Free the packet that was allocated by av_read_frame
				av_free_packet(&packet);
				return img;
			}
		}

		// Free the packet that was allocated by av_read_frame
		av_free_packet(&packet);
	}

	TImage noimg = {NULL, 0, 0, 0, 0};
	return noimg; // no frames
}

/**
 *	close video file (if opened)
 */
void CloseVideoFile()
{
	videoLoaded = 0;
	if(!file_opened)
		return;

	av_free(buffer);
	av_free(pFrameRGB);
	// Free the RGB image

	av_free(pFrame);
	// Free the YUV frame

	avcodec_close(pCodecCtx);
	// Close the codec

	av_close_input_file(pFormatCtx);
	// Close the video file

	file_opened = 0;
}

/**
 *	seeks to seekTime (in seconds)
 *	relative is either 1 or 0 depending on how should be seekTime interpreted
 *	returns nonzero value upon success
 */
int Seek(float seekTime, int relative)
{
	float targetTime = (relative)? playTime + seekTime : seekTime;
	float deltaTime = targetTime - playTime;
	// determine target time and delta time

	int64_t targetTimeFrames = (int64_t)(targetTime * AV_TIME_BASE);
	int flags = 0;//(deltaTime < 0)? AVSEEK_FLAG_BACKWARD : 0;
	// convert from seconds to time frames (not images - frames)

	const AVRational tbq = {1, AV_TIME_BASE};

	targetTimeFrames = av_rescale_q(targetTimeFrames, tbq/*AV_TIME_BASE_Q*/, // "C" uglism
		pFormatCtx->streams[videoStream]->time_base);
	// convert time frames ... somehow ...

	if(av_seek_frame(pFormatCtx, videoStream, targetTimeFrames, flags) >= 0) {
		playTime = targetTime;
		return 1;
	}
	return 0;
}
