/*$cb
 *    This file is a part of OpenMHP VDR plugin.
 *
 *    Copyright (C) 2005 Mikko Novaro <miknov@gmail.com>
 *
 *    See README file for licensing and contact information.
 $ce*/

#include <math.h>
#include <sys/ipc.h>

#include "osd.h"
#include "macros.h"
#include "keymapper.h"
#include "colors.h"
#include "setup.h"

#define WIDTH  720.0
#define HEIGHT 576.0

cOpenmhpOsd::cOpenmhpOsd(cPluginOpenmhp *OPENMHP, cXlet *xlt) {
  openmhp = OPENMHP;
  sem = new cSemaphore(ftok(OPENMHP_PATHNAME,0));
  // mutex = new cMutex();

  xlet = xlt;
  xlet->RegisterObserver(IMAGE_UPDATE, this);
  xlet->RegisterObserver(VIDEO_SCALE, this);
  bitdepth = atoi(cMenuSetupOpenmhp::bitdepthChoices[openmhp->setup->OsdBitdepth]);
  valuesPerColorComp = (int)(sqrt(sqrt(pow(2,bitdepth))));
  scale = openmhp->setup->OsdScale/100.0;
  scaledWidth = (int)(scale*WIDTH);
  scaledHeight = (int)(scale*HEIGHT);
  xOffset = (int)((WIDTH - scaledWidth) / 2);
  yOffset = (int)((HEIGHT - scaledHeight) / 2);
  vidWinX1 = 0; vidWinY1 = 0; vidWinX2 = 719; vidWinY2 = 575;
  xletthread =
    new cXletThread(xlet,
		    cMenuSetupOpenmhp::virtualXServerChoices[openmhp->setup->VirtualXServer],
		    openmhp->setup->VirtualXDisplay,
		    cMenuSetupOpenmhp::openmhpOutputChoices[openmhp->setup->OpenmhpOutput]);
}


cOpenmhpOsd::~cOpenmhpOsd() {
  // mutex->Lock();
  sem->Enter(); // prevent deleting while painting
  xletthread->Stop();
  delete xletthread;
  xletthread = NULL;
  delete imgbuff;
  imgbuff = NULL;
  delete osd;
  osd = NULL;
  delete xlet;
  xlet = NULL;
  sem->Leave();
  delete sem;
  // mutex->Unlock();
  // delete mutex;
}

void cOpenmhpOsd::Show(void)
{
  osd = cOsdProvider::NewOsd(xOffset, yOffset);
  if (osd) {
    tArea Area = { 0, 0, scaledWidth, scaledHeight,  bitdepth};
    if (osd->CanHandleAreas(&Area, 1) == oeOk) {
      osd->SetAreas(&Area, 1);
      const cFont *font = cFont::GetFont(fontOsd);
      osd->DrawRectangle(0,0,(int)(WIDTH-1),(int)(HEIGHT-1),clrTransparent);
      osd->DrawText(20, 20, tr("Loading OpenMHP..."), clrWhite, clrGray50, font);

      osd->Flush();
      imgbuff = new cBitmap(scaledWidth, scaledHeight, bitdepth, 0, 0);
      xletthread->Start();
      DBG("xlet started in a new thread.");
    } else {
      // TODO: display this on screen
      ERR(tr("Display device didn't support the selected OSD settings."));
      delete osd;
      osd = NULL;
    }
  }
}

eOSState cOpenmhpOsd::ProcessKey(eKeys Key)
{
  eOSState state = cOsdObject::ProcessKey(Key);
  if (state == osUnknown & Key != kNone) {

    int mhpKey = KeyMapper::Map(Key);
    if (mhpKey != -1) xlet->SendKeyEvent(mhpKey);
    
    state = osContinue;
  }

  return state;
}

// ------------------- XLET EVENT HANDLING -----------------------------

void cOpenmhpOsd::EventReceived(eEventType type, const cXletEvent *event) {
  DBG("got event type %i", type);
  if (type == VIDEO_SCALE) {
    cVideoScaleEvent *vse = (cVideoScaleEvent *)event;
    DBG("video scale event : %ix%ix%ix%i", vse->posx, vse->posy,
	vse->width, vse->height);
    if (vse->posx == 0 &
	vse->posy == 0 &
	vse->height == (int)HEIGHT &
	vse->width  == (int)WIDTH) {
      // if video size is full screen, do not modify by scaling
      // TODO: replace by setting vidWin.bpp to unscaled "color".
      vidWinX1 = 0; vidWinY1 = 0;
      vidWinX2 = (int)WIDTH-1; vidWinY2 = (int)HEIGHT-1;
    }
    else {
      vidWinX1 = xOffset + (int)(vse->posx * scale) -1;
      vidWinY1 = yOffset + (int)(vse->posy * scale) -1;
      vidWinX2 = xOffset + (int)((vse->width + vse->posx) * scale) -1;
      vidWinY2 = yOffset + (int)((vse->height + vse->posy) * scale) -1;
    }
#ifdef ENABLE_VIDEO_SCALING
    // commit scaling (method copied from yaepg plugin)
    osd->vidWin.x1 = vidWinX1;
    osd->vidWin.y1 = vidWinY1;
    osd->vidWin.x2 = vidWinX2;
    osd->vidWin.y2 = vidWinY2;
    osd->vidWin.bpp = 12;
    osd->Flush();
#endif
    return;
  }

  if (type == IMAGE_UPDATE) {
    // prevent drawing while osd is deleted, and vice versa.
    // return immediately if couldn't enter.
    if (sem->Enter(false) == false) return;
    /* NOTE: Locking is blocked only when 1) osd is about to be
       deleted, and after that, nothing should be done anymore, or 2)
       when updating a previous image, and image updates probably
       shouldn't be stacked. */
    //  mutex->Lock();

    /*
    // discard, if not graphics layer:
    cImageUpdateEvent *iue = (cImageUpdateEvent *)event;
    if (iue->layerindex > 0) {
      sem->Leave();
      return;
    }
    */

    int actX, actY; // unscaled layer coordinates
    int val, grphVal, backVal; // background and graphics layer color values
    double grphAlpha;
    if (osd && imgbuff) {
      for (int y=0; y<scaledHeight; y++) {
	for (int x=0; x<scaledWidth; x++) {
	  actX = (int)(x/scale);
	  actY = (int)(y/scale);
	  grphVal = xlet->GetPixel(actX, actY, 0);
	  backVal = 0;
	  /* We need background layer value only if graphics layer
	     pixel is transparent and the pixel is not behind the
	     video layer. */
	  grphAlpha = ((double)((grphVal & 0xff000000) >> 24)) / 255.0;
	  if (grphAlpha < 1.0 &
	      !(x >= vidWinX1 - xOffset &
		x <= vidWinX2 - xOffset &
		y >= vidWinY1 - yOffset &
		y <= vidWinY2 - yOffset))
	    backVal = xlet->GetPixel(actX, actY, 1);

	  if (backVal == 0) val = grphVal;
	  else {
	    if (grphAlpha == 0.0) val = backVal;
	    else {
	      /* Partly transparent graphics layer pixel with visible
		 background layer (i.e. not behind video) --> Mix
		 graphics and background layer according to alpha */
	      double grphR = (grphVal & 0x00ff0000) >> 16;
	      double grphG = (grphVal & 0x0000ff00) >> 8;
	      double grphB =  grphVal & 0x000000ff;
	      double backR = (backVal & 0x00ff0000) >> 16;
	      double backG = (backVal & 0x0000ff00) >> 8;
	      double backB =  backVal & 0x000000ff;
	      double backAlpha = 1.0 - grphAlpha;
	      int mixR = (int)(grphAlpha * grphR + backAlpha * backR);
	      int mixG = (int)(grphAlpha * grphG + backAlpha * backG);
	      int mixB = (int)(grphAlpha * grphB + backAlpha * backB);
	      int mixVal = 0xff000000 | (mixR << 16) | (mixG << 8) | mixB;
	      val = mixVal;
	    }
	  }
	  val = ReduceColors(val, valuesPerColorComp,
			     openmhp->setup->DitheringEnabled, scaledWidth);
	  imgbuff->DrawPixel(x,y,(tColor)val);
	}
      }
      osd->DrawBitmap(0,0,*imgbuff);
      osd->Flush();
    }
      
    sem->Leave();
    // mutex->Unlock();
  }
}
