/**
 *  @file   CharsetConv.h
 *  @brief  iconv または mlang を用いた文字コード変換.
 *  @author tenk* (Masashi Kitamura)
 *  @note
 *  -   win環境では、予め CoInitialize() されている必要がある.
 *  -   実質 char系エンコーディングのみの対応で、wchar_t(UTF-16)系は未対応.
 *  -   class仕様を mlang,iconvで合わせて、だいたいの使い方を似せているだけで、
 *      細かい動作は違うし、エンコードの指定は、各々の事情の名前を使う必要が
 *      あるため、完全な置き換え等は望まないこと.
 *  -   Public Domain Software
 */

#ifndef CHARSETCONV_H_INCLUDED
#define CHARSETCONV_H_INCLUDED


/* 基本的に、このようなクラス.
    class CharsetConv {
    public:
        CharsetConv();
        ~CharsetConv();
        CharsetConv(const TCHAR* dstEnc, const TCHAR* srcEnc);  // open有コンストラクタ.
        int open(const TCHAR* dstEnc, const TCHAR* srcEnc);     // 開始.
        void close();                                           // 終わり.
        size_t conv(char pDst[], size_t dstBytes, const char* pSrc, size_t srcBytes);               // 指定サイズの変換.
        size_t strConv(char pDst[], size_t dstSize, const char* pSrc, size_t srcSize=size_t(-1));   // \0文字列の変換.
        void reset();                                           // 状態のリセット( iconv 時のみ )
     };
*/

#if defined _MSC_VER // =======================================================
// mlang がんばれば他のコンパイラも可能のようだけど、実質 vc のみ...

#include <windows.h>
#include <stddef.h>
#include <assert.h>
#include <tchar.h>
#include <comdef.h>
#include <mlang.h>


/// mlang を用いた文字コード変換.
class CharsetConv {
public:
    CharsetConv() : mlang_(0), buf2_(0) { init(); }

    ~CharsetConv() {
        close();
        if (mlang_)
            mlang_->Release();
    }

    CharsetConv(const TCHAR* dstEnc, const TCHAR* srcEnc) {
        init();
        open(dstEnc, srcEnc);
    }

    int open(const TCHAR* dstEnc, const TCHAR* srcEnc) {
        assert(!!mlang_);
        if (mlang_) {
            MIMECSETINFO    mi;
            assert(dstEnc && srcEnc);
            srcEnc_ = _tcstol(srcEnc, 0, 0);
            if (srcEnc_ == 0) {
                mlang_->GetCharsetInfo(_bstr_t(srcEnc), &mi );
                srcEnc_ = mi.uiInternetEncoding;
            }
            dstEnc_ = _tcstol(dstEnc, 0, 0);
            if (dstEnc_ == 0) {
                mlang_->GetCharsetInfo(_bstr_t(dstEnc), &mi );
                dstEnc_ = mi.uiInternetEncoding;
            }
            {
                utf8EucJpFlag_ = 0;
                if (srcEnc_ == 51932 && dstEnc_ == 65001) { // euc-jp => utf-8
                    utf8EucJpFlag_ |= 1;
                } else if (srcEnc_ == 65001 && dstEnc_ == 51932) {  // utf-8 => euc-jp
                    utf8EucJpFlag_ |= 2;
                }
                if (utf8EucJpFlag_) {
                    buf2_         = (char*)::operator new( 0x1000 );
                    buf2size_     = 0x1000;
                }
            }
            return 0;
        }
        return -1;
    }

    void close() {
        if (buf2_)
            ::operator delete(buf2_);
    }

    /** 指定サイズの文字列の変換. \0を終端扱いにしないので注意.
     *  ※ 現状 UTF-16系はできそうで出来ない模様orz
     *  @param pDst      変換後の文字列を収めるバッファ
     *  @param dstBytes  pDstの領域バイト数.
     *  @param pSrc      変換する文字列
     *  @param srcBytes  pSrcのバイト数.
     *  @return  -1:エラー  以外=変換したバイト数.
     */
    size_t conv(char pDst[], size_t dstBytes, const char* pSrc, size_t srcBytes) {
        assert(!!mlang_);
        assert(pDst && pSrc && dstBytes > 0 && srcBytes > 0 && srcBytes < size_t(-1));
        DWORD    dwMode = 0;
        UINT     srcSz  = UINT(srcBytes);
        UINT     dstSz  = UINT(dstBytes);
        unsigned srcEnc = srcEnc_;
        if (utf8EucJpFlag_) {   // EUC-Jp <=> UTF-8 直接変換ができないようなので、SJIS経由にする.
            if (convUtf8EucJp(pSrc, srcSz, srcBytes))
                return size_t(-1);
            srcEnc = 932;
        }
        HRESULT rc     = mlang_->ConvertString(&dwMode, srcEnc, dstEnc_, (LPBYTE)pSrc , &srcSz, (LPBYTE)pDst , &dstSz );
        return (rc != S_FALSE) ? dstSz : size_t(-1);
    }

    /** char系文字列のみ対応で、\0終端文字列の変換.
     *  @return -1:エラー  以外:変換したバイト数(\0含まず)
     */
    size_t strConv(char pDst[], size_t dstSize, const char* pSrc, size_t srcSize=size_t(-1)) {
        size_t  srcLen = strlen(pSrc);
        UINT    srcSz  = srcSize < srcLen ? UINT(srcSize) : srcLen;
        size_t  dstSz  = conv(pDst, dstSize, pSrc, srcSz);
        if (dstSz != size_t(-1)) {
            if (dstSz >= dstSize)
                dstSz = dstSize - 1;
            pDst[dstSz] = 0;
        }
        return dstSz;
    }

    void reset() { /* mlang_->ConvertStringReset(); */ }

private:
    CharsetConv(const CharsetConv&);
    void operator=(const CharsetConv&);

    void init() {
        buf2_  = 0;
        mlang_ = 0;
        //if (FAILED(::CoCreateInstance(CLSID_CMultiLanguage, NULL, CLSCTX_ALL, IID_IMultiLanguage3, (LPVOID*)&mlang_)))
        {
            if (FAILED(::CoCreateInstance(CLSID_CMultiLanguage, NULL, CLSCTX_ALL, IID_IMultiLanguage2, (LPVOID*)&mlang_)))
                mlang_ = 0;
        }
    }

    /// EUC-Jp <=> UTF-8 直接変換ができないようなので、SJIS経由にする.
    int convUtf8EucJp(const char*& pSrc, UINT& srcSz, size_t srcBytes) {
        if (srcBytes > buf2size_) {
            ::operator delete(buf2_);
            buf2size_ = srcBytes;
            buf2_     = (char*)::operator new(srcBytes);
            if (buf2_ == 0)
                return size_t(-1);
        }
        UINT    buf2size = buf2size_;
        DWORD   dwMode = 0;
        HRESULT rc = mlang_->ConvertString(&dwMode, srcEnc_, 932, (LPBYTE)pSrc , &srcSz, (LPBYTE)buf2_, &buf2size );
        if (rc != S_OK)
            return size_t(-1);
        pSrc    = buf2_;
        srcSz   = buf2size;
        return 0;
    }

private:
    IMultiLanguage3*    mlang_;
    unsigned            srcEnc_;
    unsigned            dstEnc_;
    unsigned            utf8EucJpFlag_;
    unsigned            buf2size_;
    char*               buf2_;
};



/// 現在の言語のエンコード名の取得(いろいろ不十分だが手抜きで)
class CharsetConv_Helper {
public:
    static const TCHAR* getCurrentCharset() {
        static TCHAR    buf[64];
        _ultot(GetConsoleOutputCP(), buf, 10);
        return buf;
    }
};


#else // linux / unix       // ===============================================

#include <iconv.h>
#include <stddef.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>


/// iconv を用いた文字コード変換.
class CharsetConv {
public:
    CharsetConv() : icd_(0) {}
    ~CharsetConv() { close(); }

    CharsetConv(const char* dstEnc, const char* srcEnc) { open(dstEnc, srcEnc); }

    bool open(const char* dstEnc, const char* srcEnc) {
        assert(dstEnc && srcEnc);
      #ifdef __GNUC__   // GNU libiconv が使われている場合.
        char src[128], dst[128];
        snprintf(src, 128, "%s//TRANSLIT", srcEnc);
        snprintf(dst, 128, "%s//TRANSLIT", dstEnc);
        icd_ = iconv_open(dst   , src   );
      #else
        icd_ = iconv_open(dstEnc, srcEnc);
      #endif
        return 0;
    }

    void close() {
        iconv_close(icd_);
    }

    /** 指定サイズの文字列の変換. \0を終端扱いにしないので注意.
     *  ※ win側にあわせて、詳細なエラー情報はあきらめる...
     *  @param pDst      変換後の文字列を収めるバッファ
     *  @param dstBytes  pDstの領域バイト数.
     *  @param pSrc      変換する文字列
     *  @param srcBytes  pSrcのバイト数.
     *  @return  -1:エラー  以外=変換したバイト数.
     */
    size_t conv(char pDst[], size_t dstSize, const char* pSrc, size_t srcSize) {
        assert( icd_ != 0);
        assert(pDst && pSrc && dstSize > 0 && srcSize > 0);
        char*       s = (char*)pSrc;
        char*       d = pDst;
        size_t  rc= iconv(icd_, &s, &srcSize, &d, &dstSize);
        if (rc == size_t(-1))
            return size_t(-1);
        return d - pDst;
    }

    /** char系文字列のみ対応で、\0終端文字列の変換.
     *  @return -1:エラー  以外:変換したバイト数(\0含まず)
     */
    size_t strConv(char pDst[], size_t dstSize, const char* pSrc, size_t srcSize=size_t(-1)) {
        assert( icd_ != 0);
        assert(pDst && pSrc && dstSize > 0 && srcSize > 0);
        size_t  l     = strlen(pSrc);
        size_t  srcSz = srcSize < l ? srcSize : l;
        size_t  dstSz = dstSize;
        char*   s     = (char*)pSrc;
        char*   d     = pDst;
        size_t  rc    = iconv(icd_, &s, &srcSz, &d, &dstSz);
        dstSz         = d - pDst;
        if (dstSz >= dstSize)
            dstSz     = dstSize - 1;
        pDst[dstSz]   = 0;
        if (rc == size_t(-1))
            dstSz     = size_t(-1);
        return dstSz;
    }

    void reset() {
        iconv(icd_, NULL, NULL, NULL, NULL);
    }

private:
    CharsetConv(const CharsetConv&);
    void operator=(const CharsetConv&);

private:
    iconv_t  icd_;
};



/// 現在の言語のエンコード名の取得(いろいろ不十分だが手抜きで)
class CharsetConv_Helper {
public:
    static const char* getCurrentCharset() {
      #if 1 //ndef _WIN32
        const char* env = getenv("LANG");
        if (env) {
            const char* p = strrchr(env, '.');
            if (p)
                return p+1;
        }
      #endif
        return "";
    }
};


#endif  // ====================================================================


#endif // CHARSETCONV_H_INCLUDED