/********************************************************

	Benchmarking tool
	Copyright 2000 Eugene Kuznetsov (divx@euro.ru)

*********************************************************/

#include <config.h>
#include <fourcc.h>
#include <avifile.h>
//extern int EF_PROTECT_BELOW;
#include <aviplay.h>
#include <cpuinfo.h>
#include <utils.h>
#include <version.h>
#include <renderer.h>
#include <creators.h>

#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>

#include <X11/Xlib.h>

using namespace std;
using namespace Creators;

#define __MODULE__ "benchmark"

int help()
{
    cout << "\nUsage: benchmark [Options] <file>\n"
	//	<<"[-zoom]\n"
	"  Performs a measurement of Avifile performance for given file.\n\n"
	"  Options:\n"
	"   -help\t\tThis help\n"
	"   -yuv\t\tUse hardware acceleration if it is available. Assumes -sdl.\n"
	"   -hurry\tTest decoder for ability to 'hurry up'\n"
	"   -seek <sec>\tSeek to the given time in the movie\n"
	"   -frame <pos>\tSeek to the given frame in the movie\n"
	"   -length <#>\tMeasure #frames (100 defauls, 0 - full movie)\n"
	"   -direct\tUse direct memory buffer in video card\n"
	"   -real\tUse realtime mode (as in player) (slower)\n"
	//"  -size x y Zoom picture to dimensions x*y\n"
	"  Description of result:\n"
	"   \'Decompression\': Time spent on reading one video frame from file and its decompression.\n"
	"   \'Drawing\'      : Time spent on X server request.\n"
	"   \'Sync\'         : Time spent by X server on drawing the frame.\n";
      //"                    For DGA renderer it's zero because all drawing is performed by Avifile."
    return 0;
}

int main(int argc, char **argv)
{
//    QApplication a(argc,argv);

//    EF_PROTECT_BELOW=1;
    if(GetAvifileVersion()!=AVIFILE_VERSION)
    {
	cout<<"This binary was compiled for Avifile ver. "<<AVIFILE_VERSION<<", but the library is ver. "<<GetAvifileVersion()<<". Aborting."<<endl;
	return 0;
    }

    int result;
    char* fn = 0;
    VideoRenderer* renderer=0;
//    QWidget* m=new QWidget;
    if (argc < 2)
	return help();

    int use_zoom = 0;
    int use_yuv = 0;
    int hurry = 0;
    int direct = 0;
    int real_mode = 0;
    int xs, ys;
    int quality;
    int qual_key;
    int status;
    unsigned int measureLength = 100;
    //VideoRenderer::allow_sw_yuv = true;
    VideoRenderer::direct_resizes = true;
    double seekTime = 0.0;
    framepos_t framepos = 0;
    for(int i = 1; i < argc; i++)
    {
	if (!strcmp(argv[i], "-help") || !strcmp(argv[i], "--help")
	    || !strcmp(argv[i], "-h"))
	    return help();
	else if (!strcmp(argv[i], "-seek"))
	{
	    if (i+1 < argc)
		sscanf(argv[++i], "%lf", &seekTime);
	}
	else if (!strcmp(argv[i], "-frame"))
	{
	    if (i+1 < argc)
		sscanf(argv[++i], "%d", &framepos);
	}
	else if (!strcmp(argv[i], "-length"))
	{
	    if (i+1 < argc)
		sscanf(argv[++i], "%d", &measureLength);

	}
	else if (!strcmp(argv[i], "-yuv"))
	    use_yuv=1;
	else if (!strcmp(argv[i], "-real"))
	    real_mode=1;
	else if (!strcmp(argv[i], "-hurry"))
	    hurry=1;
	else if (!strcmp(argv[i], "-direct"))
	    direct=1;
	else if (!strcmp(argv[i], "-qual"))
	{
	    if (i + 1 < argc)
	    {
		quality=atoi(argv[i+1]);
		CodecInfo* ci=(CodecInfo *)CodecInfo::match(fccDIV3);
		if(ci)
		    SetCodecAttr(*ci, "Quality", quality);
		/*
		 if(RegCreateKeyExA(HKEY_CURRENT_USER,
		 "SOFTWARE\\Microcrap\\Scrunch", 0, 0, 0, 0, 0,
		 &qual_key, &status)==0)
		 {
		 RegSetValueExA(qual_key, "Current Post Process Mode", 0, 0, &quality, 4);
		 quality=-1;
		 RegSetValueExA(qual_key, "Force Post Process Mode", 0, 0, &quality, 4);
		 RegCloseKey(qual_key);
		 }
		 */
	    }
	}
	else if (!strcmp(argv[i], "-size"))
	{
	    if(i+2<argc)
	    {
		use_zoom=1;
		xs=atoi(argv[i+1]);
		ys=atoi(argv[i+2]);
		if((xs<0)||(ys<0))use_zoom=0;
	    }
	}
	else
	{
	    fn = argv[i];
	    cout << "Detected filename: " << fn << endl;
	}
    }

    int64_t total_time_begin = longcount();
    double read_times=0, draw_times=0, sync_times=0, hurry_times=0;
    unsigned int frames=0;
    IAviReadFile *file=0;
    Display* dpy=0;
    try
    {
	dpy=XOpenDisplay(0);
	if(!dpy)throw FATAL("Cannot open X display");
	file=CreateIAviReadFile(fn);
        IAviReadStream *stream=file->GetStream(0, AviStream::Video);

        BITMAPINFOHEADER bh;
	if(stream->StartStreaming()!=0)
	    throw FATAL("Cannot decode this video stream");
	stream->GetDecoder()->SetDestFmt(GetPhysicalDepth(dpy));
	stream->GetOutputFormat(&bh, sizeof(bh));
        if (seekTime)
	{
	    if (stream->GetLengthTime() < seekTime)
		//if (stream->SeekTimeToKeyFrame(seekTime) < 0)
		throw FATAL("Bad seek location");
	    stream->SeekTimeToKeyFrame(seekTime);

	    while (stream->GetTime()<seekTime)
	    {
		//cout << "GetTime " << stream->GetTime() << "  " << seekTime << endl;
		if (stream->ReadFrame(false) != 0)
		    throw FATAL("Bad seek location");
	    }
	}
	else if (framepos)
	{
	    stream->SeekToKeyFrame(framepos);
	    // throw FATAL("Bad seek location");
	    while (stream->GetPos()<framepos)
		if (stream->ReadFrame(false) != 0)
		    throw FATAL("Bad seek location");
	}
	bh.biWidth=abs(bh.biWidth);
	bh.biHeight=abs(bh.biHeight);
	printf("Movie size: %dx%d  [%.4s]\n", bh.biWidth, bh.biHeight, (char*) &bh.biCompression);

	if (measureLength && (stream->GetLength() - stream->GetPos()) < measureLength)
	    throw FATAL("Movie too short for given length");

	try
	{
	    if (!use_yuv)
		throw FATAL("Not needed");
	    BITMAPINFOHEADER bh;
	    stream->GetVideoFormatInfo(&bh, sizeof(bh));
	    fourcc_t fcc;
	    IVideoDecoder::CAPS caps = stream->GetDecoder()->GetCapabilities();
	    cout << "Decoder YUV capabilities: " << caps << endl;
	    if (caps & IVideoDecoder::CAP_YUY2)
		fcc = fccYUY2;
	    else if (caps & IVideoDecoder::CAP_YV12)
		fcc = fccYV12;
	    else
		throw FATAL("YUV unsupported by decoder");

	    renderer = CreateYUVRenderer(0, dpy, bh.biWidth, bh.biHeight, fcc);

	    if (fcc)
	    {
		if (stream->GetDecoder()->SetDestFmt(BitmapInfo::BitCount(fcc),
						     fcc))
		    //shouldn't happen
		    throw FATAL("Error setting YUV decoder output");
	    }
	}
	catch(FatalError& e)
    	{
	    e.Print();
	    delete renderer;
	    renderer = CreateFullscreenRenderer(0, dpy, bh.biWidth, bh.biHeight);
	}
//        m->move(100,100);
//        m->resize(20,20);

	if (use_zoom)
	    renderer->Resize(xs, ys);
	else
	{
	    xs = bh.biWidth;
	    ys = bh.biHeight;
	}

	if (direct)
	{
    	    cout << "$$$$$$$$$$$$$$$$$$$$$$$$$$  Initializing direct " << endl;
	    stream->GetDecoder()->SetDirectMemoryAccess(renderer);
            cout << "$$$$$$$$$$$$$$$$$$$$$$$$$$  set direct " << endl;
	}
	stream->GetDecoder()->SetDecodingMode((real_mode) ? IVideoDecoder::REALTIME : IVideoDecoder::DIRECT);

	//stream->ReadFrame();

	while (measureLength && !stream->Eof() && (frames < 32))
	{
	    stream->ReadFrame();
	    CImage* im=stream->GetFrame();
	    if (im)
	    {
		renderer->Draw(im);
		renderer->Sync();
                im->Release();
		frames++;
	    }
	}
        frames = 0;
	int read_counts = 0, hurry_counts = 0;

	while (!stream->Eof() && (!measureLength || frames < measureLength))
	{
	    int64_t t1 = longcount();
	    stream->ReadFrame(hurry?(frames%5?false:true):true);
	    //cout << "GetPos " << stream->GetPos() << endl;
	    int64_t t2 = longcount();
	    CImage* im = stream->GetFrame();
	    if (im)
	    {
        	renderer->Draw(im);
		int64_t t3 = longcount();
		renderer->Sync();
		int64_t t4 = longcount();
                im->Release();
		if (hurry)
		{
		    if (frames % 5)
		    {
			hurry_counts++;
			hurry_times += to_float(t2,t1);
		    }
		    else
		    {
			read_counts++;
			read_times += to_float(t2,t1);
		    }
		}
		else
		{
		    read_times += to_float(t2,t1);
		    read_counts++;
		}
		draw_times += to_float(t3,t2);
		sync_times += to_float(t4,t3);
		frames++;
	    }
	    else
                cout << "Zero image! - benchmark invalid" << endl;
	}
	float total_time = to_float(longcount(), total_time_begin);
	printf("Played %d frames in %.3f  (t: %.3f) seconds ( avg frame rate %.3f fps )\n",
	       frames, (hurry_times+read_times+draw_times+sync_times), total_time, 
	       frames/(read_times+draw_times+sync_times+hurry_times));
	if (frames)
	{
	    if (!hurry)
		printf("Average (Total) results:\n"
		       "\tDecompression %f ms\t(%.3fs)\n"
		       "\tDrawing %f ms\t\t(%.3fs)\n"
		       "\tSync %f ms\t\t(%.3fs)\n",
		       read_times/frames * 1000., read_times,
		       draw_times/frames * 1000., draw_times,
		       sync_times/frames * 1000., sync_times);
	    else
		printf("Average results:\n\t"
		       " Hurry decompression %f ms\n\t"
		       " Normal decompression %f ms\n\t"
		       " Drawing %f ms\n\t"
		       " Sync %f ms\n",
		       hurry_times/hurry_counts * 1000.,
		       read_times/read_counts * 1000.,
		       draw_times/frames * 1000., sync_times/frames * 1000.);
	}
	if (!use_yuv && sync_times > 0)
	{
	    printf("Average video output speed: %f Mb/s\n",
		   GetPhysicalDepth(dpy)/8*xs*ys*frames/sync_times/(1024*1024));
	}
    }
    catch(FatalError& error)
    {
	error.Print();
    }
    delete renderer;
    delete file;
    if (dpy)
	XCloseDisplay(dpy);
    //delete m;
    return 0;
}
