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

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <locale.h>

#include <string>
#include <vector>
#include <sstream>

#include <string>
#include <utility>
#include <iostream>

#include "imlibrenderer.h"
#include "theme.h"
#include "common.h"

using std::string;
using std::cout;
using std::endl;

//***************************************************************************
// Object
//***************************************************************************

ImlibRenderer::ImlibRenderer(int x, int y, int width, int height, 
                             string cfgPath, int utf, string thmPath)
   : Renderer(x, y, width, height, cfgPath, utf, thmPath)
{
   Imlib_Context ctx = imlib_context_new();
   imlib_context_push(ctx);
	imlib_set_color_usage(256);

   // cache

   imlib_set_font_cache_size(4 * 1024 * 1024);
   flushCache();
 
   // new image

   _cur_image = imlib_create_image(themeWidth, themeHeight);
   imlib_context_set_image(_cur_image);
}

ImlibRenderer::~ImlibRenderer()
{
   if (_cur_image)
   {
      imlib_context_set_image(_cur_image);
      imlib_free_image();
   }
}

void ImlibRenderer::flushCache()
{
   // flush the font cache

   imlib_flush_font_cache();

   // and the image cache

   imlib_set_cache_size(0);
   imlib_set_cache_size(16 * 1024 * 1024);
}

int ImlibRenderer::init(int devnum)
{
   _render_image = imlib_create_image(dspWidth, dspHeight);

   return success;
}

//***************************************************************************
// Set Font Path
//***************************************************************************

void ImlibRenderer::setFontPath(string fntPath)
{
   int e, s;
   e = s = 0;
   string p;
   int count;
   char* path;
   const char** list;

   // first clear imlibs font path

   list = (const char**)imlib_list_font_path(&count);

   for (int i = 0; i < count; i++)
   {
      tell(3, "Info: Removing '%s' from font path", list[i]);
      imlib_remove_path_from_font_path(list[i]);
   }

   imlib_list_font_path(&count);

   // add all configured font paths

   tell(0, "Info: Font path configured to '%s'", fntPath.c_str());
   
   do
   {
      e = fntPath.find(':', s);
      p = fntPath.substr(s, e == na ? fntPath.length()-s : e-s);

      if (p[0] != '/')
      {
         // make path relative to the themes directory

         asprintf(&path, "%s/graphTFT/themes/%s/%s", 
                  confPath.c_str(), themePath.c_str(), p.c_str());
      }
      else
      {
         asprintf(&path, "%s", p.c_str());
      }

      tell(0, "Info: Adding font path '%s'", path);

      if (*path && fileExists(path))
         imlib_add_path_to_font_path(path);
      else
         tell(0, "Info: Font path '%s' not found, ignoring", path);
      
      free(path);
      s = e+1;

   } while (s > 0);
   
   
   // at least add the default path

   asprintf(&path, "%s/graphTFT/fonts/", confPath.c_str());
   tell(0, "Info: Adding font path '%s'", path);
   imlib_add_path_to_font_path(path);
   free(path);
}

//***************************************************************************
// Refresh
//***************************************************************************

void ImlibRenderer::refresh(int force)
{
   LogDuration ld("ImlibRenderer::refresh()");

   // resize only if needed !

   if (xOffset || yOffset || xBorder || yBorder 
      || themeWidth != dspWidth || themeHeight != dspHeight)
   {
      tell(2, "scale image from (%d/%d) to (%d/%d)", 
        themeWidth, themeHeight, 
        dspWidth - xOffset - (2 * xBorder), 
        dspHeight - yOffset - (2 * yBorder));
      
      imlib_context_set_image(_render_image);

      imlib_blend_image_onto_image(_cur_image, 0, 0, 0, themeWidth, themeHeight,
                                   xOffset + xBorder, yOffset + yBorder,
                                   dspWidth - xOffset - (2 * xBorder), 
                                   dspHeight - yOffset - (2 * yBorder));

      pImageToDisplay = &_render_image;
   }
   else
   {
      pImageToDisplay = &_cur_image;
   }
}

void ImlibRenderer::clear()
{
	// clear the current image

	imlib_context_set_image(_cur_image);
	imlib_free_image();
	_cur_image = imlib_create_image(themeWidth, themeHeight);
	imlib_context_set_image(_cur_image);
}


//***************************************************************************
// Image
//***************************************************************************

void ImlibRenderer::image(const char* fname, int x, int y, 
                          int width, int height, 
                          bool fit, bool aspectRatio)
{
   Imlib_Image new_image;
   int imgWidth = 0;
   int imgHeight = 0;
   std::ostringstream path;
   int areaWidth = width;
   int areaHeight = height;

   if (x == na) x = 0;
   if (y == na) y = 0;

   if (fname[0] == '/')
      path << fname;
   else
      path << confPath << "/graphTFT/themes/" << themePath << "/" << fname; 

   if (!fileExists(path.str().c_str()))
   { 
      tell(0, "Image '%s' not found", path.str().c_str());
      return ;
   }

   new_image = imlib_load_image(path.str().c_str());

   if (!new_image)
   { 
      tell(0, "The image '%s' could not be loaded", path.str().c_str());
      return ;
   }

   imlib_context_set_image(new_image);
   imgWidth = imlib_image_get_width();
   imgHeight = imlib_image_get_height();

   imlib_context_set_image(_cur_image);

   if (fit)
   {
      if (aspectRatio)
      {
         double ratio = (double)imgWidth / (double)imgHeight;

         if ((double)width/(double)imgWidth < (double)height/(double)imgHeight)
         {
            height = (int)((double)width / ratio);
            y += (areaHeight-height) / 2;
         }
         else
         {
            width = (int)((double)height * ratio);
            x += (areaWidth-width) / 2;
         }
      }

      imlib_blend_image_onto_image(new_image, 0, 0, 0, 
                                   imgWidth, imgHeight, x, y, 
                                   width, height);
   }
   else
   {
      imlib_blend_image_onto_image(new_image, 0, 0, 0, 
                                   imgWidth, imgHeight, x, y, 
                                   imgWidth, imgHeight);
   }
  
   imlib_context_set_image(new_image);
   imlib_free_image();
   imlib_context_set_image(_cur_image);
}

//***************************************************************************
// Image Part
//***************************************************************************

void ImlibRenderer::imagePart(const char* fname, int x, int y, 
                              int width, int height)
{
   Imlib_Image new_image;
   std::ostringstream path;

   if (x == na) x = 0;
   if (y == na) y = 0;

   if (fname[0] == '/')
      path << fname;
   else
      path << confPath << "/graphTFT/themes/" << themePath << "/" << fname; 

   if (!fileExists(path.str().c_str()))
   { 
      tell(0, "Image '%s' not found", path.str().c_str());
      return ;
   }

   new_image = imlib_load_image(path.str().c_str());

   if (!new_image)
   { 
      tell(0, "The image '%s' could not be loaded", path.str().c_str());
      return ;
   }

   imlib_context_set_image(_cur_image);

   imlib_blend_image_onto_image(new_image, 0, 
                                x, y, width, height,
                                x, y, width, height);
  
   imlib_context_set_image(new_image);
   imlib_free_image();
   imlib_context_set_image(_cur_image);
}

//***************************************************************************
// Text Width Of
//***************************************************************************

int ImlibRenderer::textWidthOf(const char* text, const char* fontName, 
                               int fontSize)
{
   Imlib_Font font;
   int width = 20;
   int height = 20;

   if (!fontName || !text || !strlen(text))
      return 1;

   std::ostringstream font_name_size;
	font_name_size <<  fontName << "/" << fontSize;

   font = imlib_load_font((font_name_size.str()).c_str());

   if (font)
   {
      imlib_context_set_font(font);
		imlib_context_set_image(_cur_image);
      imlib_get_text_size(text, &width, &height);
      imlib_free_font();

      width /= strlen(text);
   }  
	else
		tell(1, "The font '%s' could not be loaded.", fontName);

   return width;
}

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

int ImlibRenderer::charWidthOf(const char* fontName, int fontSize)
{
   Imlib_Font font;
   int width = 20;
   int height = 20;

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

   if (!fontName)
   {
      imlib_get_text_size(text, &width, &height);
      return width / strlen(text);
   }

   std::ostringstream font_name_size;
	font_name_size <<  fontName << "/" << fontSize;

   font = imlib_load_font((font_name_size.str()).c_str());

   if (font)
   {
      imlib_context_set_font(font);
		imlib_context_set_image(_cur_image);
      imlib_get_text_size(text, &width, &height);
      imlib_free_font();

      width /= strlen(text);
   }  
	else
		tell(1, "The font '%s' could not be loaded.", fontName);

   return width;
}

//***************************************************************************
// Line Count
//***************************************************************************

int ImlibRenderer::lineCount(const char* text, const char* font_name, 
                             int size, int width)
{
   int count;
   int lineHeight, textWidth, dummy;
   string line;
   string tmp = text;
   Imlib_Font font;
   int currentWidth;
   int blankWidth, dotsWidth;
   unsigned int pos = 0;

   // load font

   std::ostringstream font_name_size;
   font_name_size <<  font_name << "/" << size;

   if (!(font = imlib_load_font((font_name_size.str()).c_str())))
   {
      tell(1, "The font '%s' could not be loaded.", font_name);
      return 0;
   }

   if (width <= 0)
      width = themeWidth;          // ;)

   imlib_context_set_font(font);
   imlib_context_set_image(_cur_image);

   imlib_get_text_size(tmp.c_str(), &textWidth, &lineHeight);
   imlib_get_text_size(" ", &blankWidth, &dummy);
   imlib_get_text_size("...", &dotsWidth, &dummy);

   for (count = 1; pos < tmp.length(); count++)
   {
      string token;
      line = "";

      currentWidth = 0;

      // fill next line ...

      while (pos < tmp.length())
      {
         int tokenWidth;
         int lf = no;

         int pA = tmp.find_first_of(" \n", pos);

         if (tmp[pA] == '\n')
         {
            lf = yes;
            tmp[pA] = ' ';
         }

         token = tmp.substr(pos, pA - pos);

         if (token == "")
         {
            line += " ";
            pos++;

            if (lf)
               break;
            else
               continue;
         }

         imlib_get_text_size(token.c_str(), &tokenWidth, &dummy);
         
         if (currentWidth + (currentWidth ? blankWidth : 0) + tokenWidth > width)
         {
            // passt nicht mehr ganz rein

            if (!line.length())
            {
               // alleinstehendes Wort -> noch zur Zeile rechnen

               pos += token.length();
            }

            break;
         }
         else
         {
            // passt noch rein

            line = line + token;
            imlib_get_text_size(line.c_str(), &currentWidth, &dummy);
            pos += token.length();

            if (lf)
               break;
         }
      } 
   }

   imlib_free_font();

   return count;
}

//***************************************************************************
// Draw Text
//***************************************************************************

int ImlibRenderer::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)
{
   string line;
   string tmp;
   int lineHeight, textWidth, dummy;
   int blankWidth;
   int dotsWidth;
   unsigned int pos;
   int currentWidth;
   Imlib_Font font;

   if (x == na) x = 0;
   if (y == na) y = 0;

   if (Str::isEmpty(text))
      return 0;
 
   if (utf8)
   {
      const int maxBuf = 10000;
      char out[maxBuf+TB];

      if (toUTF8(out, maxBuf, text) == success)
         tmp = out;
      else
         tmp = text;
   }
   else
      tmp = text;

   // load font

   std::ostringstream font_name_size;
   font_name_size <<  font_name << "/" << size;

   if (!(font = imlib_load_font((font_name_size.str()).c_str())))
   {
      tell(1, "The font '%s' could not be loaded.", font_name);
      return 0;
   }

   // tell(0, "'%s' (%d/%d)", font_name, 
   //     imlib_get_font_ascent(), imlib_get_font_descent());

   if (width <= 0)
      width = themeWidth;          // ;)

   imlib_context_set_font(font);
   imlib_context_set_image(_cur_image);
   imlib_context_set_color(r, g, b, 255);

   imlib_get_text_size(tmp.c_str(), &textWidth, &lineHeight);
   imlib_get_text_size(" ", &blankWidth, &dummy);
   imlib_get_text_size("...", &dotsWidth, &dummy);

   if (!lines)
      lines = height / lineHeight;

   if (lines <= 0)
      lines = 1;

   pos = 0;
   int tl = 1;

   for (int l = 1; l <= lines && pos < tmp.length(); l++, tl++)
   {
      int yPos = y + lineHeight * (l-1);
      string token;
      line = "";

      currentWidth = 0;

      while (pos < tmp.length())
      {
         int tokenWidth;
         int lf = no;

         int pA = tmp.find_first_of(" \n", pos);

         if (tmp[pA] == '\n')
         {
            lf = yes;
            tmp[pA] = ' ';
         }

         token = tmp.substr(pos, pA - pos);

         if (token == "")
         {
            line += " ";
            pos++;

            if (lf)
               break;
            else
               continue;
         }

         imlib_get_text_size(token.c_str(), &tokenWidth, &dummy);
         
         if (currentWidth + (currentWidth ? blankWidth : 0) + tokenWidth > width)
         {
            // passt nicht mehr ganz rein

            if (!line.length() || l == lines)
            {
               // alleinstehendes Wort oder letzte Zeile,
               // daher Abgeschnitten anzeigen

               unsigned int i = 0;

               while (currentWidth + (dots ? dotsWidth : 0) < width && i <= token.length())
               {
                  line += token.substr(i++, 1);
                  imlib_get_text_size(line.c_str(), &currentWidth, &dummy);
               }

               // einer zuviel !

               line = line.substr(0, line.length()-1);

               if (dots)
               {
                  if (currentWidth + dotsWidth > width)
                     line = line.substr(0, line.length()-1);
                  
                  line += "...";
               }

               pos += token.length();
            }

            break;
         }
         else
         {
            // passt noch rein

            // currentWidth += blankWidth + tokenWidth;  // nicht genau
            // -> so ist's besser

            line = line + token;
            imlib_get_text_size(line.c_str(), &currentWidth, &dummy);
            pos += token.length();

            if (lf)
               break;
         }
      } 

      if (skipLines && tl <= skipLines)
      {
         l--;
         continue;
      }

      if (align != 0)
         imlib_get_text_size(line.c_str(), &textWidth, &lineHeight);

      if (align == 0)                // left
         imlib_text_draw(x, yPos, line.c_str());
      else if (align == 1)           // center
         imlib_text_draw(x + (width - textWidth) / 2, yPos, line.c_str());
      else                           // right
         imlib_text_draw(x + width - textWidth -2, yPos, line.c_str());
   }

   imlib_free_font();

   return 0;
}

//***************************************************************************
// Draw Rectangle
//***************************************************************************

void ImlibRenderer::rectangle(int x, int y, 
                              int width, int height, 
                              int r, int g, int b, 
                              int tr) 
{
   if (x == na) x = 0;
   if (y == na) y = 0;

	imlib_context_set_image(_cur_image);
	imlib_context_set_color(r, g, b, tr);
	imlib_image_fill_rectangle(x, y, width, height);
}

//***************************************************************************
// Dump Image To File
//***************************************************************************

void ImlibRenderer::dumpImage2File(const char* fname, int dumpWidth, 
                                   int dumpHeight, const char* aPath)
{
	std::ostringstream path; 
   const char* format;

   if (aPath && *aPath)
      path << aPath;
   else
      path << "/tmp";
   
   format = strchr(fname, '.') + 1;

	// check if local directory structure exist, else create it.

  	if (!fileExists(path.str().c_str())) 
      mkdir(path.str().c_str(), 0777);

	// an image handle

  	Imlib_Image new_image = imlib_create_image(dumpWidth, dumpHeight);

	// get width and heigt

   imlib_context_set_image(*pImageToDisplay);
	int width = imlib_image_get_width();
	int height = imlib_image_get_height();

	imlib_context_set_image(new_image);
	imlib_blend_image_onto_image(*pImageToDisplay, 0, 0, 0, width, height, 
                                0, 0, dumpWidth, dumpHeight);

	// save the image

	imlib_image_set_format(format && *format ? format : "png");
	path << "/" << fname;
	imlib_save_image(path.str().c_str());

	imlib_free_image();
	imlib_context_set_image(*pImageToDisplay);
}

//***************************************************************************
// Get Image Buffer
//***************************************************************************

int ImlibRenderer::getImageBuffer(const void*& buffer, int quality)
{
   int size = 0;

#ifdef HAVE_IMAGE_MAGICK

   int w, h;
   DATA32* p;

   imlib_context_set_image(_cur_image);
   
   p = imlib_image_get_data_for_reading_only();
   w = imlib_image_get_width();
   h = imlib_image_get_height();
   
   if (p)
   {
      Image image(w, h, "BGRA", CharPixel, p);
      
      image.magick("JPEG");       // set format
      image.quality(quality);     // set quality
      image.write(&blob);
      
      buffer = blob.data();
      size = blob.length();
   }

#endif

   return size;
}
