//***************************************************************************
// Group VDR/GraphTFT
// File graphtft.hpp
// Date 28.10.06 - Jörg Wendel
// This code is distributed under the terms and conditions of the
// GNU GENERAL PUBLIC LICENSE. See the file COPYING for details.
//--------------------------------------------------------------------------
// Class GrapTFT
//***************************************************************************

#include <QApplication>
#include <QMetaType>
#include <Magick++.h> 

#include <X11/Xutil.h>

#define XK_MISCELLANY
#include <X11/keysymdef.h>

#include <graphtft.hpp>

using namespace Magick; 

//#define _DEBUG

//***************************************************************************
// Class GraphTft
//***************************************************************************

int GraphTft::eloquence = eloOff;

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

GraphTft::GraphTft()
{
   // init

   pixelBuffer = 0;
   pixelBufferSize = 0;
   showHelp = false;
   resize = false;
   image = 0;
   hideCursor = false;
   managed = true;
   vdrWidth = 720;
   vdrHeight = 576;
   width = 720;
   height = 576;
   border = 0;
   *dump = 0;

   thread = new ComThread();

   // the defaults

   thread->setHost("vdr");
   thread->setPort(2039);

   // parse arguments ..

   for (int i = 0; i < QCoreApplication::arguments().size(); i++)
   {
      const char* arg = QCoreApplication::arguments().at(i).toAscii();

      if (arg[0] == '?' || (strcmp(arg, "--help") == 0))
      {
         showHelp = true;
         break;
      }
      
      if (arg[0] != '-' || strlen(arg) != 2)  continue;

      switch (arg[1])
      {
         case 'h': thread->setHost(QCoreApplication::arguments().at(i+1).toAscii());       break;
         case 'p': thread->setPort(atoi(QCoreApplication::arguments().at(i+1).toAscii())); break;
         case 'e': setEloquence(atoi(QCoreApplication::arguments().at(i+1).toAscii()));    break;
         case 'W': width = atoi(QCoreApplication::arguments().at(i+1).toAscii());          break;
         case 'H': height = atoi(QCoreApplication::arguments().at(i+1).toAscii());         break;
         case 'd': strcpy(dump, QCoreApplication::arguments().at(i+1).toAscii());          break;
         case 'n': managed = false;    break;
         case 'c': hideCursor = true;  break;
         case 'r': resize = true;      break;
      }
   }
}

GraphTft::~GraphTft()
{
   if (thread)
   {
      tell(eloAlways, "Stopping thread");

      thread->stop();

      if (!thread->wait(10000))
         tell(eloAlways, "Thread would not end!");
      else 
         tell(eloAlways, "Thread ended regularly");

      delete thread;
   }

   if (pixelBuffer) free(pixelBuffer);
}

//***************************************************************************
// Show Usage
//***************************************************************************

void GraphTft::showUsage()
{
   printf("Usage: graphtft-fe\n"                                        \
          "  Parameter:\n"                                              \
          "     -h <host>       vdr host no default, please specify\n"  \
          "     -p <port>       plugin port  (default 2039)\n"          \
          "     -e <eloquence>  log level    (default 0)\n"             \
          "     -W <width>      width        (default 720)\n"           \
          "     -H <height>     height       (default 576)\n"           \
          "     -d <file>       dump each image to file (default off)\n" \
          "     -n              not managed  (default managed)\n"       \
          "     -r              resize image (default off)\n"           \
          "     ?, --help       this help\n"                            \
      );
}

//***************************************************************************
// Start
//***************************************************************************

int GraphTft::start()
{
   if (showHelp)
   {
      showUsage();
      return 0;
   }

   if (init() != 0)
      return 1;
   
   run();
   exit();
   
   return 0;
}

//***************************************************************************
// init/exit
//***************************************************************************

int GraphTft::init()
{
   Visual* vis;
   Colormap cm;
   int depth;
   int screen;

   // init communication thread

   thread->setClient(this);
   thread->start();

   // init X 

   disp   = XOpenDisplay(0);

   screen = DefaultScreen(disp);
   vis    = DefaultVisual(disp, screen);
   depth  = DefaultDepth(disp, screen);
   cm     = DefaultColormap(disp, screen);

   // create simple window

   if (managed)
   {
      win = XCreateSimpleWindow(disp, DefaultRootWindow(disp), 
                                0, depth, width, height, 0, 0, 0);
   }
   else
   {
      // create window more complex

      // attributes

      XSetWindowAttributes windowAttributes;
   
      windowAttributes.border_pixel      = BlackPixel(disp, screen);
      windowAttributes.border_pixmap	  = CopyFromParent;
      windowAttributes.background_pixel  = WhitePixel(disp, screen);
      windowAttributes.override_redirect = True;
      windowAttributes.bit_gravity       = NorthWestGravity;
      windowAttributes.event_mask        = ButtonPressMask | ButtonReleaseMask | 
         KeyPressMask | ExposureMask | SubstructureNotifyMask;


      win = XCreateWindow(disp, RootWindow(disp, screen),
                          0, 0, width, height,
                          border, depth,
                          InputOutput,
                          vis,
                          CWBackPixel | CWBorderPixel | CWOverrideRedirect | CWBitGravity | CWEventMask,
                          &windowAttributes);

//   XSizeHints sizeHints;
//    sizeHints.flags = PPosition | PSize;
//    sizeHints.x = 0;
//    sizeHints.y = 0;
//    sizeHints.width = width;
//    sizeHints.height = height;
   
//    XSetNormalHints(disp, win, &sizeHints);

   }
   
   XSelectInput(disp, win, 
                ButtonPressMask   |
                ButtonReleaseMask | 
                PointerMotionMask | 
                KeyPressMask      |
                ClientMessage     |
                SubstructureNotifyMask |
                ExposureMask);          // events to receive


   XMapWindow(disp, win);               // show
   XFlush(disp);

   imlib_set_cache_size(2048 * 1024);
   imlib_set_color_usage(256);

   imlib_context_set_dither(1);         // dither for depths < 24bpp 
   imlib_context_set_display(disp);     // set the display
   imlib_context_set_visual(vis);       // visual,
   imlib_context_set_colormap(cm);      // colormap
   imlib_context_set_drawable(win);     // and drawable we are using 

   return 0;
}

int GraphTft::exit()
{
   XCloseDisplay(disp);

   return 0;
}

//***************************************************************************
// Send Exent
//***************************************************************************

int GraphTft::sendEvent()
{
   XEvent ev;
   Display* d;
   
   if ((d = XOpenDisplay(0)) == 0) 
   {
      tell(eloAlways, "Error: Sending event failed, cannot open display, ");
      return -1;
   }

   ev.type = Expose;
   XSendEvent(d, win, False, 0, &ev);
    
   XCloseDisplay(d);
    
   return 0;
}

//***************************************************************************
// Load Image
//***************************************************************************

void GraphTft::updateImage(const uchar* buffer, int size)
{
   bufferLock.lockForWrite();

   if (image)
   {
      imlib_context_set_image(image);
      imlib_free_image();
   }

#ifdef _DEBUG

   tell(eloAlways, "loading image, from file");

   image = imlib_load_image("test.jpg");
   sendEvent();
   dumpImage(image);

   bufferLock.unlock();

   return ;
#endif

   tell(eloAlways, "loading image, size (%d)", size);

   if (!size)
      return ;

   Blob blob(buffer, size);
   Image im;

   im.read(blob);
   
   resizeBuffer(4*im.columns()*im.rows());

   im.write(0, 0, im.columns(), im.rows(), "BGRA", CharPixel, (void*)pixelBuffer);
   image = imlib_create_image_using_data(im.columns(), im.rows(), (DATA32*)pixelBuffer);
   dumpImage(image);
   sendEvent();

   bufferLock.unlock();
}

//***************************************************************************
// Resite Pixel Buffer
//***************************************************************************

int GraphTft::resizeBuffer(int size)
{
   if (pixelBufferSize < size)
   {
      if (!pixelBuffer)
         pixelBuffer = (char*)malloc(size);
      else
         pixelBuffer = (char*)realloc(pixelBuffer, size);

      pixelBufferSize = size;
   }
   
   return 0;
}

//***************************************************************************
// Paint
//***************************************************************************

int GraphTft::paint()
{
   XWindowAttributes windowAttributes;

   if (!image)
      return -1;
   
   tell(eloAlways, "paint ...");

   // get actual window size

   XGetWindowAttributes(disp, win, &windowAttributes);
   width = windowAttributes.width; 
   height = windowAttributes.height;

   // lock buffer

   bufferLock.lockForRead();

   imlib_context_set_image(image);

   // get VDR's image size

   vdrWidth = imlib_image_get_width();
   vdrHeight = imlib_image_get_height();

   if (!resize)
   {
      imlib_render_image_on_drawable(0, 0);    // render image on drawable
   }
   else
   {
      Imlib_Image buffer;

      buffer = imlib_create_image(width, height);

      imlib_context_set_image(buffer);
      // imlib_context_set_blend(1);

      imlib_blend_image_onto_image(image, 0, 
                                   0, 0, vdrWidth, vdrHeight,
                                   0, 0, width, height);

      imlib_render_image_on_drawable(0, 0);
      imlib_free_image();
   }   
   
   bufferLock.unlock();
   
   return 0;
}

//***************************************************************************
// Dump Image
//***************************************************************************

void GraphTft::dumpImage(Imlib_Image image)
{
   if (*dump)
   {
      imlib_context_set_image(image);
      imlib_save_image(dump);
   }
}

//***************************************************************************
// Run loop
//***************************************************************************

int GraphTft::run()
{
   XEvent ev;
   KeySym key_symbol;
   int running = true;
   int update = false;

   while (running)
   {
      tell(eloDebug, "looping ;)");

      do
      {
         XNextEvent(disp, &ev);

         switch (ev.type)
         {
            case Expose:        update = true;  break;
            case MotionNotify:                  break;
            case CreateNotify:  tell(eloAlways, "Create");  break;
            case DestroyNotify: tell(eloAlways, "Destroy");  break;
            case ButtonPress:
            {
               onButtonPress(ev);
               break;
            }

            case KeyPress:
            {
               key_symbol = XKeycodeToKeysym(disp, ev.xkey.keycode, 0);
               tell(eloAlways, "Key (%d) pressed", key_symbol);

               if (key_symbol == XK_Escape)
                  running = false;               
               else
                  onKeyPress(ev);
               break;
            }

            default:
               break;
         }
      }

      while (XPending(disp));

      if (update)
      {
         update = false;
         paint();
      }
   }

   return 0;
}

//***************************************************************************
// On key Press (keyboard)
//***************************************************************************

int GraphTft::onKeyPress(XEvent event)
{
   int x = event.xmotion.x;
   int y = event.xmotion.y;
   int flag = ComThread::efKeyboard;
   int button = event.xkey.keycode;

   thread->mouseEvent(x, y, button, flag);

   return 0;
}

//***************************************************************************
// On Button Press (mouse)
//***************************************************************************

int GraphTft::onButtonPress(XEvent event)
{
   static long lastTime = 0;
   static int lastButton = -1;

   int x = event.xmotion.x;
   int y = event.xmotion.y;
   int flag = 0;

   tell(eloAlways, "Button press at (%d/%d) button %d, time (%d)",
        event.xmotion.x, event.xmotion.y, 
        event.xbutton.button,
        event.xbutton.time);

   if (event.xmotion.x < 5 && event.xmotion.y < 5)
      return -1;

   if (lastButton == (int)event.xbutton.button && event.xbutton.time-lastTime < 300)
   {
      tell(eloAlways, "assuming double-click");
      flag |= cGraphTftComService::efDoubleClick;
   }

   lastTime = event.xbutton.time;
   lastButton = event.xbutton.button;

   if (resize)
   {
      x = (int)(((double)event.xmotion.x / (double)width) * (double)vdrWidth);
      y = (int)(((double)event.xmotion.y / (double)height) * (double)vdrHeight);
   }

   thread->mouseEvent(x, y, event.xbutton.button, flag);

   return 0;
}
