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

//#undef DPRINTF

#include <cstdio>
#ifndef _WIN32_WCE
#include <iostream>
#include <iomanip>
#endif
#include <stdexcept>
#include <cassert>
#include <cmath>
#include "Wave/Wavelet.hh"
#include "Wave/MirrorPosition.hh"
#include "Wave/PeriodicPosition.hh"
#include "WImage/miscdefs.h"
#include "WImage/debug.h"
#include "WImage/tools.h"

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

void
Wavelet::init (FilterSet *pfset)
{
  bool lleft = true, hright = true, hsymm = false;
  m_analow = NEW (Filter (pfset->alow ()));
  m_anahigh = NEW (Filter (pfset->ahigh ()));
  m_synlow = NEW (Filter (pfset->slow ()));
  m_synhigh = NEW (Filter (pfset->shigh ()));
  m_symmetric = pfset->issym ();


  /*
   * amount of space to leave for padding
   * vectors for symmetric extensions
   */
  m_npad = MAX (m_analow->fsize (), m_anahigh->fsize ());
  if (m_symmetric)
    {
      DPRINTF (("Symmetric Wavelet: mirror padding: %d\n", m_npad));
      if (m_analow->fsize () % 2 != 0)
        {
          /*
           * Odd filter length, no extra mirror axis on low left
           * or high right
           */
          m_apad  = NEW (MirrorPosition (false, false));
          lleft  = false;
          hright = false;
        }
      else
        {
          /*
           * Even filter length, we get symmetric sign-change.
           */
          m_apad  = NEW (MirrorPosition (true, true));
          hsymm  = true;
        }
      m_slpad = NEW (MirrorPosition (lleft, true));
      m_shpad = NEW (MirrorPosition (true, hright));

      /* always symmetric */
      ((MirrorPosition*)m_slpad)->setsymm (true);
      ((MirrorPosition*)m_shpad)->setsymm (hsymm);
    }
  else
    {
      DPRINTF (("Asymmetric Wavelet: periodic padding: %d\n", m_npad));
      m_apad  = NEW (PeriodicPosition ());
      m_slpad = NEW (PeriodicPosition ());
      m_shpad = NEW (PeriodicPosition ());
    }

  m_apositions = NULL;
  m_apsize = 0;

  m_slpositions = NULL;
  m_slpsize = 0;
  m_shpositions = NULL;
  m_shsigns = NULL;
  m_shpsize = 0;
}


Wavelet::Wavelet (FilterSet &fset)
{
  init (&fset);
}

Wavelet::~Wavelet (void)
{
  DELETE (m_analow);
  DELETE (m_anahigh);
  DELETE (m_synlow);
  DELETE (m_synhigh);
  DELETE (m_apad);
  DELETE (m_slpad);
  DELETE (m_shpad);
  DELETENOTNULLAR (m_apositions);
  DELETENOTNULLAR (m_slpositions);
  DELETENOTNULLAR (m_shpositions);
  DELETENOTNULLAR (m_shsigns);
}

void
Wavelet::analysis (ReferenceVector &dest, ReferenceVector &src, int steps)
{
  ReferenceVector *s = &src;
  ReferenceVector *d = &dest;
  ReferenceVector *t;
  int loSize = src.size ();
  int hiSize = 0;

  DPRINTF (("analysis: steps == %d\n", steps));

  (void)level (dest, src, steps);
  for (; steps > 0; steps--, t = s, s = d, d = t)
    {
      if (loSize <= 2 && m_symmetric)
        {
          throw invalid_argument ("analysis: "
                                  "Low pass subband is too small.");
        }

      anastep (*d, *s, loSize);

      /*
       * Optimization: Only the second half of what has been
       * written into src needs to be available in dest
       */
      hiSize = loSize / 2;
      loSize = DIV2 (loSize + 1);
      if (d == &src)
        {
          for (int i = loSize; i < loSize + hiSize; i++)
            {
              s->to (i, d->at (i));
            }
        }
    }

  /*
   * If the last write went to src, we must copy a missing bit into
   * dest now...
   */
  if (s == &src)
    {
      for (int i = 0; i < loSize; i++)
        {
          dest.to (i, src.at (i));
        }
    }
}

void
Wavelet::synthesis (ReferenceVector &dest, ReferenceVector &src,
                    int steps, int prevSteps)
{
  m_slpad->size (src.size ());
  m_shpad->size (src.size ());

  int *loSize = NEW (int [steps]);
  int *hiSize = NEW (int [steps]);

  loSize[0] = (src.size () + 1) / 2;
  hiSize[0] = src.size () / 2;

  for (int i = 1; i < prevSteps; i++)
    {
      loSize[i] = DIV2 (loSize [i - 1] + 1);
      hiSize[i] = DIV2 (loSize [i - 1]);
    }

  for (; steps > 0; steps--)
    {
      synstep (dest, src, hiSize[steps - 1] + loSize[steps - 1]);

      /*
       * As with reconstruction we start with writing less until we
       * write the whole vector we only copy updated cols back to
       * the source. After the last write we don't copy anything back
       * anymore.
       */
      if (steps > 1)
        {
          for (int i = 0; i < hiSize[steps - 1] + loSize[steps - 1]; i++)
            {
              src.to (i, dest.at (i));
            }
        }
    }
  DELETE (hiSize);
  DELETE (loSize);
}

int
Wavelet::level (ReferenceVector &dest, ReferenceVector &src, int steps)
{
  int ret = 0;

  (void)tools_powerOfTwo (src.size (), ret);

  if (src.size () != dest.size ())
    {
      throw invalid_argument ("level: vector sizes differ.");
    }
  if (steps > ret)
    {
      throw invalid_argument ("level: more steps than levels..");
    }
  return ret;
}

void
Wavelet::anastep (ReferenceVector &dest, ReferenceVector &src, int size)
{
  int halfend = DIV2 (size + 1);
  int basepos1, basepos2;

  DPRINTF (("anastep: size == %d\n", size));

  if (m_apsize < size + 2 * m_npad)
    {
      DELETENOTNULLAR (m_apositions);
      m_apositions = NEW (int [size + 2 * m_npad]);
    }

  m_apad->size (size);

  for (int i = -m_npad; i < size + m_npad; i++)
    {
      m_apositions[i+m_npad] = m_apad->pos (i, size);
    }

  for (int i = 0; i < halfend; i++)
    {
      dest.to (i, 0.0);
      basepos1 = MUL2 (i) + m_analow->first ();
      for (int j = 0; j < m_analow->fsize (); j++)
        {
          DPRINTF (("dst[%2d] = dst[%2d] + src[%2d] * analysisLow [%2d]\n",
                    i, i, m_apositions[basepos1 + j + m_npad], j));
          DPRINTF (("dst[%2d] = %+7.2f + %+7.2f * %+7.2f  =====> %+8.2f\n",
                    i, dest.at (i),
                    src.at (m_apositions[basepos1 + j + m_npad]),
                    m_analow->at (j),
                    dest.at (i)
                    + src.at (m_apositions[basepos1 + j + m_npad])
                    * m_analow->at (j)));
          dest.to (i, dest.at (i)
                   + src.at (m_apositions[basepos1 + j + m_npad])
                   * m_analow->at (j));
        }
      DPRINTF (("\n"));

      if (i == size / 2)
        {
          assert (size / 2 < halfend);
          continue;
        }

      dest.to (halfend+i, 0.0);
      basepos1 = MUL2 (i) + m_anahigh->first ();
      basepos2 = halfend+i;
      for (int j = 0; j < m_anahigh->fsize (); j++)
        {
          DPRINTF (("dst[%2d] = dst[%2d] + src[%2d] * analysisHigh[%2d]\n",
                    basepos2, basepos2,
                    m_apositions[basepos1 + j + m_npad], j));
          DPRINTF (("dst[%2d] = %+7.2f + %+7.2f * %+7.2f  =====> %+8.2f\n",
                    i, dest.at (basepos2),
                    src.at (m_apositions[basepos1 + j + m_npad]),
                    m_anahigh->at (j),
                    dest.at (basepos2)
                    + src.at (m_apositions[basepos1 + j + m_npad])
                    * m_anahigh->at (j)));
          dest.to (basepos2, dest.at (basepos2)
                   + src.at (m_apositions[basepos1 + j + m_npad])
                   * m_anahigh->at (j));
        }
      DPRINTF (("\n\n"));
    }
}

void
Wavelet::synstep (ReferenceVector &dest, ReferenceVector &src, int size)
{
  int loSize = (size+1)/2;
  int hiSize = size/2;


  int first = m_synlow->first ();
  int last  = m_synlow->fsize () - 1 + first;
  int loopend;
  int cur = 0;
  int basepos1;

  for (int x = 0; x < size; x++)
    {
      dest.to (x, 0.0);
    }

  if (m_slpsize < loSize + 2 * m_npad)
    {
      DELETENOTNULLAR (m_slpositions);
      m_slpositions = NEW (int [loSize + 2 * m_npad]);
    }

  if (m_shpsize < hiSize + 2 * m_npad)
    {
      DELETENOTNULLAR (m_shpositions);
      m_shpositions = NEW (int [hiSize + 2 * m_npad]);
      DELETENOTNULLAR (m_shsigns);
      m_shsigns = NEW (int [hiSize + 2 * m_npad]);
    }

  m_slpad->size (loSize);
  if (m_symmetric)
    {
      ((MirrorPosition*)m_slpad)->setraxis (size % 2 == 0? true: false);
    }

  for (int i = -m_npad; i < loSize + m_npad; i++)
    {
      m_slpositions[i+m_npad] = m_slpad->pos (i, loSize);
    }

  loopend = DIV2 (size-1-first);
  for (int i = -last/2; i <= loopend; i++)
    {
      basepos1 = 2 * i + first;
      coeff value = src.at (m_slpositions[i+m_npad]);
      for (int j = 0; j < m_synlow->fsize (); j++)
        {
          cur = basepos1 + j;
          if (cur >= 0 && cur < size)
            {
              dest.to (cur, dest.at (cur) + value * m_synlow->at (j));
            }
        }
    }


  m_slpad->size (hiSize);
  if (m_symmetric)
    {
      ((MirrorPosition*)m_shpad)->setraxis ((m_analow->fsize () % 2 == 0)
                                            == (size % 2 == 0)? true: false);
    }

  for (int i = -m_npad; i < hiSize + m_npad; i++)
    {
      m_shpositions[i+m_npad] = m_shpad->pos (i, loSize, hiSize,
                                              m_shsigns[i+m_npad]);
    }

  first = m_synhigh->first ();
  last  = m_synhigh->fsize () - 1 + first;

  loopend = DIV2 (size-1-first);
  for (int i = -last/2; i <= loopend; i++)
    {
      basepos1 = 2 * i + first;
      coeff value = src.at (m_shpositions[i+m_npad]);
      if (i < 0 || i >= hiSize)
        {
          value *= (coeff)m_shsigns[i+m_npad];
        }

      for (int j = 0; j < m_synhigh->fsize (); j++)
        {
          cur = basepos1 + j;

          if (cur >= 0 && cur < size)
            {
              dest.to (cur, dest.at (cur) + value * m_synhigh->at (j));
            }
        }
    }
}


