// tinygettext - A gettext replacement that works directly on .po files
// Copyright (c) 2009 Ingo Ruhnke <grumbel@gmail.com>
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//    claim that you wrote the original software. If you use this software
//    in a product, an acknowledgement in the product documentation would be
//    appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//    misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

#include "precompiled.h"

#include <ctype.h>
#include <assert.h>
#include <sstream>
#include <errno.h>
#include <stdexcept>
#include <string.h>
#include <stdlib.h>

#include "tinygettext/iconv.hpp"
#include "tinygettext/log_stream.hpp"

namespace tinygettext {

IConv::IConv()
  : to_charset(),
    from_charset(),
    cd(nullptr)
{}

IConv::IConv(const std::string& from_charset_, const std::string& to_charset_)
  : to_charset(),
    from_charset(),
    cd(nullptr)
{
  set_charsets(from_charset_, to_charset_);
}

IConv::~IConv()
{
  if (cd)
    iconv_close(cd);
}

void
IConv::set_charsets(const std::string& from_charset_, const std::string& to_charset_)
{
  if (cd)
    iconv_close(cd);

  from_charset = from_charset_;
  to_charset   = to_charset_;

  for(std::string::iterator i = to_charset.begin(); i != to_charset.end(); ++i)
    *i = static_cast<char>(toupper(*i));

  for(std::string::iterator i = from_charset.begin(); i != from_charset.end(); ++i)
    *i = static_cast<char>(toupper(*i));

  if (to_charset == from_charset)
  {
    cd = nullptr;
  }
  else
  {
    cd = iconv_open(to_charset.c_str(), from_charset.c_str());
    if (cd == reinterpret_cast<iconv_t>(-1))
    {
      if(errno == EINVAL)
      {
        std::ostringstream str;
        str << "IConv construction failed: conversion from '" << from_charset
            << "' to '" << to_charset << "' not available";
        throw std::runtime_error(str.str());
      }
      else
      {
        std::ostringstream str;
        str << "IConv: construction failed: " << strerror(errno);
        throw std::runtime_error(str.str());
      }
    }
  }
}

/// Convert a string from encoding to another.
std::string
IConv::convert(const std::string& text)
{
  if (!cd)
  {
    return text;
  }
  else
  {
    size_t inbytesleft  = text.size();
    size_t outbytesleft = 4*inbytesleft; // Worst case scenario: ASCII -> UTF-32?

    // We try to avoid to much copying around, so we write directly into
    // a std::string
    const char* inbuf = &text[0];
    std::string result(outbytesleft, 'X');
    char* outbuf = &result[0];

    // Try to convert the text.
    size_t ret = iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
    if (ret == static_cast<size_t>(-1))
    {
      if (errno == EILSEQ || errno == EINVAL)
      { // invalid multibyte sequence
        iconv(cd, nullptr, nullptr, nullptr, nullptr); // reset state

        // FIXME: Could try to skip the invalid byte and continue
        log_error << "error: tinygettext:iconv: invalid multibyte sequence in:  \"" << text << "\"" << std::endl;
      }
      else if (errno == E2BIG)
      { // output buffer to small
        assert(false && "tinygettext/iconv.cpp: E2BIG: This should never be reached");
      }
      else if (errno == EBADF)
      {
        assert(false && "tinygettext/iconv.cpp: EBADF: This should never be reached");
      }
      else
      {
        assert(false && "tinygettext/iconv.cpp: <unknown>: This should never be reached");
      }
    }

    result.resize(4*text.size() - outbytesleft);

    return result;
  }
}

} // namespace tinygettext

/* EOF */
