//==============================================
//  copyright            : (C) 2003-2005 by Will Stokes
//==============================================
//  This program is free software; you can redistribute it
//  and/or modify it under the terms of the GNU General
//  Public License as published by the Free Software
//  Foundation; either version 2 of the License, or
//  (at your option) any later version.
//==============================================

//Systemwide includes
#include <qtimer.h>
#include <qpixmap.h>
#include <qimage.h>
#include <qapplication.h>
#include <qpainter.h>
#include <qstyle.h>
#include <qmutex.h>
#include <qcursor.h>

//Projectwide includes
#include "ALabel.h"

//==============================================
ALabel::ALabel( QWidget* parent, const char* name,
                        QPixmap* hoverOverImage,
                        int setMethod, int removalMethod,
                        int resetMethod, int removalBeforeResetMethod,
                        int initDelay,  int accel)  : QLabel(parent,name)
{
  delayedActionHead = NULL;
  delayedActionTail = NULL;

  //set animation defaults
  this->setMethod = setMethod;
  this->removalMethod = removalMethod;
  this->removalBeforeResetMethod = removalBeforeResetMethod;
  this->resetMethod = resetMethod;

  //set hover over image
  this->hoverOverImage = hoverOverImage;
  drawHoverOverImage = false;

  setMouseTracking(true);
  handCursorShown = false;

  //by default image is not shown, we are not animating, nor resetting the image
  imageShown = false;
  animating = false;
  resettingImage = false;
  pixStore = NULL;
  resetPixStore = NULL;

  //don't clear pixmap area before painting, prevents flicker
  setWFlags(WRepaintNoErase);

  //set delay defaults
  this->initDelay = initDelay;
  this->accel = accel;
  this->minDelay = 1;

  //create timer object and setup signals
  timer = new QTimer();
  connect(timer, SIGNAL(timeout()), this, SLOT(animate()) );
}
//==============================================
void ALabel::setAnimationMethods( int setMethod, int removalMethod,
                                                        int resetMethod, int removalBeforeResetMethod )
{
  //set animation defaults
  this->setMethod = setMethod;
  this->removalMethod = removalMethod;
  this->removalBeforeResetMethod = removalBeforeResetMethod;
  this->resetMethod = resetMethod;
}
//==============================================
void ALabel::setPixmap ( const QPixmap & p )
{
  //get locks on queues
  queueMutex.lock();
  animatingBoolMutex.lock();
  //if currently animating then append job to queue
  if(animating || delayedActionHead != NULL)
  {
    appendJob(new QPixmap(p));
    animatingBoolMutex.unlock();
    queueMutex.unlock();
    return;
  }
  //else set animating to true, actually initiate job
  else
  {
    animating = true;
    animatingBoolMutex.unlock();
    queueMutex.unlock();
    internalSetPixmap( p );
  }
}
//==============================================
void ALabel::internalSetPixmap ( const QPixmap & p )
{
  //if previous image already present remove it first
  if(pixStore)
  {
    resettingImage = true;

    //backup new set image to be retrieved later
    if(resetPixStore)
    {
//      cout << "LEAK DETECTED when trying to resetPixStore\n";
      delete resetPixStore;
    }
    resetPixStore = new QImage(p.convertToImage());

    //set animation method
    animationType = removalBeforeResetMethod;

    //remove old pixmap
    animatePixmap();
  }
  //else immediately set image
  else
  {
    resettingImage = false;
    //set pixmap store and animate setting it
    pixStore = new QImage(p.convertToImage());
    animationType = setMethod;
    animatePixmap();
  }
}
//==============================================
void ALabel::removePixmap ( bool forceImmediate)
{
  //get locks on queues
  queueMutex.lock();
  animatingBoolMutex.lock();
  //if currently animating then append job to queue
  if(animating || delayedActionHead != NULL)
  {
    appendJob( NULL );
    animatingBoolMutex.unlock();
    queueMutex.unlock();
    return;
  }
  //else set animating to true, actually initiate job (only if no image currently exists)
  else
  {
    //only remove image if image currently exists
    if(pixStore != NULL)
      animating = true;

    animatingBoolMutex.unlock();
    queueMutex.unlock();

    if(animating)
      internalRemovePixmap( forceImmediate );
  }
}
//==============================================
void ALabel::internalRemovePixmap( bool forceImmediate )
{
  //set animation method and animate
  if(forceImmediate)
    animationType = DISAPPEAR_IMMEDIATELY;
  else
    animationType = removalMethod;

  animatePixmap();
}
//==============================================
void ALabel::animatePixmap()
{
  //set the step paramater. for slide transitions this is the number
  //of columns to be displayed. for fade transitions this is what percentage
  //of the fade we have completed so start out at 1.
  if(animationType == SLIDE_OUT_LEFT ||
     animationType == SLIDE_OUT_RIGHT ||
     animationType == DISAPPEAR_IMMEDIATELY)
  { step = pixStore->width()-1; }
  else
  { step = 1; }

  //set initial delay/speed
  delay = initDelay;

  //find current time, used to decide how many new columns to reveal in first iteration
  lastTime.start();

  //begin animation
  animate();
}
//==============================================
void ALabel::animate()
{
  //---------------------------------
  //backup last # of columns shown
  int lastStep = step;
  //---------------------------------
  //determine new number of columns to be shown
  if(animationType == APPEAR_IMMEDIATELY)
    step = pixStore->width();
  else if(animationType == DISAPPEAR_IMMEDIATELY)
    step = 0;
  else
  {
    //determine # of ms that have passed since last redraw
    currentTime.start();
    double ms = lastTime.msecsTo(currentTime);

    //determine increment
    int inc = (int)(ms/(delay+1));

    if(animationType == SLIDE_OUT_LEFT ||
       animationType == SLIDE_OUT_RIGHT)
    { inc = -inc; }

    //if increment is not zero then update last time
    if(inc != 0)
    {
      lastTime = currentTime;
    }

    //update number of columns shown
    step = step + inc;

    //boundary conditions
    if( animationType == FADE_TRANSITION)
    {
      if(step > 100)
        step = 100;
    }
    else
    {
      if(step > pixStore->width())
        step = pixStore->width();
      else if(step < 0)
        step = 0;
     }
  }
  //---------------------------------
  //if percentage of frade has changed then redraw
  if(animationType == FADE_TRANSITION)
  {
    if(step != lastStep)
    {
      //compute intermediate image resolution
      double w1 = pixStore->width();
      double h1 = pixStore->height();
      double w2 = resetPixStore->width();
      double h2 = resetPixStore->height();
      double alpha = ((double)step) / 100.0;
      int w = (int) ( w1 + (w2-w1)*alpha );
      int h = (int) ( h1 + (h2-h1)*alpha );

      //resize old and new images
      QImage oldImg = pixStore->scale( w, h );
      QImage newImg = resetPixStore->scale( w, h );

      //scale each image by alpha (step/100) and 1-alpha and combine
      int maxDepth = pixStore->depth();
      if(resetPixStore->depth() > maxDepth)
        maxDepth = resetPixStore->depth();

      QImage tmpImage(w, h, maxDepth);

      int x,y;
      for(x = 0; x<w; x++)
      {
        for(y = 0; y<h; y++)
        {
          QRgb v1 = oldImg.pixel(x,y);
          QRgb v2 = newImg.pixel(x,y);
          int r = (int) (alpha* qRed(v2) + (1-alpha)*qRed(v1));
          int g = (int) (alpha* qGreen(v2) + (1-alpha)*qGreen(v1));
          int b = (int) (alpha* qBlue(v2) + (1-alpha)*qBlue(v1));

          tmpImage.setPixel(x, y, qRgb(r,g,b) );
        }
      }

      //set image shown to this interpolated image
      QPixmap tmpPixmap(step, pixStore->height() );
      tmpPixmap.convertFromImage( tmpImage );
      QLabel::setPixmap( tmpPixmap );

      //I've noticed when we replace images with ones which are shorter (height wise) some pixels
      //are left behind. this is annoying. a quick fix is to so a erase repaint in this situation.
      //another kludgly (but flickerless) improvement woudl be to create a pixmap that is the
      //same size as the current but paints the bottom and top edges with the background color.
      //I honestly think this is a bug in Qt that Trolltech needs to address. Maybe I should report it to them.
      if(h2 < h1)
        repaint(true);
      else
        repaint(false);
    }
  }
  //if the number of cols to be shown has changed then redraw
  else if(step != lastStep &&
     (
       animationType != DISAPPEAR_IMMEDIATELY ||
       (animationType == DISAPPEAR_IMMEDIATELY && !resettingImage)
     )
   )
  {
    //draw empty image
    if(step == 0)
    {
      QLabel::setPixmap( NULL );
      emit pixmapRemoved();
      repaint(true);
    }
    //draw full image
    else if(step == pixStore->width() )
    {
      QLabel::setPixmap( *pixStore );
      repaint(false);
    }
    //draw a portion the image
    else
    {
      //construct temp image
      QImage tmpImage(step, pixStore->height(), pixStore->depth() );
      int x,y;
      for(x = 0; x<step; x++)
      {
        for(y = 0; y<pixStore->height(); y++)
        {
          if(animationType == SLIDE_IN_LEFT ||
            animationType == SLIDE_OUT_LEFT)
          { tmpImage.setPixel( x, y, pixStore->pixel(pixStore->width()-step+x,y) ); }
          else
          { tmpImage.setPixel( x, y, pixStore->pixel(x,y) ); }
        } //end for y
      } //end for x

      //set label to use temp image (a portion of the full image)
      QPixmap tmpPixmap(step, pixStore->height() );
      tmpPixmap.convertFromImage( tmpImage );
      QLabel::setPixmap( tmpPixmap );

      if(animationType == SLIDE_OUT_LEFT ||
          animationType == SLIDE_OUT_RIGHT)
        repaint(true);
      else
        repaint(false);
    }
  }
  //---------------------------------
  //not done animating, reiterate
  if(
      ((animationType == SLIDE_IN_LEFT ||
       animationType == SLIDE_IN_RIGHT) && step < pixStore->width()) ||
      ((animationType == SLIDE_OUT_LEFT ||
       animationType == SLIDE_OUT_RIGHT) && step > 0) ||
      (animationType ==  FADE_TRANSITION && step < 100)
    )
  {
    //update speed
    delay = delay - accel;
    if(delay < minDelay) delay = minDelay;

    //restart timer
    timer->start( delay, TRUE );
  }
  //---------------------------------
  //done animating....
  else
  {
    //If we just removed the image then delete the old pixStore object
    if(step == 0)
    {
      imageShown = false;
      delete pixStore;
      pixStore = NULL;
    }
    else
    {
      imageShown = true;
    }

    //if we are actually in the middle of a reset action then we've
    //just finished removing the old image, now it's time to start setting the new image
    if(resettingImage && animationType !=  FADE_TRANSITION)
    {
      resettingImage = false;

      if(pixStore)
      {
//        cout << "ERROR! Leak detected!\n";
        delete pixStore;
        pixStore = NULL;
      }

      pixStore = resetPixStore;
      resetPixStore = NULL;
      animationType = resetMethod;
      animatePixmap();
    }
    //else truely done
    else
    {
      //we were running a fade effect then delete old pixmap and repalce it with new one
      if( animationType == FADE_TRANSITION )
      {
        resettingImage = false;
        delete pixStore;
        pixStore = resetPixStore;
        resetPixStore = NULL;
      }

      //check to see pending animations exist, if so pop off next action from list
      queueMutex.lock();

      //simplify list of pending actions
      cleanStack();

      if(delayedActionHead == NULL)
      {
        //done!
        queueMutex.unlock();
        animatingBoolMutex.lock();
        animating = false;
        animatingBoolMutex.unlock();
        return;
      }

      //ok, we're not done, pop off first entry from queue, then start up actual job
      Action* currAction = delayedActionHead;
      delayedActionHead = delayedActionHead->getNext();
      if(delayedActionHead == NULL)
        delayedActionTail = NULL;

      queueMutex.unlock();

      //start job
      if(currAction->getImage() == NULL) internalRemovePixmap();
      else internalSetPixmap( *currAction->getImage() );

      //free old action object
      delete currAction;
    }
  }
  //---------------------------------------
}
//==============================================
void ALabel::drawContents( QPainter* p )
{
  //draw conents of label
  QLabel::drawContents(p);

  //if animation complete and image is being shown, draw hover over image
  if(!animating && imageShown && drawHoverOverImage)
  {
    QRect r = style().itemRect( p, contentsRect(), alignment(), isEnabled(), pixmap(), text());

    int minDim = r.width();
    if(r.height() < minDim)
      minDim = r.height();
    if(minDim > hoverOverImage->width())
    {
      r.setLeft( r.right() - hoverOverImage->width() );
      r.setBottom( r.top() + hoverOverImage->height() );
      hoverOverRect = r;
      p->drawPixmap( r, *hoverOverImage);
    }
    else
    {
      QImage resizedImage = hoverOverImage->convertToImage().scale(minDim, minDim);
      QPixmap resizedPixmap(resizedImage);
      r.setLeft( r.right() - resizedPixmap.width() );
      r.setBottom( r.top() + resizedPixmap.height() );
      hoverOverRect = r;
      p->drawPixmap( r, resizedPixmap);
    }
  }
}
//==============================================
void ALabel::enterEvent( QEvent*)
{
  if(hoverOverImage)
  {
    drawHoverOverImage = true;
    repaint( false );
  }
}
//==============================================
void ALabel::leaveEvent( QEvent*)
{
  if(hoverOverImage)
  {
    drawHoverOverImage = false;
    repaint(false);
  }
}
//==============================================
void ALabel::mousePressEvent( QMouseEvent* )
{ emit mousePress(); }
//==============================================
void ALabel::mouseReleaseEvent( QMouseEvent* e)
{
  //if there is no hover over image then ignore event
  if( hoverOverImage == NULL ) return;
  
  QPainter* p = new QPainter();
  QRect r = style().itemRect( p, contentsRect(), alignment(), isEnabled(), pixmap(), text());
  delete p;
  int minDim = r.width();
  if(r.height() < minDim)
    minDim = r.height();
  if(minDim > hoverOverImage->width())
  {
    r.setLeft( r.right() - hoverOverImage->width() );
    r.setBottom( r.top() + hoverOverImage->height() );
  }
  else
  {
    QImage resizedImage = hoverOverImage->convertToImage().scale(minDim, minDim);
    QPixmap resizedPixmap(resizedImage);
    r.setLeft( r.right() - resizedPixmap.width() );
    r.setBottom( r.top() + resizedPixmap.height() );
  }
  
  if(r.contains( e->pos() ) )
  {
    removePixmap();
    emit mouseRelease();
  }
}
//==============================================
void ALabel::mouseDoubleClickEvent( QMouseEvent* )
{ emit mouseDoubleClick(); }
//==============================================
void ALabel::mouseMoveEvent( QMouseEvent* e)
{
  //need rect so draw hover over image must exist before any checks can occur
  if( !drawHoverOverImage )
    return;

  //if hand not shown but over hover over image then turn hand cursor on
  if( !handCursorShown && hoverOverRect.contains( e->x(), e->y() ) )
  {
    setCursor( QCursor( Qt::PointingHandCursor ) );
    handCursorShown = true;
    return;
  }

  //if hand cursor shown but nolonger over hover over image set cursor back to normal
  if( handCursorShown && !hoverOverRect.contains( e->x(), e->y() ) )
  {
    setCursor( QCursor( Qt::ArrowCursor ) );
    handCursorShown = false;
    return;
  }
}
//==============================================
void ALabel::appendJob(QPixmap* pix)
{
  Action* newAct = new Action(pix);
  if(delayedActionHead == NULL)
    delayedActionHead = newAct;
  else
    delayedActionTail->setNext( newAct );

  delayedActionTail = newAct;
}
//==============================================
void ALabel::cleanStack()
{
  //if stack empty already clean
  if(delayedActionHead == NULL)
    return;

  //if no image currently displayed, pop off all remove actions from head of list
  if(pixStore == NULL)
  {
      Action* currAction = delayedActionHead;
      while(currAction != NULL && currAction->getImage() == NULL)
      {
        Action* next = currAction->getNext();
        delete currAction;
        currAction = next;
      }

      delayedActionHead = currAction;
      if(currAction == NULL)
        delayedActionTail = NULL;
  }

  //if one pending operations no simplifications possible
  if(delayedActionHead == delayedActionTail)
    return;

  //if last action is a set operation then remove all other entries
  if(delayedActionTail->getImage() != NULL)
  {
    Action* temp = delayedActionHead;
    delayedActionHead = delayedActionTail;
    while(temp != delayedActionHead)
    {
      Action* next = temp->getNext();
      delete temp;
      temp = next;
    }
    return;
  }

  //last action is a remove operation, if no current image then cull entire stack
  if(pixStore == NULL)
  {
    Action* temp = delayedActionHead;
    while(temp != NULL)
    {
      Action* next = temp->getNext();
      delete temp;
      temp = next;
    }
    delayedActionHead = NULL;
    delayedActionTail = NULL;
    return;
  }
  //else remove all but last pending operations since final remove action will remove current image
  else
  {
    Action* temp = delayedActionHead;
    while(temp != delayedActionTail)
    {
      Action* next = temp->getNext();
      delete temp;
      temp = next;
    }
    delayedActionHead = delayedActionTail;
    return;
  }
}
//==============================================
Action::Action(QPixmap* image)
{
  this->image = image;
  next = NULL;
}
//==============================================
Action::~Action()
{
  delete image;
  image = NULL;
}
//==============================================
Action* Action::getNext()
{ return next; }
//==============================================
void Action::setNext( Action* next)
{ this->next = next; }
//==============================================
QPixmap* Action::getImage()
{ return image; }
//==============================================
