/* This file is part of MetaDOM
 * a generic bind package for the Document Object Model API.
 * Copyright (C) 2001  Luca Padovani <luca.padovani@cs.unibo.it>
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * For more information, please visit the author's home page
 * http://www.cs.unibo.it/~lpadovan
 * or send an email to <luca.padovani@cs.unibo.it>
 */

#include <config.h>

#include <glib.h>
#include <assert.h>
#include <iconv.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>

#if defined(WORDS_BIGENDIAN)
#define ICONV_UTF16 ICONV_UTF16BE
#else
#if !defined(ICONV_UTF16LE)
#define ICONV_UTF16 ICONV_UTF16BE
#define UTF16_BIGENDIAN
#else
#define ICONV_UTF16 ICONV_UTF16LE
#endif
#endif

#if defined(WORDS_BIGENDIAN)
#define ICONV_UCS4 ICONV_UCS4BE
#else
#if !defined(ICONV_UCS4LE)
#define ICONV_UCS4 ICONV_UCS4BE
#define UCS4_BIGENDIAN
#else
#define ICONV_UCS4 ICONV_UCS4LE
#endif
#endif

#include "GdomeSmartDOMDOMString.hh"

namespace GdomeSmartDOM {

static size_t
convertString(iconv_t cd, const void* source, size_t inBytesLeft, void** dest)
{
  assert(cd != (iconv_t) -1);
  assert(source != NULL);
  assert(dest != NULL);

  static char buffer[128];
  size_t outBytesLeft = sizeof(buffer);

#ifdef ICONV_CONST
  const char* inbuf = reinterpret_cast<const char*>(source);
#else
  char* inbuf = new char[inBytesLeft];
  assert(inbuf != NULL);
  memcpy(inbuf, source, inBytesLeft);
  char* inbuf0 = inbuf;
#endif // ICONV_CONST

  char* outbuf = reinterpret_cast<char*>(buffer);
  char* outbuf0 = buffer;

  size_t nConv = 0;
  char* res = NULL;

  while (inBytesLeft > 0) {
    //cout << "before: " << (void*) inbuf << " " << inBytesLeft << " " << (void*) outbuf << " " << outBytesLeft << endl;
    size_t iconv_res = iconv(cd, &inbuf, &inBytesLeft, &outbuf, &outBytesLeft);
    //cout << "after: " << (void*) inbuf << " " << inBytesLeft << " " << (void*) outbuf << " " << outBytesLeft << endl;

    unsigned n = outbuf - outbuf0;
    if (res == NULL) {
      nConv = n;
      res = new char[nConv];
      assert(res != NULL);
      memcpy(res, buffer, n);
    } else {
      char* newRes = new char[n + nConv];
      assert(res != NULL);
      memcpy(newRes, res, nConv);
      memcpy(newRes + nConv, buffer, n);
      delete [] res;
      res = newRes;
      nConv += n;
    }

    if (iconv_res == -1) {
      if (errno == E2BIG) {
	outbuf = outbuf0;
	outBytesLeft = sizeof(buffer);
      } else {
	perror("iconv: ");
	break;
      }
    }
  }

  *dest = res;

#ifndef ICONV_CONST
  delete [] inbuf0;
#endif // ICONV_CONST

  return nConv;
}

class DOMStringBuffer
{
public:
  DOMStringBuffer(unsigned);
  DOMStringBuffer(const Char16*, unsigned);

  void ref(void);
  void unref(void);
  bool isEmpty(void) const;

  friend class DOMString;

protected:
  ~DOMStringBuffer();

private:
  unsigned counter;
  unsigned size;
  Char16* buffer;
};

DOMStringBuffer::DOMStringBuffer(unsigned size0)
{
  counter = 1;
  size = size0;
  if (size > 0) buffer = new Char16[size];
  else buffer = NULL;
}

DOMStringBuffer::DOMStringBuffer(const Char16* b0, unsigned s0)
{
  assert(b0 != NULL);

  counter = 1;
  size = s0;
  if (s0 > 0) {
    buffer = new Char16[size];
    for (unsigned i = 0; i < size; i++) buffer[i] = b0[i];
  } else
    buffer = NULL;
}

DOMStringBuffer::~DOMStringBuffer()
{
  assert(counter == 0);
  size = 0;
  delete [] buffer;
  buffer = NULL;
}

void
DOMStringBuffer::ref()
{
  counter++;
}

void
DOMStringBuffer::unref()
{
  assert(counter > 0);
  if (--counter == 0) delete this;
}

bool
DOMStringBuffer::isEmpty() const
{
  for (unsigned i = 0; i < size; i++)
    if (buffer[i] != 0x09 && buffer[i] != 0x0a && buffer[i] != 0x0d && buffer[i] != 0x20)
      return false;
  
  return true;
}

DOMString::DOMString()
{
  ptr = NULL;
}

DOMString::DOMString(const DOMString& s)
{
  ptr = NULL;
  *this = s;
}

DOMString::DOMString(const char* buffer)
{
  ptr = NULL;
  if (buffer != NULL) fromC(buffer);
}

DOMString::DOMString(const char* buffer, unsigned size)
{
  ptr = NULL;
  if (buffer != NULL) fromC(buffer, size);
}

DOMString::~DOMString()
{
  if (ptr != NULL) {
    ptr->unref();
    ptr = NULL;
  }
}

bool
DOMString::isEmpty() const
{
  if (ptr == NULL) throw NullString();
  return ptr->isEmpty();
}

bool
DOMString::operator==(const DOMString& s) const
{
  if (ptr == NULL) 
    return s.ptr == NULL;
  else {
    if (s.ptr == NULL) return false;
    if (ptr->size != s.ptr->size) return false;
    for (unsigned i = 0; i < ptr->size; i++)
      if (ptr->buffer[i] != s.ptr->buffer[i]) return false;
    return true;
  }
}

bool
DOMString::operator==(const char* s) const
{
  if (ptr == NULL)
    return s == NULL;
  else {
    if (s == NULL) return false;
    for (unsigned i = 0; i < ptr->size && s[i] != '\0'; i++)
      if (ptr->buffer[i] != s[i]) return false;
    return true;
  }
}

DOMString&
DOMString::operator=(const DOMString& s)
{
  if (this != &s) {
    if (s.ptr != NULL) s.ptr->ref();
    if (ptr != NULL) ptr->unref();
    ptr = s.ptr;
  }

  return *this;
}

DOMString&
DOMString::operator=(const char* s)
{
  fromC(s);
  return *this;
}

DOMString&
DOMString::operator+=(const DOMString& s)
{
  if (ptr == NULL || s.ptr == NULL) throw NullString();
  DOMStringBuffer* newBuffer = new DOMStringBuffer(ptr->size + s.ptr->size);
  memcpy(newBuffer->buffer, ptr->buffer, ptr->size * sizeof(Char16));
  memcpy(newBuffer->buffer + ptr->size, s.ptr->buffer, s.ptr->size * sizeof(Char16));
  ptr->unref();
  ptr = newBuffer;
  return *this;
}

DOMString&
DOMString::operator+=(const char* s)
{
  if (ptr == NULL || s == NULL) throw NullString();
  unsigned s_size = strlen(s);
  DOMStringBuffer* newBuffer = new DOMStringBuffer(ptr->size + s_size);
  memcpy(newBuffer->buffer, ptr->buffer, ptr->size * sizeof(Char16));
  for (unsigned i = 0; i < s_size; i++) newBuffer->buffer[i + ptr->size] = s[i];
  ptr->unref();
  ptr = newBuffer;
  return *this;
}

DOMString
DOMString::operator+(const DOMString& s) const
{
  DOMString res = clone();
  res += s;
  return res;
}

DOMString
DOMString::operator+(const char* s) const
{
  DOMString res = clone();
  res += s;
  return res;
}

unsigned
DOMString::length() const
{
  if (ptr == NULL) throw NullString();
  return ptr->size;
}

Char16
DOMString::at(unsigned i) const
{
  if (ptr == NULL) throw NullString();
  if (i >= ptr->size) throw IndexOutOfRange();
  return ptr->buffer[i];
}

Char16&
DOMString::operator[](unsigned i)
{
  if (ptr == NULL) throw NullString();
  if (i >= ptr->size) throw IndexOutOfRange();
  return ptr->buffer[i];
}

char*
DOMString::toC() const
{
  if (ptr == NULL) throw NullString();

  char* res = new char[ptr->size + 1];
  for (unsigned i = 0; i < ptr->size; i++) res[i] = ptr->buffer[i];
  res[ptr->size] = '\0';

  return res;
}

Char8*
DOMString::toUTF8(unsigned& length) const
{
  if (ptr == NULL) throw NullString();
  if (ptr->size == 0) {
    length = 0;
    return NULL;
  }

#if !defined(WORDS_BIGENDIAN) && defined(UTF16_BIGENDIAN)
  Char16* sourceBuffer = new Char16[ptr->size];
  for (unsigned i = 0; i < ptr->size; i++)
    sourceBuffer[i] = GUINT16_SWAP_LE_BE(ptr->buffer[i]);
#else
  Char16* sourceBuffer = ptr->buffer;
#endif

  static bool first = true;
  static iconv_t cd;
  if (first) {
    cd = iconv_open(ICONV_UTF8, ICONV_UTF16);
    assert(cd != (iconv_t) -1);
    first = false;
  }

  Char8* res;
  length = convertString(cd, sourceBuffer, ptr->size * sizeof(Char16), reinterpret_cast<void**>(&res));
  length /= sizeof(Char8);

#if !defined(WORDS_BIGENDIAN) && defined(UTF16_BIGENDIAN)
  delete [] sourceBuffer;
#endif

  return res;
}

Char16*
DOMString::toUTF16(unsigned& length) const
{
  if (ptr == NULL) throw NullString();
  if (ptr->size == 0) {
    length = 0;
    return NULL;
  }

  Char16* res = new Char16[ptr->size];
  assert(res != NULL);

  memcpy(res, ptr->buffer, ptr->size * sizeof(Char16));
  length = (ptr->size * sizeof(Char16)) / sizeof(Char16);

  return res;
}

Char32*
DOMString::toUnicode(unsigned& length) const
{
  if (ptr == NULL) throw NullString();
  if (ptr->size == 0) {
    length = 0;
    return NULL;
  }

#if !defined(WORDS_BIGENDIAN) && defined(UTF16_BIGENDIAN)
  Char16* sourceBuffer = new Char16[ptr->size];
  for (unsigned i = 0; i < ptr->size; i++)
    sourceBuffer[i] = GUINT16_SWAP_LE_BE(ptr->buffer[i]);
#else
  Char16* sourceBuffer = ptr->buffer;
#endif

  static bool first = true;
  static iconv_t cd;
  if (first) {
    cd = iconv_open(ICONV_UCS4, ICONV_UTF16);
    assert(cd != (iconv_t) -1);
    first = false;
  }

  Char32* res;
  length = convertString(cd, sourceBuffer, ptr->size * sizeof(Char16), reinterpret_cast<void**>(&res));
  length /= sizeof(Char32);

#if !defined(WORDS_BIGENDIAN) && defined(UTF16_BIGENDIAN)
  delete [] sourceBuffer;
#endif

#if !defined(WORDS_BIGENDIAN) && defined(UCS4_BIGENDIAN)
  for (unsigned i = 0; i < length; i++)
    res[i] = GUINT32_SWAP_LE_BE(res[i]);
#endif

  return res;
}

void
DOMString::fromC(const char* s)
{
  if (s == NULL) throw NullString();
  fromC(s, strlen(s));
}

void
DOMString::fromC(const char* s, unsigned length)
{
  if (ptr != NULL) ptr->unref();

  if (s == NULL) ptr = NULL;
  else if (length == 0) ptr = new DOMStringBuffer(0);
  else {
    ptr = new DOMStringBuffer(length);
    for (unsigned i = 0; i < length; i++)
      ptr->buffer[i] = s[i];
  }
}

void
DOMString::fromUTF8(const Char8* buffer, unsigned length)
{
  if (ptr != NULL) ptr->unref();

  if (buffer == NULL) ptr = NULL;
  else if (length == 0) ptr = new DOMStringBuffer(0);
  else {
    static bool first = true;
    static iconv_t cd;
    if (first) {
      cd = iconv_open(ICONV_UTF16, ICONV_UTF8);
      assert(cd != (iconv_t) -1);
      first = false;
    }
    
    Char16* res;
    unsigned size = convertString(cd, buffer, length * sizeof(Char8), reinterpret_cast<void**>(&res));
    assert(res != NULL);
#if !defined(WORDS_BIGENDIAN) && defined(UTF16_BIGENDIAN)
    ptr = new DOMStringBuffer(size / sizeof(Char16));
    for (unsigned i = 0; i < ptr->size; i++)
      ptr->buffer[i] = GUINT16_SWAP_LE_BE(res[i]);
#else
    ptr = new DOMStringBuffer(res, size / sizeof(Char16));
#endif
    delete [] res;
  }
}

void
DOMString::fromUTF16(const Char16* buffer, unsigned length)
{
  if (ptr != NULL) ptr->unref();

  if (buffer == NULL) ptr = NULL;
  else if (length == 0) ptr = new DOMStringBuffer(0);
  else ptr = new DOMStringBuffer(buffer, length);
}

void
DOMString::fromUnicode(const Char32* buffer, unsigned length)
{
  if (ptr != NULL) ptr->unref();
  
  if (buffer == NULL) ptr = NULL;
  else if (length == 0) ptr = new DOMStringBuffer(0);
  else {
    static bool first = true;
    static iconv_t cd;
    if (first) {
      cd = iconv_open(ICONV_UTF16, ICONV_UCS4);
      assert(cd != (iconv_t) -1);
      first = false;
    }
    
    Char16* res;
#if !defined(WORDS_BIGENDIAN) && defined(UCS4_BIGENDIAN)
    Char32* beBuffer = new Char32[length];
    for (unsigned i = 0; i < length; i++)
      beBuffer[i] = GUINT32_SWAP_LE_BE(buffer[i]);
    unsigned size = convertString(cd, beBuffer, length * sizeof(Char32), reinterpret_cast<void**>(&res));
    delete [] beBuffer;
#else
    unsigned size = convertString(cd, buffer, length * sizeof(Char32), reinterpret_cast<void**>(&res));
#endif
    assert(res != NULL);
#if !defined(WORDS_BIGENDIAN) && defined(UTF16_BIGENDIAN)
    ptr = new DOMStringBuffer(size / sizeof(Char16));
    for (unsigned i = 0; i < ptr->size; i++)
      ptr->buffer[i] = GUINT16_SWAP_LE_BE(res[i]);
#else
    ptr = new DOMStringBuffer(res, size / sizeof(Char16));
#endif
    delete [] res;
  }
}

DOMString
DOMString::clone() const
{
  if (ptr == NULL) return *this;
  else {
    DOMString res;
    res.fromUTF16(ptr->buffer, ptr->size);
    return res;
  }
}

DOMString
DOMString::substr(unsigned offset, unsigned length) const
{
  if (ptr == 0) throw NullString();
  if (offset + length > ptr->size) throw IndexOutOfRange();
  DOMString res;
  res.fromUTF16(ptr->buffer + offset, length);
  return res;
}

}

