/*
 * Implementation of class ColorVideo
 *
 * $Date$
 * $Revision$
 *
 */

#include "WImage/ColorVideo.hh"
#include "WImage/ColorImage.hh"
#ifndef _WIN32_WCE
#include "WImage/VidReader.hh"
#include "WImage/AviReader.hh"
#include "WImage/VidWriter.hh"
#include "WImage/AviWriter.hh"
#endif
#include "WImage/VideoFrame.hh"
#include "WImage/tools.h"
#include "WImage/debug.h"
#include <stdexcept>
#ifndef _WIN32_WCE
#include <iostream>
#endif
#include <cassert>

#if !(defined __WATCOMC__ && __WATCOMC__ < 1230)
using namespace std;
#endif

  
ColorVideo::ColorVideo (const ColorVideo &cv) 
  : ColorBuffer (cv.rows () * cv.cols (), cv.colors (), cv.colormodel ())
{
  m_arrays = NULL;
  m_colorImage = NULL;
  m_frameRate = 0;
  ColorVideo::init (cv.colors (), cv.colormodel (), cv.rows (), 
                    cv.cols (), cv.frames ());
  assert (this->colors () == cv.colors ());
  assert (this->m_arrays[0]->frames () == cv.m_arrays[0]->frames ());
  assert (this->m_arrays[0]->size () == cv.m_arrays[0]->size ());
  for (int c = 0; c < m_colors; c++)
    {
      for (int i = 0; i < m_xyzsize; i++)
        {
          m_arrays[c]->array () [i] = cv.m_arrays[c]->array () [i];
        }
    }
}

ColorVideo::ColorVideo (int colors, clrmodel colormodel, int rows, int cols, 
                        int frames) : ColorBuffer (rows * cols, colors, colormodel)
{
  m_frameRate = 0;
  m_arrays = NULL;
  m_colorImage = NULL;
  ColorVideo::init (colors, colormodel, rows, cols, frames);
}

ColorVideo::ColorVideo (void) : ColorBuffer (0, 0, cm_unknown)
{
  m_frameRate = 0;
  m_colorImage = NULL;
  m_arrays = NULL;
}

ColorVideo::~ColorVideo (void)
{
  destroy ();
}

#ifndef _WIN32_WCE
void
ColorVideo::videoDimensions (const char *fname, int &y, int &x, int &z, 
                             int colors, int skip)
{
  int nonZero = 0;
  FileName fn (fname);
  switch (fn.guess ())
    {
    case fn_vid:
      if (y > 0)
        {
          nonZero++;
        }
      if (x > 0)
        {
          nonZero++;
        }
      if (z > 0)
        {
          nonZero++;
        }
      if (nonZero > 2)
        {
          throw invalid_argument ("videoDimensions: more than two "
                                  "nonzero values.");
        }
      if (nonZero > 1)
        {
          if (y > 0 && x > 0)
            {
              z = VidReader::framesInFile (fname, y, x, colors, skip);
              return;
            }
          int fileSize = tools_fileSize (fname) - skip;
          if (z > 0 && y > 0)
            {
              x = fileSize / (y * colors);
            }
          else if (z > 0 && x > 0)
            {
              y = fileSize / (x * colors);
            }
          return;
        }
      throw invalid_argument ("videoDimensions: not enough "
                                  "nonzero values.");;
    case fn_avi:
      z = AviReader::framesInFile (fname);
      AviReader::fileDimensions (fname, y, x);
      break;
    default:
      throw invalid_argument ("videoDimensions: "
                              "Unknown/unsupported file type.");
    }
}
#endif

ColorVideo *
ColorVideo::clone (void) const
{
  return NEW (ColorVideo (*this));
}


void
ColorVideo::init (int colors, clrmodel cmodel, int rows, int cols, int frames)
{
  if (colors < 1)
    {
      throw invalid_argument ("init: invalid number of colors.");
    }
  if (rows < 1 || cols < 1)
    {
      throw invalid_argument ("init: invalid number of rows or columns.");
    }
  if (frames < 1)
    {
      throw invalid_argument ("init: invalid number of frames.");
    }

  m_frames = frames;
  m_ysize = rows;
  m_xsize = cols;
  m_xysize = rows * cols;
  m_xyzsize = m_xysize * m_frames;
  m_current = 0;
  
  if (m_arrays == NULL)
    {
      m_arrays = NEW (VideoArray < coeff > * [m_colors]);
    }
  
  if (rows * cols * frames > 0)
    {
      for (int i = 0; i < m_colors; i++)
        {
          m_arrays[i] = NEW (VideoArray < coeff > (rows, cols, frames));
          m_images[i] = NEW (VideoFrame (m_arrays[i]));
        }
      m_colorImage = NEW (ColorImage (m_colors, m_cmodel, m_images, true));
      assert (m_frames == frames);
    }
  else
    {
      for (int i = 0; i < m_colors; i++)
        {
          m_arrays[i] = NULL;
          m_images[i] = NULL;
        }
      m_colorImage = NULL;
    }
}


void
ColorVideo::destroy (void)
{
  if (m_arrays != NULL)
    {
      for (int i = 0; i < m_colors; i++)
        {
          DELETENOTNULL (m_arrays[i]);
        }
      DELETE (m_arrays);
    }    

  DELETENOTNULL (m_colorImage);
  
  ColorBuffer::destroy ();
  m_current = 0;
  m_frames = 0;
}

void
ColorVideo::swapColors (int ch1, int ch2)
{
  ColorBuffer::swapColors (ch1, ch2);
  VideoArray < coeff > *tmp = m_arrays[ch1];
  m_arrays[ch1] = m_arrays[ch2];
  m_arrays[ch2] = tmp;
}

void 
ColorVideo::current (int current)
{
  m_current = current;
  VideoArray < coeff > *alias = NULL;
  for (int i = 0; i < m_colors; i++)
    {
      alias = dynamic_cast < VideoArray < coeff > * > (m_arrays[i]);
      alias->current (m_current);
    }
}
  

#ifndef _WIN32_WCE
void 
ColorVideo::read (char const *fname, int from, int to, int rawy, int rawx, 
                  int colors, clrmodel cmodel, int skip)
{
  FileName fn (fname);
  read (fname, fn.guess (), from, to, rawy, rawx, colors, cmodel, skip);
}


void 
ColorVideo::read (char const *fname, filetype ftype, int from, int to, 
                  int rawy, int rawx, int colors, clrmodel cmodel, int skip)
{
  VideoReader *vr = NULL;
  int frames = to - from;

  if (from <= 0 || to <= 0 || to < from)
    {
      if (from < 0)
        {
          from = 0;
        }
      if (to < from)
        {
          to = 0;
        }
    }
      
  switch (ftype)
    {
    case fn_vid:
      if (to <= from)
        {
          frames = VidReader::framesInFile (fname, rawy, rawx, colors, skip) 
            - from;
        }
      destroy ();
      ColorBuffer::init (colors, cmodel);
      ColorVideo::init (colors, cmodel, rawy, rawx, frames);
      vr = NEW (VidReader (fname, m_arrays, rawy, rawx, colors, 
                           from, to, skip));
      break;
    case fn_avi:
      if (to <= from)
        {
          frames = AviReader::framesInFile (fname) - from;
        }
      AviReader::fileDimensions (fname, rawy, rawx);
      m_frameRate = AviReader::frameRate (fname);
      destroy ();
      ColorBuffer::init (3, cm_rgb);
      ColorVideo::init (3, cm_rgb, rawy, rawx, frames);
      vr = NEW (AviReader (fname, m_arrays, rawy, rawx, from, to));
      break;
    default:
      throw invalid_argument ("read: unknown file type.");
    }
  vr->read ();
  DELETE (vr);
  
  assert (!(m_colors > 1 && m_cmodel == cm_unknown));
}

void 
ColorVideo::write (char const *fname, int rawskip, bool beautify)
{
  if (m_colors < 1)
    {
      throw invalid_argument ("write: empty video.");
    }
  
  FileName fn (fname);
  write (fname, fn.guess (), rawskip, beautify);
}

void 
ColorVideo::write (char const *fname, filetype ftype, 
                   int rawskip, bool beautify)
{
  VideoWriter *vw = NULL;
  
  ColorVideo *beauty = this;

  if (m_images[0]->anasteps () - m_images[0]->synsteps () > 0 
      || (this->smin () < 0) || (this->smax () > 255))
    {
      beautify = true;
      beauty = this->clone ();
    }

  switch (ftype)
    {
    case fn_vid:
      vw = NEW (VidWriter (fname, m_arrays, m_colors, rawskip));
      break;
    case fn_avi:
      if (m_frameRate <= DBL_EPSILON)
        {
          throw invalid_argument ("write: frame rate not set.");
        }
      vw = NEW (AviWriter (fname, m_arrays, m_colors, m_frameRate));
      break;
    default:
      throw invalid_argument ("write: unknown file type.");
    }
  
  if (beautify)
    {
      beauty->beautify ();
    }

  vw->write ();
  DELETE (vw);

  if (beautify)
    {
      DELETE (beauty);
    }
}
#endif

coeff 
ColorVideo::smax (void) const
{
  if (m_colors < 1)
    {
      throw invalid_argument ("smax: empty image.");
    }
  coeff result = m_images[0]->smax ();
  for (int i = 1; i < m_colors; i++)
    {
      coeff *ar = m_arrays[i]->array ();
      for (int j = 0; j < m_xyzsize; j++)
        {
          if (ar[j] > result)
            {
              result = ar[j];
            }
        }
    }
  return result;
}

coeff 
ColorVideo::smin (void) const
{
  if (m_colors < 1)
    {
      throw invalid_argument ("smin: empty image.");
    }
  coeff result = m_images[0]->smin ();
  for (int i = 1; i < m_colors; i++)
    {
      coeff *ar = m_arrays[i]->array ();
      for (int j = 0; j < m_xyzsize; j++)
        {
          if (ar[j] < result)
            {
              result = ar[j];
            }
        }
    }
  return result;
}

bool 
ColorVideo::equals (ColorBuffer &buf) const
{
  return epsilons (buf, COEFF_EPSILON);
}

bool 
ColorVideo::epsilons (ColorBuffer &buf, coeff epsilon) const
{
  ColorVideo *cv = dynamic_cast < ColorVideo* > (&buf);
  if (cv != NULL)
    {
      return epsilonsVideo (*cv, epsilon);
    }
  return ColorBuffer::epsilons (buf, epsilon);
}

bool 
ColorVideo::epsilonsFrames (int f1, int f2, coeff epsilon) const
{
  for (int c = 0; c < m_colors; c++)
    {
      if (!m_arrays[c]->epsilonsFrames (f1, f2, epsilon))
        {
          return false;
        }
    }
  return true;
}

bool 
ColorVideo::equalsFrames (int f1, int f2) const
{
  for (int c = 0; c < m_colors; c++)
    {
      if (!m_arrays[c]->equalsFrames (f1, f2))
        {
          return false;
        }
    }
  return true;
}


void 
ColorVideo::truncate (coeff min, coeff max)
{
  int oldcur = current ();
  for (int frame = 0; frame < m_frames; frame++)
    {
      current (frame);
      for (int i = 0; i < m_xysize; i++)
        {
          for (int j = 0; j < m_colors; j++)
            {
              if (m_images[j]->at (i) < min)
                {
                  m_images[j]->to (i, min);
                }
              else if (m_images[j]->at (i) > max)
                {
                  m_images[j]->to (i, max);
                }
            }
        }
    }
  current (oldcur);
  
  assert (smin () >= 0.0);
  assert (smax () < 256);
}

void 
ColorVideo::beautify (int yoffs, int xoffs, int ysize, int xsize)
{
  int oldcur = current ();
  Image **tempchannels = NEW (Image* [m_colors]);
  coeff fill = (smax () - smin ()) / 2;
      
  for (int frame = 0; frame < m_frames; frame++)
    {
      current (frame);
      if (xsize * ysize == 0)
        {
          valadjust ();
        }
      else
        {
          for (int i = 0; i < m_colors; i++)
            {
              tempchannels[i] = m_images[i]->crop (yoffs, xoffs, ysize, xsize);
            }
          ColorImage templl (m_colors, m_cmodel, tempchannels);
      
          for (int i = 0; i < m_colors; i++)
            {
              DELETE (tempchannels[i]);
            }
      
          for (int row = 0; row < ysize; row++)
            {
              for (int col = 0; col < xsize; col++)
                {
                  for (int ch = 0; ch < m_colors; ch++)
                    {
                      m_images[ch]->to (yoffs + row, xoffs + col, fill);
                    }
                }
            }
      
          templl.beautify ();
          valadjust ();

          for (int i = 0; i < m_colors; i++)
            {
              m_images[i]->paste (yoffs, xoffs, templl.channel (i));
            }
        }
    }
  DELETE (tempchannels);
  current (oldcur);
}

coeff 
ColorVideo::averageColor (int fromY, int fromX, int toY, int toX)
{
  coeff ret = 0;
  int oldcur = current ();
  for (int frame = 0; frame < m_frames; frame++)
    {
      current (frame);
      
      for (int i = 0; i < m_colors; i++)
        {
          ret += m_images[i]->saverage (fromY, fromX, toY, toX);
        }
    }
  current (oldcur);
  return ret / (m_colors * m_frames);
}

unsigned char *
ColorVideo::toPixmap (clrmodel cmodel)
{
  clrmodel old = m_cmodel;
  if (old !=  cmodel)
    {
      colormodel (cmodel);
    }

  unsigned char *pixels = NEW (unsigned char [m_colors * m_xyzsize]);

  /* import */
  for (int i = 0; i < m_xyzsize; i++)
    {
      for (int j = 0; j < m_colors; j++)
        {
          coeff *ar = m_arrays[j]->array ();
          pixels[m_colors * i + j] = (unsigned char)tools_coeff2pixel (ar[i]);
        }
    }
  
  if (old !=  cmodel)
    {
      colormodel (old);
    }

  return pixels;
}

void 
ColorVideo::colormodelVideo (clrmodel model)
{
  m_cmodel = model;
  int oldcur = current ();
  for (int frame = 0; frame < m_frames; frame++)
    {
      current (frame);
      ColorBuffer::colormodel (m_cmodel);
    }
  current (oldcur);
}

bool 
ColorVideo::epsilonsVideo (ColorVideo &cv, coeff epsilon) const
{
  if (!(cv.frames () == this->frames ()
        && cv.rows () == this->rows () 
        && cv.cols () == this->cols ()
        && cv.size () == this->size ()))
    {
      return false;
    }

  for (int c = 0; c < m_colors; c++)
    {
      coeff *ar1 = m_arrays[c]->array ();
      coeff *ar2 = cv.m_arrays[c]->array ();
      for (int i = 0; i < m_xyzsize; i++)
        {
          if (ar2[i] > ar1[i])
            {
              if (ar2[i] - ar1[i] > epsilon)
                {
                  return false;
                }
            }
          else
            {
              if (ar1[i] - ar2[i] > epsilon)
                {
                  return false;
                }
            }
        }
    }
  
  return true;
}

void 
ColorVideo::reframe (int frames, bool keephead)
{
  for (int i = 0; i < m_colors; i++)
    {
      m_arrays[i]->reframe (frames, keephead);
    }
  m_frames = frames;
  m_xyzsize = m_xysize * m_frames;
}

void 
ColorVideo::resize (int rows, int cols)
{
  for (int i = 0; i < m_colors; i++)
    {
      m_arrays[i]->resize (rows, cols);
    }
  m_ysize = rows;
  m_xsize = cols;
  m_xysize = rows * cols;
  m_xyzsize = m_xysize * m_frames;
}

