// --------------------------------------------------------------------
// Various utility classes
// --------------------------------------------------------------------
/*

    This file is part of the extensible drawing editor Ipe.
    Copyright (C) 1993-2004  Otfried Cheong

    Ipe 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.

    As a special exception, you have permission to link Ipe with the
    CGAL library and distribute executables, as long as you follow the
    requirements of the Gnu General Public License in regard to all of
    the software in the executable aside from CGAL.

    Ipe is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
    License for more details.

    You should have received a copy of the GNU General Public License
    along with Ipe; if not, you can find it at
    "http://www.gnu.org/copyleft/gpl.html", or write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include "ipeutils.h"
#include "ipepage.h"
#include "ipegroup.h"
#include "iperef.h"
#include "ipeimage.h"
#include "ipetext.h"

#include <zlib.h>

// --------------------------------------------------------------------

/*! \class IpeBitmapFinder
  \ingroup high
  \brief A visitor that recursively scans objects and collects all bitmaps.
*/

void IpeBitmapFinder::ScanPage(const IpePage *page)
{
  for (IpePage::const_iterator it = page->begin(); it != page->end(); ++it)
    (*this)(*it);
}

void IpeBitmapFinder::VisitGroup(const IpeGroup *obj)
{
  for (IpeGroup::const_iterator it = obj->begin(); it != obj->end(); ++it)
    (*it)->Accept(*this);
}

void IpeBitmapFinder::VisitReference(const IpeReference *obj)
{
  if (obj->Object())
    obj->Object()->Accept(*this);
}

void IpeBitmapFinder::VisitImage(const IpeImage *obj)
{
  iBitmaps.push_back(obj->Bitmap());
}

// --------------------------------------------------------------------

/*! \class IpeBBoxPainter
  \ingroup high
  \brief Paint objects using this painter to compute an accurate bounding box.

  The IpeObjct::BBox member function computes a bounding box useful
  for distance calculations and optimizations.  To find a bounding box
  that is accurate for the actual \b drawn object, paint the object
  using an IpeBBoxPainter, and retrieve the box with BBox.
*/

IpeBBoxPainter::IpeBBoxPainter(const IpeStyleSheet *style)
  : IpePainter(style)
{
  // nothing else
}

void IpeBBoxPainter::DoMoveTo(const IpeVector &v)
{
  iV = Matrix() * v;
  Add(iV);
}

void IpeBBoxPainter::DoLineTo(const IpeVector &v)
{
  iV = Matrix() * v;
  Add(iV);
}

void IpeBBoxPainter::DoCurveTo(const IpeVector &v1, const IpeVector &v2,
			       const IpeVector &v3)
{
  IpeBezier bez(iV, Matrix() * v1, Matrix() * v2, Matrix() * v3);
  IpeRect bb = bez.BBox();
  double lw = 0.0;
  if (LineWidth())
    lw = Repository()->ToScalar(LineWidth()).ToDouble();
  iBBox.AddPoint(bb.Min() - IpeVector(lw, lw));
  iBBox.AddPoint(bb.Max() + IpeVector(lw, lw));
  iV = Matrix() * v3;
}

void IpeBBoxPainter::DoDrawBitmap(IpeBitmap)
{
  iBBox.AddPoint(Matrix() * IpeVector(0.0, 0.0));
  iBBox.AddPoint(Matrix() * IpeVector(0.0, 1.0));
  iBBox.AddPoint(Matrix() * IpeVector(1.0, 1.0));
  iBBox.AddPoint(Matrix() * IpeVector(1.0, 0.0));
}

void IpeBBoxPainter::DoDrawText(const IpeText *text)
{
  iBBox.AddPoint(Matrix() * IpeVector(0,0));
  iBBox.AddPoint(Matrix() * IpeVector(0, text->TotalHeight()));
  iBBox.AddPoint(Matrix() * IpeVector(text->Width(), text->TotalHeight()));
  iBBox.AddPoint(Matrix() * IpeVector(text->Width(), 0));
}

void IpeBBoxPainter::Add(const IpeVector &pos)
{
  double lw = 0.0;
  if (LineWidth())
    lw = Repository()->ToScalar(LineWidth()).ToDouble();
  iBBox.AddPoint(IpeVector(pos.iX - lw, pos.iY - lw));
  iBBox.AddPoint(IpeVector(pos.iX + lw, pos.iY + lw));
}

// --------------------------------------------------------------------

/*! \class IpeA85Stream
  \ingroup high
  \brief Filter stream adding ASCII85 encoding.
*/

inline uint A85Word(const uchar *p)
{
  return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
}

inline void A85Encode(uint w, char *p)
{
  p[4] = char(w % 85 + 33);
  w /= 85;
  p[3] = char(w % 85 + 33);
  w /= 85;
  p[2] = char(w % 85 + 33);
  w /= 85;
  p[1] = char(w % 85 + 33);
  p[0] = char(w / 85 + 33);
}

IpeA85Stream::IpeA85Stream(IpeStream &stream)
  : iStream(stream)
{
  iN = 0;
  iCol = 0;
}

void IpeA85Stream::PutChar(char ch)
{
  iCh[iN++] = ch;
  if (iN == 4) {
    // encode and write
    uint w = A85Word(iCh);
    if (w == 0) {
      iStream.PutChar('z');
      ++iCol;
    } else {
      char buf[6];
      buf[5] = '\0';
      A85Encode(w, buf);
      iStream.PutCString(buf);
      iCol += 5;
    }
    if (iCol > 70) {
      iStream.PutChar('\n');
      iCol = 0;
    }
    iN = 0;
  }
}

void IpeA85Stream::Close()
{
  if (iN) {
    for (int k = iN; k < 4; ++k)
      iCh[k] = 0;
    uint w = A85Word(iCh);
    char buf[6];
    A85Encode(w, buf);
    buf[iN + 1] = '\0';
    iStream.PutCString(buf);
  }
  iStream.PutCString("~>\n");
  iStream.Close();
}

// --------------------------------------------------------------------

/*! \class IpeA85Source
  \ingroup high
  \brief Filter source adding ASCII85 decoding.
*/

IpeA85Source::IpeA85Source(IpeDataSource &source)
  : iSource(source)
{
  iEof = false;
  iN = 0;     // number of characters buffered
  iIndex = 0;  // next character to return
}

int IpeA85Source::GetChar()
{
  if (iIndex < iN)
    return iBuf[iIndex++];

  if (iEof)
    return EOF;

  int ch;
  do {
    ch = iSource.GetChar();
  } while (ch == '\n' || ch == '\r' || ch == ' ');

  if (ch == '~' || ch == EOF) {
    iEof = true;
    iN = 0;      // no more data, immediate EOF
    return EOF;
  }

  iIndex = 1;
  iN = 4;

  if (ch == 'z') {
    iBuf[0] = iBuf[1] = iBuf[2] = iBuf[3] = 0;
    return iBuf[0];
  }

  int c[5];
  c[0] = ch;
  for (int k = 1; k < 5; ++k) {
    do {
      c[k] = iSource.GetChar();
    } while (c[k] == '\n' || c[k] == '\r' || c[k] == ' ');
    if (c[k] == '~' || c[k] == EOF) {
      iN = k - 1;
      iEof = true;
      break;
    }
  }

  for (int k = iN + 1; k < 5; ++k)
    c[k] = 0x21 + 84;

  uint t = 0;
  for (int k = 0; k < 5; ++k)
    t = t * 85 + (c[k] - 0x21);

  for (int k = 3; k >= 0; --k) {
    iBuf[k] = char(t & 0xff);
    t >>= 8;
  }

  return iBuf[0];
};

// --------------------------------------------------------------------

/*! \class IpeDeflateStream
  \ingroup high
  \brief Filter stream adding flate compression.
*/

struct IpeDeflateStream::Private {
  z_stream iFlate;
};

IpeDeflateStream::IpeDeflateStream(IpeStream &stream, int level)
  : iStream(stream), iIn(0x400), iOut(0x400) // create buffers
{
  iPriv = new Private;
  z_streamp z = &iPriv->iFlate;

  z->zalloc = 0;
  z->zfree = 0;
  z->opaque = 0;

  int err = deflateInit(z, level);
  if (err != Z_OK) {
    ipeDebug("deflateInit returns error %d", err);
    assert(false);
  }

  iN = 0;
}

IpeDeflateStream::~IpeDeflateStream()
{
  if (iPriv) {
    z_streamp z = &iPriv->iFlate;
    deflateEnd(z);
    delete iPriv;
  }
}

void IpeDeflateStream::PutChar(char ch)
{
  iIn[iN++] = ch;
  if (iN < iIn.size())
    return;

  // compress and write
  z_streamp z = &iPriv->iFlate;
  z->next_in = (Bytef *) iIn.data();
  z->avail_in = iIn.size();
  while (z->avail_in) {
    z->next_out = (Bytef *) iOut.data();
    z->avail_out = iOut.size();
    int err = deflate(z, Z_NO_FLUSH);
    if (err != Z_OK) {
      ipeDebug("deflate returns error %d", err);
      assert(false);
    }
    // save output
    iStream.PutRaw(iOut.data(), z->next_out - (Bytef *) iOut.data());
  }
  iN = 0;
}

void IpeDeflateStream::Close()
{
  // compress and write remaining data
  z_streamp z = &iPriv->iFlate;
  z->next_in = (Bytef *) iIn.data();
  z->avail_in = iN;

  int err;
  do {
    z->next_out = (Bytef *) iOut.data();
    z->avail_out = iOut.size();
    err = deflate(z, Z_FINISH);
    if (err != Z_OK && err != Z_STREAM_END) {
      ipeDebug("deflate returns error %d", err);
      assert(false);
    }
    iStream.PutRaw(iOut.data(), z->next_out - (Bytef *) iOut.data());
  } while (err == Z_OK);

  err = deflateEnd(z);
  if (err != Z_OK) {
    ipeDebug("deflateEnd returns error %d", err);
    assert(false);
  }

  delete iPriv;
  iPriv = 0; // make sure no more writing possible
  iStream.Close();
}

//! Deflate a buffer in a single run.
/*! The returned buffer may be larger than necessary: \a deflatedSize is set to
  the number of bytes actually used. */
IpeBuffer IpeDeflateStream::Deflate(const char *data, int size, int &deflatedSize, int compressLevel)
{
  ulong dfsize = ulong(size * 1.001 + 13);
  IpeBuffer deflatedData(dfsize);
  int err = compress2((Bytef *) deflatedData.data(), &dfsize,
		      (const Bytef *) data, size, compressLevel);
  if (err != Z_OK) {
    ipeDebug("Zlib compress2 returns errror %d", err);
    assert(false);
  }
  deflatedSize = dfsize;
  return deflatedData;
}

// --------------------------------------------------------------------

/*! \class IpeInflateSource
  \ingroup high
  \brief Filter source adding flate decompression.
*/

struct IpeInflateSource::Private {
  z_stream iFlate;
};

IpeInflateSource::IpeInflateSource(IpeDataSource &source)
  : iSource(source), iIn(0x400), iOut(0x400)
{
  iPriv = new Private;
  z_streamp z = &iPriv->iFlate;

  z->zalloc = Z_NULL;
  z->zfree = Z_NULL;
  z->opaque = Z_NULL;

  FillBuffer();

  int err = inflateInit(z);
  if (err != Z_OK) {
    ipeDebug("inflateInit returns error %d", err);
    delete iPriv;
    iPriv = 0;  // set EOF
    return;
  }

  iP = iOut.data();
  z->next_out = (Bytef *) iP;
}

IpeInflateSource::~IpeInflateSource()
{
  if (iPriv) {
    z_streamp z = &iPriv->iFlate;
    inflateEnd(z);
    delete iPriv;
  }
}

void IpeInflateSource::FillBuffer()
{
  char *p = iIn.data();
  char *p1 = iIn.data() + iIn.size();
  z_streamp z = &iPriv->iFlate;
  z->next_in = (Bytef *) p;
  z->avail_in = 0;
  while (p < p1) {
    int ch = iSource.GetChar();
    if (ch == EOF)
      return;
    *p++ = char(ch);
    z->avail_in++;
  }
}

//! Get one more character, or EOF.
int IpeInflateSource::GetChar()
{
  if (!iPriv)
    return EOF;

  z_streamp z = &iPriv->iFlate;
  if (iP < (char *) z->next_out)
    return *iP++;

  // next to decompress some data
  if (z->avail_in == 0)
    FillBuffer();

  if (z->avail_in > 0) {
    // data is available
    z->next_out = (Bytef *) iOut.data();
    z->avail_out = iOut.size();
    int err = inflate(z, Z_NO_FLUSH);
    if (err != Z_OK && err != Z_STREAM_END) {
      ipeDebug("inflate returns error %d", err);
      inflateEnd(z);
      delete iPriv;
      iPriv = 0;  // set EOF
      return EOF;
    }
    iP = iOut.data();
    if (iP < (char *) z->next_out)
      return *iP++;
    // didn't get any new data, must be EOF
  }

  // FillBuffer didn't get any data, must be EOF, so we are done
  inflateEnd(z);
  delete iPriv;
  iPriv = 0;
  return EOF;
}

// --------------------------------------------------------------------
