/*
 * softmpeg.c: A plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 */

#include <getopt.h>
#include <stdlib.h>
#include <vdr/interface.h>
#include <vdr/plugin.h>
#include <vdr/player.h>
#include <vdr/osd.h>

#include <time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include "i18n.h"

//#include <dfb++/dfb++.h>
#include <directfb.h>
#include "softmpeg.h"

static IDirectFB *dfb = NULL;
static IDirectFBDisplayLayer *primarylayer = NULL;

static IDirectFBWindow *window = NULL;
static DFBWindowID windowid = -1;

static DFBDisplayLayerID layerid = -1;

struct softmpeg_decoder *d = NULL;

int videolayer_is_overlay = 0;


#define DFBCHECK(x...)   \
do {		     \
    DFBResult err = x;   \
			 \
	if (err != DFB_OK) { \
		fprintf(stderr, "%s <%d>:\n\t", __FILE__, __LINE__ );   \
		DirectFBErrorFatal( #x, err );			   \
	}							    \
} while (0)

#define COLORKEY 17,8,79

static DFBEnumerationResult enum_layers_callback(unsigned int id, DFBDisplayLayerDescription desc, void *data)
{
	/* We take the first video layer not being the primary */
	if (id != DLID_PRIMARY && desc.caps & DLCAPS_SURFACE && desc.type & DLTF_VIDEO) {
		layerid = id;
		return DFENUM_CANCEL;
	}

	return DFENUM_OK;
}

static int dfb_init(int *argc, char ***argv)
{
	DFBCHECK(DirectFBInit(argc, argv));
	DFBCHECK(DirectFBSetOption("no-cursor", NULL));
	DFBCHECK(DirectFBCreate(&dfb));
	DFBCHECK(dfb->GetDisplayLayer(dfb, DLID_PRIMARY, &primarylayer));
	DFBCHECK(primarylayer->SetCooperativeLevel(primarylayer, DLSCL_ADMINISTRATIVE));
	DFBCHECK(dfb->EnumDisplayLayers(dfb, enum_layers_callback, NULL));

	if (videolayer_is_overlay != 0) {
		DFBCHECK(primarylayer->SetBackgroundColor(primarylayer, COLORKEY, 0xff));
		layerid = -1;
	}
	
	if (layerid == -1) {
		DFBWindowDescription windowdesc;
		fprintf(stderr, "could not find suitable videolayer - using window on primary layer\n");

		windowdesc.flags = (DFBWindowDescriptionFlags)(DWDESC_HEIGHT | DWDESC_WIDTH);
		windowdesc.width = 720;
		windowdesc.height = 576;

		DFBCHECK(primarylayer->CreateWindow(primarylayer, &windowdesc, &window));
		window->GetID(window, &windowid);

		window->SetOpacity(window, 0xff);
	} else {
		fprintf(stderr, "using videolayer\n");
	}
	
	return 0;
}

static void dfb_destroy(void)
{
	if (window)
		window->Release(window);

	if (primarylayer)
		primarylayer->Release(primarylayer);

	if (dfb)
		dfb->Release(dfb);
}

static const char *VERSION = "0.0.1";
static const char *DESCRIPTION = "A software emulated MPEG2 device";
static const char *MAINMENUENTRY = "SoftMPEG";

#define MAX_OSD_WINDOWS 4	

// --- cSoftOsd -----------------------------------------------
class cSoftOsd : public cOsd {
private:
	IDirectFBWindow *window[MAX_OSD_WINDOWS];
protected:
public:
    cSoftOsd(int XOfs, int XOfs);
    virtual ~cSoftOsd();
    virtual eOsdError CanHandleAreas(const tArea *Areas, int NumAreas);
    virtual eOsdError SetAreas(const tArea *Areas, int NumAreas);
    virtual void Flush(void);
};

cSoftOsd::cSoftOsd(int X, int Y) : cOsd(X, Y)
{
	DFBWindowDescription windowdesc;

	fprintf(stderr, "[softmpeg] cSoftOsd OSD @ %d x %d\n", X, Y);

	for (int i = 0; i < MAX_OSD_WINDOWS; i++) {
		window[i] = NULL;		
	}
}

cSoftOsd::~cSoftOsd()
{
	fprintf(stderr, "[softmpeg] xxx %s\n", __FUNCTION__);

	for (int i = 0; i < MAX_OSD_WINDOWS; i++) {
		if (window[i])
			DFBCHECK(window[i]->Destroy(window[i]));
		window[i] = NULL;		
	}
}

eOsdError cSoftOsd::CanHandleAreas(const tArea *Areas, int NumAreas)
{
	eOsdError Result = cOsd::CanHandleAreas(Areas, NumAreas);

	fprintf(stderr, "[softmpeg] %s, areas:%d\n", __FUNCTION__, NumAreas);

	return Result;
}

eOsdError cSoftOsd::SetAreas(const tArea *Areas, int NumAreas)
{
	eOsdError Result = cOsd::SetAreas(Areas, NumAreas);

	fprintf(stderr, "[softmpeg] %s, areas:%d\n", __FUNCTION__, NumAreas);

	for (int i = 0; i < MAX_OSD_WINDOWS; i++) {
		if (window[i])
			DFBCHECK(window[i]->Destroy(window[i]));
		window[i] = NULL;		
	}

	for (int i = 0; i < NumAreas; i++) {
		const tArea *a = &Areas[i];
		DFBWindowDescription windowdesc;

		fprintf(stderr, "[softmpeg] %s, area %d: t:%d, l:%d, w:%d, h:%d\n", __FUNCTION__, i, Left(), Top(), a->Width(), a->Height());
	
		windowdesc.flags = (DFBWindowDescriptionFlags)(DWDESC_HEIGHT | DWDESC_WIDTH | DWDESC_PIXELFORMAT | DWDESC_POSX | DWDESC_POSY | DWDESC_CAPS);
		windowdesc.height = a->Height();
		windowdesc.width = a->Width();
		windowdesc.posx = Left();
		windowdesc.posy = Top();
		windowdesc.pixelformat = DSPF_ARGB; 
		windowdesc.caps = DWCAPS_ALPHACHANNEL;
		DFBCHECK(primarylayer->CreateWindow(primarylayer, &windowdesc, &window[i]));
		window[i]->SetOpacity(window[i], 0x00);
	}
	
	return Result;
}

void cSoftOsd::Flush(void)
{
    for (int i = 0; i < MAX_OSD_WINDOWS; i++)
    {
   	cBitmap *Bitmap = GetBitmap(i);
	if (!Bitmap)
		break;

	fprintf(stderr, "[softmpeg] %s: wnd:%d, le:%d, top:%d, w:%d, h:%d\n", __FUNCTION__, i, Left(), Top(), Bitmap->Width(), Bitmap->Height());

 	IDirectFBWindow *osd = window[i];

	IDirectFBSurface *surface;
	DFBCHECK(osd->GetSurface(osd, &surface));

	int pitch;
	uint8_t *buf;
	DFBCHECK(surface->Lock(surface, DSLF_WRITE, (void **)&buf, &pitch));

	for (int yp = 0; yp < Bitmap->Height(); yp++) {
		for (int ix = 0; ix < Bitmap->Width(); ix++) {

			const tIndex *adr =  Bitmap->Data(ix, yp);
			tColor c = Bitmap->Color(*adr);

			buf[4*ix+0]=(c >>  0) & 255; //Blue
			buf[4*ix+1]=(c >>  8) & 255; //Green
			buf[4*ix+2]=(c >> 16) & 255; //Red
			buf[4*ix+3]=(c >> 24) & 255; //Alpha
			// fprintf(stderr,"%2x %2x %2x %2x\n",(c >> 24)& 255,(c >> 16)& 255,(c >> 8)& 255,(c >> 0)& 255);
		}
		buf += pitch;
	}
	DFBCHECK(surface->Unlock(surface));
	DFBCHECK(surface->Flip(surface, 0, (DFBSurfaceFlipFlags)DSFLIP_ONSYNC));
	osd->SetOpacity(osd, 0xff);
    }
}

// --- cSoftOsdProvider -----------------------------------------------
class cSoftOsdProvider : public cOsdProvider {
private:
    cOsd *osd;
public:
    virtual cOsd *CreateOsd(int Left, int Top);
};

cOsd * cSoftOsdProvider::CreateOsd(int Left, int Top)
{
	fprintf(stderr, "[softmpeg] %s\n", __FUNCTION__);
	osd = new cSoftOsd(Left, Top);
	return osd;
}

// --- cSoftMPEG ------------------------------------------------------------
class cPluginSoftMPEG:public cPlugin {
      private:
      public:
	cPluginSoftMPEG(void);
	virtual ~ cPluginSoftMPEG();
	virtual const char *Version(void) {
		return VERSION;
	} virtual const char *Description(void) {
		return tr(DESCRIPTION);
	}
	virtual const char *CommandLineHelp(void);
	virtual bool Initialize(void);
	virtual bool ProcessArgs(int argc, char *argv[]);
	virtual bool Start(void);
	virtual void Housekeeping(void);
	virtual const char *MainMenuEntry(void) {
		return tr(MAINMENUENTRY);
	}
	virtual cOsdObject *MainMenuAction(void);
	virtual cMenuSetupPage *SetupMenu(void);
	virtual bool SetupParse(const char *Name, const char *Value);
};

// Global variables that control the overall behaviour:


// --- cSoftMPEG --------------------------------------------------
class cSoftMPEG:public cDevice {
      private:
      public:
	cSoftMPEG(void);
	~cSoftMPEG();
	virtual bool HasDecoder(void) const;
	virtual bool CanReplay(void) const;
	virtual bool SetPlayMode(ePlayMode PlayMode);
	virtual void TrickSpeed(int Speed);
	virtual void Clear(void);
	virtual void Play(void);
	virtual void Freeze(void);
	virtual void Mute(void);
	virtual void StillPicture(const uchar * Data, int Length);
	virtual bool Poll(cPoller & Poller, int TimeoutMs = 0);
	virtual int PlayVideo(const uchar * Data, int Length);
	virtual int ProvidesCa(const cChannel *Channel) const;
	virtual void MakePrimaryDevice(bool On);
};

cSoftMPEG::cSoftMPEG()
{
	struct softmpeg_directfb_initdata init;

	fprintf(stderr, "[softmpeg] cSoftMPEG\n");
	if (NULL != getenv("DIRECTFB_VIDEO_IS_OVERLAY")) {
		videolayer_is_overlay = 1;	
	}
	
	dfb_init(NULL, NULL);

	init.window_id = windowid;
	init.layer_id = layerid;
	init.width = 720;
	init.height = 576;

	d = softmpeg_decoder_create(NULL, NULL, SOFTMPEG_DIRECTFB, &init, SOFTMPEG_FUSIONSOUND, NULL);

	if (layerid != -1 && videolayer_is_overlay != 0) {
		IDirectFBDisplayLayer *videolayer;
		DFBDisplayLayerDescription desc;
		DFBDisplayLayerConfig dlc;
		IDirectFBSurface *surface;

		DFBCHECK(dfb->GetDisplayLayer(dfb, layerid, &videolayer));
		DFBCHECK(videolayer->SetCooperativeLevel(videolayer, DLSCL_ADMINISTRATIVE));

		/* make sure to use destination colorkeying, so that our OSD is visible */
		DFBCHECK(videolayer->GetDescription(videolayer, &desc));
		dlc.flags = (DFBDisplayLayerConfigFlags) DLCONF_OPTIONS;
		dlc.options = (DFBDisplayLayerOptions) DLOP_DST_COLORKEY;
		DFBCHECK(videolayer->SetConfiguration(videolayer, &dlc));
		DFBCHECK(videolayer->SetDstColorKey(videolayer, COLORKEY));

		DFBCHECK(videolayer->GetSurface(videolayer, &surface));
		DFBCHECK(surface->Clear(surface, COLORKEY, 0xff));
		DFBCHECK(surface->Flip(surface, 0, (DFBSurfaceFlipFlags)DSFLIP_ONSYNC));
	}
}

cSoftMPEG::~cSoftMPEG()
{
	fprintf(stderr, "[softmpeg] ~cSoftMPEG\n");
	softmpeg_decoder_destroy(d);
	dfb_destroy();
}

void cSoftMPEG::MakePrimaryDevice(bool On)
{
	fprintf(stderr, "[softmpeg] MakePrimaryDevice\n");
	new cSoftOsdProvider();
}

bool cSoftMPEG::HasDecoder(void) const {
	return true;
}

bool cSoftMPEG::CanReplay(void) const 
{
	fprintf(stderr, "[softmpeg] CanReplay()\n");
	return true;		// We can replay
}

bool cSoftMPEG::SetPlayMode(ePlayMode PlayMode)
{
	fprintf(stderr, "[softmpeg] SetPlayMode()\n");

	switch (PlayMode) {
	case 0: {
		softmpeg_decoder_stop(d);
		break;
	}
	case 1: {
		break;
	}
	default:
		/*stop */
		break;

	}
	fprintf(stderr, "setting Playmode to %d\n", PlayMode);
	return true;
}

void cSoftMPEG::TrickSpeed(int Speed)
{
	fprintf(stderr, "[softmpeg] Trickspeed %d\n",Speed);
}

void cSoftMPEG::Clear(void)
{
	fprintf(stderr, "[softmpeg] Clear\n");
}

void cSoftMPEG::Play(void)
{
	fprintf(stderr, "[softmpeg] Play\n");
}

void cSoftMPEG::Freeze(void)
{
	fprintf(stderr, "[softmpeg] Freeze\n");
}

void cSoftMPEG::Mute(void)
{
	fprintf(stderr, "[softmpeg] Mute\n");
}

void cSoftMPEG::StillPicture(const uchar * Data, int Length)
{
	fprintf(stderr, "[softmpeg] StillPicture\n");
}

#include <time.h>
bool cSoftMPEG::Poll(cPoller & Poller, int TimeoutMs)
{
/*
	fprintf(stderr, "[softmpeg] Poll, TimeoutMs:%d\n",TimeoutMs);
	struct timespec req;
	req.tv_sec = 0;
	req.tv_nsec = 20*1000*1000;
	nanosleep(&req, NULL);
*/
	return true;
}

int cSoftMPEG::ProvidesCa(const cChannel *Channel) const
{
	fprintf(stderr, "[softmpeg] ProvidesCa\n");
	return 0;
}

int cSoftMPEG::PlayVideo(const uchar * Data, int Length)
{
	int delay = 0;
//	fprintf(stderr, "[softmpeg] PlayVideo: %d bytes\n",Length);
	delay = softmpeg_decoder_process_pes_data(d,(unsigned char*)Data,Length);
	if (0 != delay) {
		struct timespec req;
		req.tv_sec = delay / 1000;
		req.tv_nsec = (delay % 1000) * 1000 * 1000;
		fprintf(stderr,"delaying %d ms\n",delay);
		nanosleep(&req, NULL);
	}
	return Length;
}

// --- cPluginSoftMPEG ----------------------------------------------------------

cPluginSoftMPEG::cPluginSoftMPEG(void)
{
	// Initialize any member variables here.
	// DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
	// VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
}

cPluginSoftMPEG::~cPluginSoftMPEG()
{
	// Clean up after yourself!
}

const char *cPluginSoftMPEG::CommandLineHelp(void)
{
	// Return a string that describes all known command line options.
	return "Softmpeg";
}

bool cPluginSoftMPEG::ProcessArgs(int argc, char *argv[])
{
	// Implement command line argument processing here if applicable.
	return true;
}

bool cPluginSoftMPEG::Start(void)
{
	// Start any background activities the plugin shall perform.
	RegisterI18n(Phrases);
	return true;
}

bool cPluginSoftMPEG::Initialize(void)
{
	// Start any background activities the plugin shall perform.
	fprintf(stderr, "[softmpeg] initializing Plugin\n");
	new cSoftMPEG();
	return true;
}

void cPluginSoftMPEG::Housekeeping(void)
{
	// Perform any cleanup or other regular tasks.
}

cOsdObject *cPluginSoftMPEG::MainMenuAction(void)
{
	// Perform the action when selected from the main VDR menu.
	return NULL;
}

cMenuSetupPage *cPluginSoftMPEG::SetupMenu(void)
{
	// Return a setup menu in case the plugin supports one.
	return NULL;
}

bool cPluginSoftMPEG::SetupParse(const char *Name, const char *Value)
{
	// Parse your own setup parameters and store their values.
	return false;
}

VDRPLUGINCREATOR(cPluginSoftMPEG);	// Don't touch this!
