/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * This file is part of the libmspub project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "libmspub_utils.h"

#include <cstdarg>
#include <cstring>
#include <string.h> // for memcpy

#include <unicode/ucnv.h>
#include <unicode/utypes.h>

#include <zlib.h>

#define ZLIB_CHUNK 16384

namespace libmspub
{

#ifdef DEBUG

void debugPrint(const char *const format, ...)
{
  va_list args;
  va_start(args, format);
  std::vfprintf(stderr, format, args);
  va_end(args);
}

#endif

using std::strcmp;
const char *windowsCharsetNameByOriginalCharset(const char *name)
{
  if (strcmp(name, "Shift_JIS") == 0)
  {
    return "windows-932";
  }
  if (strcmp(name, "GB18030") == 0)
  {
    return "windows-936";
  }
  if (strcmp(name, "Big5") == 0)
  {
    return "windows-950";
  }
  if (strcmp(name, "ISO-8859-1") == 0)
  {
    return "windows-1252";
  }
  if (strcmp(name, "ISO-8859-2") == 0)
  {
    return "windows-1250";
  }
  if (strcmp(name, "windows-1251") == 0)
  {
    return "windows-1251";
  }
  if (strcmp(name, "windows-1256") == 0)
  {
    return "windows-1256";
  }
  return nullptr;
}

const char *mimeByImgType(ImgType type)
{
  switch (type)
  {
  case PNG:
    return "image/png";
  case JPEG:
    return "image/jpeg";
  case DIB:
    return "image/bmp";
  case PICT:
    return "image/pict";
  case WMF:
    return "image/wmf";
  case EMF:
    return "image/emf";
  case TIFF:
    return "image/tiff";
  default:
    MSPUB_DEBUG_MSG(("Unknown image type %d passed to mimeByImgType!\n", type));
    return nullptr;
  }
}

void rotateCounter(double &x, double &y, double centerX, double centerY, short rotation)
{
  double vecX = x - centerX;
  double vecY = centerY - y;
  double sinTheta = sin(rotation * M_PI / 180.);
  double cosTheta = cos(rotation * M_PI / 180.);
  double newVecX = cosTheta * vecX - sinTheta * vecY;
  double newVecY = sinTheta * vecX + cosTheta * vecY;
  x = centerX + newVecX;
  y = centerY - newVecY;
}

double doubleModulo(double x, double y)
{
  if (y <= 0) // y <= 0 doesn't make sense
  {
    return x;
  }
  while (x < 0)
  {
    x += y;
  }
  while (x >= y)
  {
    x -= y;
  }
  return x;
}

double toFixedPoint(int fp)
{
  unsigned short fractionalPart = ((unsigned short) fp) & 0xFFFF;
  short integralPart = fp >> 16;
  return integralPart + fractionalPart / 65536.;
}

double readFixedPoint(librevenge::RVNGInputStream *input)
{
  return toFixedPoint(readS32(input));
}

void flipIfNecessary(double &x, double &y, double centerX, double centerY, bool flipVertical, bool flipHorizontal)
{
  double vecX = x - centerX;
  double vecY = centerY - y;
  if (flipVertical)
  {
    y = centerY + vecY;
  }
  if (flipHorizontal)
  {
    x = centerX - vecX;
  }
}

unsigned correctModulo(int x, unsigned n) // returns the canonical representation of x in Z/nZ
//difference with C++ % operator is that this never returns negative values.
{
  if (x < 0)
  {
    int result = x % (int)n;
    //sign of result is implementation defined
    if (result < 0)
    {
      return n + result;
    }
    return result;
  }
  return x % n;
}

librevenge::RVNGBinaryData inflateData(librevenge::RVNGBinaryData deflated)
{
  librevenge::RVNGBinaryData inflated;
  unsigned char out[ZLIB_CHUNK];
  const unsigned char *data = deflated.getDataBuffer();
  z_stream strm;
  int ret;
  strm.zalloc = Z_NULL;
  strm.zfree = Z_NULL;
  strm.opaque = Z_NULL;
  strm.avail_in = 0;
  strm.next_in = Z_NULL;
  if (inflateInit2(&strm,-MAX_WBITS) != Z_OK)
  {
    return librevenge::RVNGBinaryData();
  }
  int have;
  unsigned left = deflated.size();
  do
  {
    strm.avail_in = ZLIB_CHUNK > left ? left : ZLIB_CHUNK;
    strm.next_in = (unsigned char *)data;
    do
    {
      strm.avail_out = ZLIB_CHUNK;
      strm.next_out = out;
      ret = inflate(&strm, Z_NO_FLUSH);
      if (ret < 0 || ret == Z_NEED_DICT)
      {
        inflateEnd(&strm);
        return librevenge::RVNGBinaryData();
      }
      have = ZLIB_CHUNK - strm.avail_out;
      inflated.append(out, have);
    }
    while (strm.avail_out == 0);
    data += ZLIB_CHUNK > left ? left : ZLIB_CHUNK;
    left -= ZLIB_CHUNK > left ? left : ZLIB_CHUNK;
  }
  while (ret != Z_STREAM_END);
  inflateEnd(&strm);
  return inflated;
}

namespace
{

static void _appendUCS4(librevenge::RVNGString &text, unsigned ucs4Character)
{
  unsigned char first;
  int len;
  if (ucs4Character < 0x80)
  {
    first = 0;
    len = 1;
  }
  else if (ucs4Character < 0x800)
  {
    first = 0xc0;
    len = 2;
  }
  else if (ucs4Character < 0x10000)
  {
    first = 0xe0;
    len = 3;
  }
  else if (ucs4Character < 0x200000)
  {
    first = 0xf0;
    len = 4;
  }
  else if (ucs4Character < 0x4000000)
  {
    first = 0xf8;
    len = 5;
  }
  else
  {
    first = 0xfc;
    len = 6;
  }

  char outbuf[7] = { 0 };
  int i;
  for (i = len - 1; i > 0; --i)
  {
    outbuf[i] = char((ucs4Character & 0x3f) | 0x80);
    ucs4Character >>= 6;
  }
  outbuf[0] = char((ucs4Character & 0xff) | first);
  outbuf[len] = '\0';

  text.append(outbuf);
}

} // anonymous namespace

#define MSPUB_NUM_ELEMENTS(array) sizeof(array)/sizeof(array[0])

uint8_t readU8(librevenge::RVNGInputStream *input)
{
  if (!input || input->isEnd())
  {
    MSPUB_DEBUG_MSG(("Something bad happened here!"));
    if (input)
    {
      MSPUB_DEBUG_MSG((" Tell: %ld\n", input->tell()));
    }
    throw EndOfStreamException();
  }
  unsigned long numBytesRead;
  uint8_t const *p = input->read(sizeof(uint8_t), numBytesRead);

  if (p && numBytesRead == sizeof(uint8_t))
    return *(uint8_t const *)(p);
  throw EndOfStreamException();
}

uint16_t readU16(librevenge::RVNGInputStream *input)
{
  auto p0 = (uint16_t)readU8(input);
  auto p1 = (uint16_t)readU8(input);
  return (uint16_t)(p0|(p1<<8));
}

uint32_t readU32(librevenge::RVNGInputStream *input)
{
  auto p0 = (uint32_t)readU8(input);
  auto p1 = (uint32_t)readU8(input);
  auto p2 = (uint32_t)readU8(input);
  auto p3 = (uint32_t)readU8(input);
  return (uint32_t)(p0|(p1<<8)|(p2<<16)|(p3<<24));
}

int8_t readS8(librevenge::RVNGInputStream *input)
{
  return (int8_t)readU8(input);
}

int16_t readS16(librevenge::RVNGInputStream *input)
{
  return (int16_t)readU16(input);
}

int32_t readS32(librevenge::RVNGInputStream *input)
{
  return (int32_t)readU32(input);
}

uint64_t readU64(librevenge::RVNGInputStream *input)
{
  auto p0 = (uint64_t)readU8(input);
  auto p1 = (uint64_t)readU8(input);
  auto p2 = (uint64_t)readU8(input);
  auto p3 = (uint64_t)readU8(input);
  auto p4 = (uint64_t)readU8(input);
  auto p5 = (uint64_t)readU8(input);
  auto p6 = (uint64_t)readU8(input);
  auto p7 = (uint64_t)readU8(input);
  return (uint64_t)(p0|(p1<<8)|(p2<<16)|(p3<<24)|(p4<<32)|(p5<<40)|(p6<<48)|(p7<<56));
}

void readNBytes(librevenge::RVNGInputStream *input, unsigned long length, std::vector<unsigned char> &out)
{
  if (length == 0)
  {
    MSPUB_DEBUG_MSG(("Attempt to read 0 bytes!"));
    return;
  }

  unsigned long numBytesRead = 0;
  const unsigned char *tmpBuffer = input->read(length, numBytesRead);
  if (numBytesRead != length)
  {
    out.clear();
    return;
  }
  out = std::vector<unsigned char>(numBytesRead);
  memcpy(out.data(), tmpBuffer, numBytesRead);
  return;
}

unsigned long getLength(librevenge::RVNGInputStream *const input)
{
  if (!input)
    throw EndOfStreamException();

  const long orig = input->tell();

  unsigned long end = 0;

  if (0 == input->seek(0, librevenge::RVNG_SEEK_END))
  {
    end = static_cast<unsigned long>(input->tell());
  }
  else
  {
    // RVNG_SEEK_END does not work. Use the harder way.
    if (0 != input->seek(0, librevenge::RVNG_SEEK_SET))
      throw EndOfStreamException();
    while (!input->isEnd())
    {
      readU8(input);
      ++end;
    }
  }

  if (0 != input->seek(orig, librevenge::RVNG_SEEK_SET))
    throw EndOfStreamException();

  return end;
}

#define SURROGATE_VALUE(h,l) (((h) - 0xd800) * 0x400 + (l) - 0xdc00 + 0x10000)


void appendCharacters(librevenge::RVNGString &text, const std::vector<unsigned char> &characters,
                      const char *encoding)
{
  if (characters.empty())
  {
    MSPUB_DEBUG_MSG(("Attempt to append 0 characters!"));
    return;
  }

  UErrorCode status = U_ZERO_ERROR;
  UConverter *conv = nullptr;
  conv = ucnv_open(encoding, &status);
  if (U_SUCCESS(status))
  {
    // ICU documentation claims that character-by-character processing is faster "for small amounts of data" and "'normal' charsets"
    // (in any case, it is more convenient :) )
    const auto *src = (const char *)characters.data();
    const char *srcLimit = (const char *)src + characters.size();
    while (src < srcLimit)
    {
      auto ucs4Character = (uint32_t)ucnv_getNextUChar(conv, &src, srcLimit, &status);
      if (U_SUCCESS(status))
      {
        _appendUCS4(text, ucs4Character);
      }
    }
  }
  if (conv)
  {
    ucnv_close(conv);
  }
}

bool stillReading(librevenge::RVNGInputStream *input, unsigned long until)
{
  if (input->isEnd())
    return false;
  if (input->tell() < 0)
    return false;
  if ((unsigned long)input->tell() >= until)
    return false;
  return true;
}

}

/* vim:set shiftwidth=2 softtabstop=2 expandtab: */
