//
// One header file iconv wrapper for C++11 2022-09-29.12 GPL
// https://github.com/trueroad/iconv_wrapper
//
// Copyright (C) 2016, 2019, 2022 Masamichi Hosoda. All rights reserved.
//
// One header file iconv wrapper for C++11 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 3 of the License, or
// (at your option) any later version.
//
// One header file iconv wrapper for C++11 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 One header file Commandline Parse for C++11.
// If not, see <http://www.gnu.org/licenses/>.
//

#ifndef INCLUDE_GUARD_ICONV_WRAPPER_H
#define INCLUDE_GUARD_ICONV_WRAPPER_H

#include <iconv.h>
#include <string>
#include <system_error>

namespace iconv_wrapper
{
 class iconv
 {
 public:
   // Open
   void open (const std::string &fromcode, const std::string &tocode);

   // Close
   void close () noexcept;

   // Convert
   std::string convert (const std::string &in);

   // Convert (with getting the incomplete input / output
   // when an exception occurs)
   std::string &convert (const std::string &in,
                         std::string::size_type *pinpos,
                         std::string *pout);

   // Get initial sequence
   std::string get_initial_sequence (void);

   // Get initial sequence
   std::string &get_initial_sequence (std::string *pout);

   // Reset conversion state
   void reset (void) const noexcept;

   // Constructor / destructor
   iconv () = default;

   iconv (const std::string &fromcode, const std::string &tocode)
   {
     open (fromcode, tocode);
   }

   ~iconv () noexcept
   {
     close ();
   }

   // Enable move
   iconv (iconv &&) = default;
   iconv& operator = (iconv &&) = default;

 private:
   // Internal class
   // There is two different implements for the second argument of iconv ().
   //   `const char**' and `char **'
   // This class is for automatic type cast switching.
   class iconv_const_cast
   {
   public:
     iconv_const_cast (const char **in) noexcept:
       t (in)
     {
     }
     operator char** () const noexcept
     {
       return const_cast<char**>(t);
     }
     operator const char ** () const noexcept
     {
       return t;
     }
   private:
     const char ** t;
   };

   // Internal function
   void do_iconv (std::string *pout, const char *inbuf, size_t *pinleft,
                  std::string::size_type *pinpos = nullptr);

   // Disable copy
   // Since iconv_t cannot duplicate.
   iconv (iconv const &) = delete;
   iconv& operator = (iconv const &) = delete;

   // Const
   // Here is C cast instead of C++ cast.
   // C++ reinterpret_cast (pointer) and static_cast (integer) cannot be used.
   // Since it depends on the implementation of iconv_t.
   const iconv_t invalid_cd = (iconv_t)-1;

   // Conversion descriptor
   iconv_t convdesc = invalid_cd;
 };

 inline void iconv::open (const std::string &fromcode,
                          const std::string &tocode)
 {
   if (convdesc == invalid_cd)
     {
       close ();
     }
   convdesc = iconv_open (tocode.c_str (), fromcode.c_str ());
   if (convdesc == invalid_cd)
     {
       throw std::system_error (errno, std::system_category ());
     }
 }

 inline void iconv::close () noexcept
 {
   if (convdesc != invalid_cd)
     {
       iconv_close (convdesc);
       convdesc = invalid_cd;
     }
 }

 inline std::string iconv::convert (const std::string &in)
 {
   std::string out (in.size (), '\0');
   convert (in, nullptr, &out);
   return out;
 }

 inline std::string &iconv::convert (const std::string &in,
                                     std::string::size_type *pinpos,
                                     std::string *pout)
 {
   size_t inleft {in.size ()};
   if (inleft)
     do_iconv (pout, &in.at (0), &inleft, pinpos);
   else
     pout->clear();
   return *pout;
 }

 inline std::string iconv::get_initial_sequence (void)
 {
   std::string out (1, '\0');
   get_initial_sequence (&out);
   return out;
 }

 inline std::string &iconv::get_initial_sequence (std::string *pout)
 {
   do_iconv (pout, nullptr, nullptr);
   return *pout;
 }

 inline void iconv::reset (void) const noexcept
 {
   ::iconv (convdesc, nullptr, nullptr, nullptr, nullptr);
 }

 inline void iconv::do_iconv (std::string *pout,
                              const char *inbuf, size_t *pinleft,
                              std::string::size_type *pinpos)
 {
   if (pout->empty ())
     {
       pout->resize (1);
     }
   const char *inbuf_tmp {inbuf};
   char *outbuf {&pout->at (0)};
   size_t outleft {pout->size ()};
   size_t s;

   while ((s = ::iconv (convdesc,
                        iconv_const_cast(&inbuf_tmp), pinleft,
                        &outbuf, &outleft)) == static_cast<size_t>(-1))
     {
       if (errno != E2BIG)
         {
           if (pinpos && inbuf)
             {
               *pinpos = (inbuf_tmp - inbuf);
             }
           pout->resize (outbuf - &pout->at (0));
           throw std::system_error (errno, std::system_category ());
         }
       std::string::size_type pos = (outbuf - &pout->at (0));
       pout->resize (pout->size () * 2);
       outbuf = &pout->at (pos);
       outleft = pout->size () - pos;
     }
   pout->resize (outbuf - &pout->at (0));
 }
}

#endif  // INCLUDE_GUARD_ICONV_WRAPPER_H