/**
 *  GraphTFT plugin for the Video Disk Recorder 
 * 
 *  dfbrenderer.c - A plugin for the Video Disk Recorder
 *
 *  (c) 2004 Lars Tegeler, Sascha Volkenandt  
 *
 * This code is distributed under the terms and conditions of the
 * GNU GENERAL PUBLIC LICENSE. See the file COPYING for details.
 *
 * $Id: dfbrenderer.c,v 1.9 2007/12/03 19:58:30 root Exp $
 *
 **/

//The most part of this Code is from the softmpeg Plugin (part of the libsoftmpeg Project) witch is under LGPL:

#include <stdlib.h>

#include "directfb.h"
#include "dfbrenderer.h"
#include "cache.h"
#include "common.h"

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

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

static IDirectFBSurface *info_surface = NULL;

static DFBDisplayLayerID layerid = (DFBDisplayLayerID)-1;

#ifdef HAVE_SOFTMPEG
struct softmpeg_decoder *sdec = NULL;
#endif

int videolayer_is_overlay;

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, int width, int height)
{
   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 = (DFBDisplayLayerID)-1;
   }

   if (layerid == (DFBDisplayLayerID)-1)
   {
      DFBWindowDescription windowdesc;
      tell(4 , "dfbdevout.c: 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
   {
      tell(4 , "dfbdevout.c: using videolayer\n");
   }

   DFBSurfaceDescription dsc;
   dsc.flags = (DFBSurfaceDescriptionFlags)(DSDESC_CAPS | DSDESC_WIDTH | DSDESC_HEIGHT);
   dsc.caps = (DFBSurfaceCapabilities)(DSCAPS_PRIMARY | DSCAPS_FLIPPING);
   dsc.width = width;
   dsc.height = height;
   DFBCHECK(dfb->CreateSurface(dfb, &dsc, &info_surface));

   return 0;
}

static void dfb_destroy(void)
{
   // delete images and fonts
   imageCache.Clear();
   fontCache.Clear();

   if (info_surface)
      info_surface->Release(info_surface);

   if (window)
      window->Release(window);

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

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



//----------------------------------------------------------------------
void DFbRenderer::deinit()
{
   tell(4 , "dfbdevout.c: deinit\n");
   if (_initialized == -1)
      return;

#ifdef HAVE_SOFTMPEG
   if (sdec)
      softmpeg_decoder_destroy(sdec);
#endif
   dfb_destroy();

   _initialized = -1;
}

//----------------------------------------------------------------------
int DFbRenderer::init(int devnum)
{
#ifdef HAVE_SOFTMPEG
   struct softmpeg_directfb_initdata init;
#endif
   tell(4,"dfbdevout.c: Init\n");
   if (NULL != getenv("DIRECTFB_VIDEO_IS_OVERLAY"))
   {
      videolayer_is_overlay = 1;
   }
   else
      videolayer_is_overlay = 0;

   dfb_init(NULL, NULL, dspWidth, dspHeight);
#ifdef HAVE_SOFTMPEG
   init.window_id = windowid;
   init.layer_id = layerid;
   init.width = dspWidth;
   init.height = dspHeight;

   sdec = softmpeg_decoder_create(NULL, NULL, SOFTMPEG_DIRECTFB, &init, SOFTMPEG_FUSIONSOUND, NULL);
#endif

   if (layerid != (DFBDisplayLayerID)-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));
   }
   _initialized = 1;

   return 0;
}

void DFbRenderer::transform(int &x, int &y)
{
   tell(4 , "dfbdevout.c: transform(2)\n");
   x = x*dspWidth/themeWidth;
   y = y*dspHeight/themeHeight;
   tell(5 , "dfbdevout.c: exit transform(2)\n");
}

void DFbRenderer::transform(int &x, int &y, int &w, int &h)
{
   tell(4 , "dfbdevout.c: transform(4)\n");
   x = x*dspWidth/themeWidth;
   y = y*dspWidth/themeHeight;
   w = w*dspWidth/themeWidth;
   h = h*dspWidth/themeHeight;
}

//----------------------------------------------------------------------

DFbRenderer::DFbRenderer(int x, int y, int width, int height, string cfgPath, 
                         int utf, string thmPath)
   : Renderer(x, y, width, height, cfgPath, utf, thmPath)
{
   _initialized = -1;
   tell(4 , "dfbdevout.c: DFbRenderer()\n");
}

DFbRenderer::~DFbRenderer()
{
   tell(4 , "dfbdevout.c: ~DFbRenderer()\n");
   deinit();
}

//-----------------------------------------------------------------------
void DFbRenderer::refresh()
{
   tell(4 , "dfbdevout.c: refresh()\n");
   DFBCHECK(info_surface->Flip(info_surface, 0, (DFBSurfaceFlipFlags)DSFLIP_ONSYNC));
   tell(5 , "dfbdevout.c: leave refresh()\n");
}

void DFbRenderer::clear()
{
   tell(4 , "dfbdevout.c: clear()\n");
   DFBCHECK(info_surface->Clear(info_surface, 0, 0, 0, 0));
   tell(5 , "dfbdevout.c: leave clear()\n");
}


void DFbRenderer::setPlayMode(bool Video)
{
   tell(4 , "dfbdevout.c: SetPlayMode()\n");
   _showImage = !Video;

   if (Video)
   {
      ;//info_surface->SetOpacity(info_surface, 0xff);
#ifdef HAVE_SOFTMPEG
#else
#ifdef HAVE_IMLIB
      _showImage = no;
      //_decoder = new cMpeg2Decoder(frame_buffer,PIX_FMT_YUV, dspWidth, dspHeight);
      //_decoder->Start();
#endif
#endif

   }
   else
   {
      ;//info_surface->SetOpacity(info_surface, 0x00);
#ifdef HAVE_SOFTMPEG
      if (sdec) softmpeg_decoder_stop(sdec);
#else
#ifdef HAVE_IMLIB
      //if (_decoder){
      // _decoder->Stop();
      // delete(_decoder);
      //}
#endif
#endif
   }
}

int DFbRenderer::playVideo(const uchar *Data, int Length)
{
   tell(7 , "dfbdevout.c: playVideo()\n");
#ifdef HAVE_SOFTMPEG
   if (sdec)
   {
      int delay = 0;
      tell(8 , "dfbdevout.c: PlayVideo: %d bytes\n",Length);
      delay = softmpeg_decoder_process_pes_data(sdec,(unsigned char*)Data,Length);
      if (0 != delay)
      {
         struct timespec req;
         req.tv_sec = delay / 1000;
         req.tv_nsec = (delay % 1000) * 1000 * 1000;
         tell(4 , "dfbdevout.c: PlayVideo delaying %d ms\n",delay);
         nanosleep(&req, NULL);
      }
      return Length;
   }
#else
#ifdef HAVE_IMLIB
   /*
   int result=_decoder->Decode(Data, Length);
   // restart the decoder
   if (result == -1) {
    delete(_decoder);
    _decoder= new cMpeg2Decoder(frame_buffer,PIX_FMT_YUV, dspWidth, dspHeight);
    return 0;
}
   return Length;
   */
#endif
#endif
   return 0;
}

bool DFbRenderer::devicePoll(cPoller &Poller, int TimeoutMs)
{
   tell(7 , "dfbdevout.c: devicePoll()\n");
   // For now lets say we always ready
   return true;
}

void DFbRenderer::deviceClear(void)
{
   tell(4 , "dfbdevout.c: deviceClear()\n");
   //Fill Framebuffer with zero
   clear();
}

void DFbRenderer::image(const char* fname, int x, int y,
                        int coverwidth, int coverheight, 
                        bool fit, bool aspectRatio)
{
   char* fpath;
   IDirectFBImageProvider* image;
   IDirectFBSurface* render_surface = 0;

   if (!fname)
      return;

   if (!imageCache.Contains(fname))
   {
      if (fname[0] == '/')
         asprintf(&fpath, "%s", fname);
      else
      {
         const char* tmp = themePath.c_str();
         asprintf(&fpath, "%s/graphTFT/themes/%s/%s", confPath.c_str(), tmp, fname);
      }

      tell(4,"creating imageprovider for %s\n", fpath);

      if (dfb->CreateImageProvider(dfb, fpath, &image) == 0)
      {
         DFBSurfaceDescription image_desc;
         DFBRectangle rect;
         DFBCHECK(image->GetSurfaceDescription(image, &image_desc));
         transform(x, y, image_desc.width, image_desc.height);
         DFBCHECK(dfb->CreateSurface(dfb, &image_desc, &render_surface));
         rect.x = 0; rect.y = 0;
         rect.w = image_desc.width;
         rect.h = image_desc.height;
         DFBCHECK(image->RenderTo(image, render_surface, &rect));
         image->Release(image);
         imageCache[fname] = render_surface;
      }
      free(fpath);
   }
   else
   {
      render_surface = imageCache[fname];
      transform(x, y);
   }

   if (render_surface)
   {
      DFBCHECK(info_surface->SetBlittingFlags(info_surface, DSBLIT_BLEND_ALPHACHANNEL));
      DFBCHECK(info_surface->Blit(info_surface, render_surface, NULL, x, y));
   }
}

//***************************************************************************
// Char Width Of
//***************************************************************************

int DFbRenderer::charWidthOf(const char* fontName , int fontSize)
{ 
   IDirectFBFont* font = 0;
   int width = 20;
   DFBFontDescription desc;
   char* fpath;

   const char* text = "We need here something like a "
      "representive sentence WITH SOME UPPER CASE LETTERS";

   if (!fontCache.Contains(FontFaceSize(fontName, fontSize)))
   {
      asprintf(&fpath, "%s/graphTFT/fonts/%s.ttf", confPath.c_str(), fontName);

      tell(4,"creating font for %s\n", fpath);

      desc.flags = DFDESC_HEIGHT;
      desc.height = fontSize*4/3;
      transform(desc.width, desc.height);

      if (dfb->CreateFont(dfb, fpath, &desc, &font) == 0)
         fontCache[FontFaceSize(fontName, fontSize)] = font;

      free(fpath);
   }
   else
      font = fontCache[FontFaceSize(fontName, fontSize)];

   if (font)
   {
      int textWidth;

      DFBCHECK(info_surface->SetFont(info_surface, font));
      DFBCHECK(font->GetStringWidth(font, text, -1, &textWidth));

      return textWidth / strlen(text);
   }

   return width;
}

//***************************************************************************
// Text
//***************************************************************************

int DFbRenderer::text(const char* text, 
                      const char* font_name, int size, int align, 
                      int x, int y,
                      int r, int g, int b, 
                      int width, int height, int lines, 
                      int dots, int skipLines)
{
   IDirectFBFont* font;
   DFBFontDescription desc;
   char* fpath;
   int lineCount;

   if (Str::isEmpty(text))
      return 0;
   
   tell(4, "dfbdevout.c: text()\n");

   if (utf8)
   {
      const int maxBuf = 10000;
      char out[maxBuf+TB]; *out = 0;
       
      if (toUTF8(out, maxBuf, text) == success)
         text = out;
   }

   if (!fontCache.Contains(FontFaceSize(font_name, size)))
   {
      asprintf(&fpath, "%s/graphTFT/fonts/%s.ttf", confPath.c_str(), font_name);

      tell(4,"creating font for %s\n", fpath);

      desc.flags = DFDESC_HEIGHT;
      desc.height = size*4/3;
      transform(desc.width, desc.height);

      if (dfb->CreateFont(dfb, fpath, &desc, &font) == 0)
         fontCache[FontFaceSize(font_name, size)] = font;

      free(fpath);
   }
   else
      font = fontCache[FontFaceSize(font_name, size)];

   if (font)
   {
      int lineHeight, textWidth, pos, index, maxLines;
      string tmp = text, tmp2;
      DFBRegion rgn;

      transform(x, y, width, height);
      DFBCHECK(info_surface->SetColor(info_surface, r, g, b, 0xff));
      rgn.x1 = x; rgn.y1 = y; rgn.x2 = x+width-1, rgn.y2 = y+height-1;
      DFBCHECK(info_surface->SetClip(info_surface, &rgn));
      DFBCHECK(info_surface->SetFont(info_surface, font));
      DFBCHECK(font->GetHeight(font, &lineHeight));
      DFBCHECK(font->GetStringWidth(font, text, -1, &textWidth));

      if (lines != 0)
         maxLines = lines;
      else
         maxLines = height / lineHeight;

      lineCount = 1;

      while (textWidth > width && textWidth > 0 && lineCount <= maxLines)
      {
         int w3 = 0;

         for (index = 1; w3 < width; ++index)
         {
            string tmp3 = tmp.substr(0, index);
            DFBCHECK(font->GetStringWidth(font, tmp3.c_str(), -1, &w3));
         }

         tmp2 = tmp.substr(0, index+1);
         pos = tmp2.rfind(' ');

         if (pos != -1 && pos != 0)
         {
            tmp2.resize(pos);
            tmp = tmp.substr(pos+1);
         }
         else
         {
            tmp2.resize(index-2);
            tmp = tmp.substr(index-2);
         }

         DFBCHECK(font->GetStringWidth(font, tmp2.c_str(), -1, &textWidth));

         if (align == 0)
            DFBCHECK(info_surface->DrawString(info_surface, tmp2.c_str(), -1, x, y + (lineHeight * (lineCount-1)), DSTF_TOP));
         else if (align == 1)
            DFBCHECK(info_surface->DrawString(info_surface, tmp2.c_str(), -1, x + (width - textWidth) / 2, y + (lineHeight * (lineCount-1)), DSTF_TOP));
         else
            DFBCHECK(info_surface->DrawString(info_surface, tmp2.c_str(), -1, x + width - textWidth, y + (lineHeight * (lineCount-1)), DSTF_TOP));

         lineCount++;
         DFBCHECK(font->GetStringWidth(font, tmp.c_str(), -1, &textWidth));
      }

      if (lineCount <= maxLines && tmp.size() > 0)
      {
         if (align == 0)
            DFBCHECK(info_surface->DrawString(info_surface, tmp.c_str(), -1, x, y + (lineHeight * (lineCount-1)), DSTF_TOP));
         else if (align == 1)
            DFBCHECK(info_surface->DrawString(info_surface, tmp.c_str(), -1, x + (width - textWidth) / 2, y + (lineHeight * (lineCount-1)), DSTF_TOP));
         else
            DFBCHECK(info_surface->DrawString(info_surface, tmp.c_str(), -1, x + width - textWidth, y + (lineHeight * (lineCount-1)), DSTF_TOP));
      }
      else
         lineCount--;

      DFBCHECK(info_surface->SetFont(info_surface, NULL));
      DFBCHECK(info_surface->SetClip(info_surface, NULL));
   }

   return lineCount;
}

void DFbRenderer::rectangle(int x, int y, int width, int height, int r, int g, int b, int a)
{
   tell(4 , "dfbdevout.c: rectangle()\n");
   if (width == 0 || height == 0)
      return;
   tell(4,"drawing rectangle\n");
   transform(x, y, width, height);
   DFBCHECK(info_surface->SetColor(info_surface, r, g, b, a));
   DFBCHECK(info_surface->FillRectangle(info_surface, x, y, width, height));
}

void DFbRenderer::dumpImage2File(const char* fname, int dumpWidth, int dumpHeight, const char* aPath)
{
   std::string fileName;

   if (fname == NULL || fname[0] == '\0')
      fileName = "graphTFT";
   else
      fileName = fname;

   info_surface->Dump(info_surface, aPath ? aPath : "/tmp", fileName.c_str());
   tell(4 , "dfbrenderer.c: image dumped to %s/%s", aPath ? aPath : "/tmp", fileName.c_str());
}

