/**
  \class CCamPanel
  \brief Abstract class for panels, visual representations of various stages in the image processing

  The process from image to (compressed) datastream is a long one, involving
  a number of steps. A CamPanel is used to hold data for a step, and if
  possible draw a graphical representation on the screen (this is great
  for debugging, but also produces some pretty pictures :)). 
  
  A CamPanel can show something as simple as the RGB image retrieved from
  the video device, or the DCT-ized version of the differential
  motion-compensated YUV image. Whatever is shown, it needs to be
  represented in a uniform manner through a decent base class.

  CamPanels are handled by CamWidget: a CamPanel registers itself with
  CamWidget; this class determines which panels should be shown (depending
  on flags and user settings), arranges them on the screen and gives the
  command to draw them.
  
  CamPanels have a unique name; this name is used to register the panel and
  retrieve others. Note: the name is unique per class of CamPanel, not per
  instance.

  For example, the differentiator works on a YUV image, so when the
  diff-panel needs to be drawn/calculated, it will request the image(s) from
  the YUV panel (which may or not be visible). The YUV panel may in turn
  require the RGB panel, etcetera. A panel may be needed by several other
  panels, or require more than one panel. Circular references are not
  allowed.
  
  The signal/slot mechanism of Qt is used to keep things going; the 
  'lower' panels with basic images are updated by CamWidget; these
  panels emited Updated() signal which 'higher', more sophisticated
  Panels may connect to. They in turn will emit Updated() signals when
  they are done with their work.
  
  Panels can, and will be, created by various classes. For example, a video
  compressor based on the DCT and H.3* protocols may register a set of
  panels, while another compressor based upon subband image coding will use
  another set of panels. Both sets however, may be dependant upon the same
  'base' panels. CamWidget itself registers 3 panels, nl. the full colour
  image, the YUV image and differentional YUV image (which are pretty basic :))

  Panels come in three basic forms:
  * a single RGB image that forms the begin (or end)point
  * YUV panels in 4:2:0 format, used for steps. The UV panels are a quarter in
    size and displayed below the large Y image
  * Non-image panels, for example graphs
    
  TODO: A full list of available panels

 */

#include <assert.h>
#include <stdio.h>
#include <string.h>

#include <qcolor.h>
#include <qevent.h>
#include <qpainter.h>

#if HAVE_CONFIG_H
#include "config.h"
#endif
#if HAVE_INTELCPU
#include "video_asm.h"
#endif

#include "CamPanel.h"

/**
  \brief Constructor.
  \param new_name Unique name for this kind of panel.
  \param new_description Short description for this panel.
  \param panel_type One of \ref PanelTypes, sets type of this panel
  \param draw Whether or not this panel can be drawn at all.
  
  Initializes panel to 0-size and not visible. By making this constructor
  protected this class cannot be instantiated directly, but only through
  a subclass.
  
  This class is derived from QWidget, with one notable difference: the 
  WRepaintNoErase flag is set, so that we don't get annoying flicker when
  we update our panel (normally, a widget is erase before updated).
 */
CCamPanel::CCamPanel(const QString &new_name, const QString &new_description, int panel_type, bool draw, QWidget *parent, const char *name)
	: QWidget(parent, name, Qt::WRepaintNoErase), ImgSize(0, 0), VisSize(0, 0)
{
   PanelType = panel_type;
   Drawable = draw;
   Name = new_name;
   Description = new_description;

   Usage = 0;

#if 0
   QDialog *dlg;
   int id;

   pMenu = new QPopupMenu();
   pMenu->insertItem("&Hide", this, SLOT(Hide()));
   pMenu->insertSeparator();
   dlg = GetPropertiesDialog();
   if (dlg)
     id = pMenu->insertItem("&Properties...", dlg, SLOT(show()));
   else {
     id = pMenu->insertItem("&Properties...", this, SLOT(Show())); // This is a dummy 
     pMenu->setItemEnabled(id, FALSE);
   }
#endif   
   MenuVisible = FALSE;   
}

/** 
  \brief Destructor
  
  Destroys all internal structures, images, etc. Is virtual to allow
  proper destruction of subclasses.
 */
CCamPanel::~CCamPanel()
{
  if (IsUsed())
    DecrementUse();
  if (IsUsed())
    printf("Panel %s is being destroyed while in use!\n", (const char *)GetName());
}

// private

void CCamPanel::CallIntg(int n, uchar *dst, uchar *src)
{
   if (dst == NULL || src == NULL)
     return;
#if HAVE_INTELCPU
# if HAVE_MMX
    calc_intg128_smx(n, dst, src);
# else
    calc_intg128(n, dst, src);
# endif
#else
   // Non assambly version
   int i, t;
   uchar *d, *s;
   
   s = src;
   d = dst;
   for (i = 0; i < n; i++) {
      t = *d + (*s - 128) << 1;
      if (t < 0) t = 0;
      if (t > 255) t = 255;
      *d = t;
      s++;
      d++;
   }
#endif
}

// protected

/**
  \brief Sets principle image size
  \param new_size New size of image
  
  This function will set size of the underlying image. It could be
  an image is enhanced, scaled down or has multiple panels; in that
  case VisibleSize != ImageSize. This size is mainly for functions who
  operate on the image and need to know the dimensions. For convenience,
  it also sets the variables image_w, image_h, half_w and half_h.

  This function will emit ChangedImageSize() when the size is different.
  
  See also \ref SetSize
*/

void CCamPanel::SetImageSize(const QSize &new_size)
{
qDebug("CCamPanel::SetImageSize(%dx%d)", new_size.width(), new_size.height());
   if (new_size != ImgSize) {
     ImgSize = new_size;
     image_w = ImgSize.width();
     image_h = ImgSize.height();
     half_w = image_w >> 1;
     half_h = image_h >> 1;
     emit ChangedImageSize(new_size);
   }
}


/**
  \brief Sets visible image size
  \param new_size New size of image
  
  This function will set the new, visible size (which may be different from
  the image size set by \ref SetImageSize) and emits a ChangedVisibleSize()
  signal if necessary.
  
  See also \ref SetSize
*/
void CCamPanel::SetVisibleSize(const QSize &new_size)
{
qDebug("CCamPanel::SetVisibleSize(%dx%d)", new_size.width(), new_size.height());
   if (new_size != VisSize) {
     VisSize = new_size;
     resize(new_size);
     emit ChangedVisibleSize(new_size);
   }
}

/**
  \brief Create image buffers in ImgRGB, ImgY/U/V or Graph resp.
  
  This function will call create() in the QImage objects ImgRGB or
  ImgY/U/V with the apropriate sizes; it will also fill the YUV buffers
  with a grayscale palet and set it to all-equal gray.
  
  In case of a Graph panel, it does the same for the QPixmap object.
*/ 
void CCamPanel::CreateImages()
{
   switch(PanelType) {
     case RGB: 
       ImgRGB.create(image_w, image_h, 32); 
       break;
     case YUV420:
       ImgY.create(image_w, image_h, 8, 256);
       ImgU.create(half_w,  half_h,  8, 256);
       ImgV.create(half_w,  half_h,  8, 256);
       for (int i = 0; i < 256; i++) {
         ImgY.setColor(i, qRgb(i, i, i));
         ImgU.setColor(i, qRgb(i, i, i));
         ImgV.setColor(i, qRgb(i, i, i));
       }
       ImgY.fill(128);
       ImgU.fill(128);
       ImgV.fill(128);
       break;
     case Graph:
       PixGraph.resize(image_w, image_h);
       break;
   }
}

/**
  \brief Make SIGNAL/SLOT connections for usage counting
  
  This function connect()s the SIGNALs of this class to the apropriate
  SLOTSs in the parent, so that the parent class knows if it is being
  used by any of its children.
  
  Classes that depend on multiple parents should call ConnectUsage for 
  each of its parent classes.
*/
void CCamPanel::ConnectUsage(CCamPanel *parent)
{
   // Cascade usage and visibility
   connect(this, SIGNAL(ChangedToVisible()), parent, SLOT(IncrementUse()));
   connect(this, SIGNAL(ChangedToHidden()), parent, SLOT(DecrementUse()));
   connect(this, SIGNAL(ChangedToUsed()), parent, SLOT(IncrementUse()));
   connect(this, SIGNAL(ChangedToUnused()), parent, SLOT(DecrementUse()));

}

/**
  \brief Make SIGNAL/SLOT connections to propagate updates
  
  This function is similar to \ref CCamPanel::ConnectUsage but this will
  make the necessary connections to make sure changes in size are propagated
  through the panels.
  
  It is probably a bad idea to connect to more than one parent for updates.
 */
void CCamPanel::ConnectResizes(CCamPanel *parent)
{
   connect(parent, SIGNAL(ChangedImageSize(const QSize &)), this, SLOT(SetSize(const QSize &)));
}


// public


/**
  \brief Returns unique name of panel.
 */
QString CCamPanel::GetName() const
{
   return Name;
}

/**
  \brief Returns a short description of the panel.
 */
QString CCamPanel::GetDescription() const
{
   return Description;
}   

/**
  \brief Returns the panel type
  \return One of the \ref PanelTypes enums
 */
int CCamPanel::GetPanelType() const
{
   return PanelType;
}

/**
  \brief Return size of principle image. May be (0, 0)
  \return An object of type QSize.
  
  See also \ref CCamPanel::SetImageSize
 */
QSize CCamPanel::GetImageSize() const
{
   return ImgSize;
}

/**
  \brief Return size of drawable portion; may be (0, 0) when there's nothing to draw.
  \return An object of type QSize.
 */
QSize CCamPanel::GetVisibleSize() const
{
   return VisSize;
}

/**
  \return TRUE when this panel can be drawn.
  
  This function tells if this panel has something to draw at all. Some
  panels may only be used as place holders of data, and cannot draw anything
  useful. In that case IsDrawable() returns false. The Panel will be
  included in the list, but not show up in the PanelSelector, for example.
 */
bool CCamPanel::IsDrawable()
{
   return Drawable;
}   


/**
  \brief Returns usage counter.
  
  This function returns the number of panels that claimed this panel for use;
  it returns an integer of 0 or higher. A value of 0 means this panel can
  be removed without problems.
 */
int CCamPanel::IsUsed()
{
   return Usage;
}

/**
  \brief Return image representing step.
  \return QImage representing the current step.
  \param n Selector for sub-image.
  
  This function returns an image for this panel. For RGB panels, it will 
  return ImgRGB, and for YUV panels ImgU, ImgU or ImgV, depending on n.
  Graph panels never return an image (but they should draw something when
  Draw() is called).
  
  This function can be overloaded for panels that have special needs. When
  there is nothing useful to return, this function will return a null image.
 */
const QImage &CCamPanel::GetImage(int n)
{
   switch (PanelType) {
     case RGB: return ImgRGB; break;
     case YUV420:
       switch(n) {
         case 0: return ImgY; break;
         case 1: return ImgU; break;
         case 2: return ImgV; break;
       }
       break;
   }
   return ImgNull; // Null image
}

/**
  \brief Return pixmap object
  
  Similar to \ref GetImage, this function returns the Pixmap object for 
  Graph panel.
*/

const QPixmap &CCamPanel::GetPixmap() const
{
   return PixGraph;
}

/**
  \return Returns a Dialog object
  
  Panels that have properties that can be manipulated by the user should
  return a QDialog object that is used to perform these manipulations. This
  dialog may not be a modal dialog box, and the handling is left up to
  the panel & dialog code.
  
  Default implementation returns a NULL pointer, i.e. this panel does
  not have such a dialog.
*/
QDialog *CCamPanel::GetPropertiesDialog() const
{
   return NULL;
}



/**
  \brief Make deep copies of images, by overwriting buffers
  
  Blegh; the = operator is the ONLY operator that can't be inherited. Too bad.
 */
const CCamPanel &CCamPanel::copy(const CCamPanel &src)
{
   if (PanelType == src.PanelType && ImgSize == src.ImgSize) {
     int pixels;

     pixels = image_w * image_h;
     switch(PanelType) {
       case RGB: 
         memcpy(ImgRGB.bits(), src.ImgRGB.bits(), pixels << 2);
         break;
       case YUV420:
         memcpy(ImgY.bits(), src.ImgY.bits(), pixels);
         memcpy(ImgU.bits(), src.ImgU.bits(), pixels >> 2);
         memcpy(ImgV.bits(), src.ImgV.bits(), pixels >> 2);
         break;
     }
   }
   return *this;
}

/**
  \brief Operator overload that will add the values of the buffers of \b plus to this panel

  The CCamPanel equivalent of 'a += b', where the YUV values are regarded
  as signed quantities with a virtual null point at 128. This is the
  integration step.
 */
const CCamPanel &CCamPanel::operator += (const CCamPanel &plus)
{
   // Make a thorough check first
   if (PanelType == plus.PanelType && ImgSize == plus.ImgSize) {
     int pixels;
     
     pixels = image_w * image_h;
     switch(PanelType) {
       case RGB:
         CallIntg(pixels << 2, ImgRGB.bits(), plus.ImgRGB.bits());
         break;
       case YUV420:
         CallIntg(pixels     , ImgY.bits(), plus.ImgY.bits());
         CallIntg(pixels >> 2, ImgU.bits(), plus.ImgU.bits());
         CallIntg(pixels >> 2, ImgV.bits(), plus.ImgV.bits());
         break;
     }
   }
   return *this;
}


/* Overloaded functions from QWidget; we're going to be quite firm on 
   the size we desire; only fixed size.
 */
 
/**
  Return our visible size as the sizeHint.
*/
QSize CCamPanel::sizeHint() const
{
   return VisSize;
}

QSizePolicy CCamPanel::sizePolicy() const
{
   return QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
}

/**
  \brief Paint visible representation of step
  \param p A QPainter object that has already been moved to the correct location

  This function will draw the Panel contents on screen. The default
  implementation will draw ImgRGB or ImgYUV, depending on its PanelType. A
  Graph panel should overload this function.
*/
void CCamPanel::paintEvent(QPaintEvent *e)
{
   QPainter p(this);

   switch (PanelType) {
     case RGB: 
       p.drawImage(0     , 0      , ImgRGB); 
       break;
     case YUV420:
       p.drawImage(0     , 0      , ImgY);
       p.drawImage(0     , image_h, ImgU);
       p.drawImage(half_w, image_h, ImgV);
       break;
     case Graph:
       p.drawPixmap(0    , 0      , PixGraph);
       break;
   }
}



// public slots

/** 
  \brief Slot to signal changes in image size.
  
  Whenever the image size is changed, this function should be called with
  the new image size.  It should determine the panel size (which may be
  smaller or larger than the image size). 
  
  The default implemenation sets the visible and image size equal
  for RGB and Graph panels, while the height of YUV420 panels is 1.5 the
  image height to allow space for the UV boxes.
  
  Note that the Image and Visible size can always be overruled by calling
  \ref SetImageSize and \ref SetVisibleSize
*/
void CCamPanel::SetSize(const QSize &new_size)
{
qDebug("CCamPanel::SetSize(%dx%d)", new_size.width(), new_size.height());
   SetImageSize(new_size);
   if (PanelType == YUV420)
     SetVisibleSize(QSize(image_w, image_h + half_h));
   else
     SetVisibleSize(new_size);
   CreateImages();
}

/** 
  \brief Increment usage counter.
  
  Wheneven a panel has need of this panel it should call IncrementUse()
  to indicate its needed and should emit Updated() signals.
  Triggers UsageChanged().
 */
void CCamPanel::IncrementUse()
{
#if TRACE_PANELSIGNALS
   printf("CCamPanel::IncrementUse() [%s]\n", (const char *)GetName());
#endif   
   Usage++;
   emit ChangedUsage(Usage);
   if (Usage == 1)
     emit ChangedToUsed();
}

/** 
  \brief Decrement usage counter.
  
  Wheneven a panel nog longer needs this panel it should call DecrementUse()
  so this panel can release resources or stop calculations that are no
  longer required. The usage counter may not drop below zero, and this
  is considered an error condition.
  Triggers UsageChanged().
 */
void CCamPanel::DecrementUse()
{
#if TRACE_PANELSIGNALS
   printf("CCamPanel::DecrementUse() [%s]\n", (const char *)GetName());
#endif   
   assert(Usage > 0);
   Usage--;
   emit ChangedUsage(Usage);
   if (Usage == 0)
     emit ChangedToUnused();
}


void CCamPanel::RightClick(const QPoint &pos)
{
   pMenu->popup(pos);
   MenuVisible = !MenuVisible;
}

