/*---------------------------------------------------------------------------*
 |          Copyright (c) 2005-2010 PDFlib GmbH. All rights reserved.        |
 +---------------------------------------------------------------------------+
 |    This software may not be copied or distributed except as expressly     |
 |    authorized by PDFlib GmbH's general license agreement or a custom      |
 |    license agreement signed by PDFlib GmbH.                               |
 |    For more information about licensing please refer to www.pdflib.com.   |
 *---------------------------------------------------------------------------*/

// $Id: tet.hpp,v 1.25 2010/02/26 11:12:48 stm Exp $
//
// C++ wrapper for TET
//
//

#ifndef TETLIB_HPP
#define TETLIB_HPP

#include <string>
#include <sstream>
#include <iostream>
#include <stdexcept>

// We use TET as a C++ class name, therefore hide the actual C struct
// name for TET usage with C++.
typedef struct TET_s TET_cpp;
#define TET TET_cpp
#include "tetlib.h"
#undef TET

/*
 * TETCPP_USE_PDFLIB_NAMESPACE
 *
 * If set to a non-zero value, wrap all declarations in the namespace "pdflib".
 * Otherwise no namespace declaration is created. For backward compatibility
 * with the TET 3 C++ wrapper, define this as 0.
 */
#ifndef TETCPP_USE_PDFLIB_NAMESPACE
#define TETCPP_USE_PDFLIB_NAMESPACE 1
#endif

/*
 * TETCPP_PDFLIB_WSTRING
 *
 * The tet.hpp header declares one instantiation of the basic_TET
 * template as type "TET". By default this TET type is std::wstring-based.
 * For backward compatibility with the TET 3 std::string-based C++ wrapper,
 * define this as 0.
 */
#ifndef TETCPP_TET_WSTRING
#define TETCPP_TET_WSTRING 1
#endif

#if TETCPP_USE_PDFLIB_NAMESPACE
namespace pdflib {
#endif

#if defined(_MSC_VER) && defined(_MANAGED)
/*
 * Dummy declaration to prevent linker warning for .NET. If it doesn't see
 * a declaration of the structure it will complain. The structure is never
 * used in the .NET wrapper, so it is safe to declare it as an empty structure
 * here.
 */
struct TET_s {};

#pragma unmanaged
#endif

// The C++ class wrapper for TET

#if defined(_MSC_VER)
// Suppress Visual C++ warnings about ignored exception specifications.
#pragma warning(disable: 4290)
#endif

template<class pstring, class conv> class basic_TET;

/**
 * The "do-nothing" converter that has the effect that the basic_TET class
 * behaves in the same way as the TET 3.0 wrapper.
 */
template<class pstring>
class TETNoOpConverter
{
public:
    static bool do_conversion()
    {
        return false;
    }

    static void convert_to_pdf_bytes(
        const basic_TET<pstring, TETNoOpConverter<pstring> >& p,
        const pstring& in, std::string& out)
    {
        throw std::logic_error(
                "TETNoOpConverter::convert_to_pdf_bytes: internal error: "
                "converter called although do_conversion() returns false");
    }

    static void convert_to_pdf_utf8(
            const basic_TET<pstring, TETNoOpConverter<pstring> >& p,
            const pstring&, std::string&)
    {
        throw std::logic_error(
                "TETNoOpConverter::convert_to_pdf_utf8: internal error: "
                "converter called although do_conversion() returns false");
    }

    static void convert_to_pdf_utf16(
            const basic_TET<pstring, TETNoOpConverter<pstring> >& p,
            const pstring&, std::string&)
    {
        throw std::logic_error(
                "TETNoOpConverter::convert_to_pdf_utf16: internal error: "
                "converter called although do_conversion() returns false");
    }

    static void convert_to_pstring(
            const basic_TET<pstring, TETNoOpConverter<pstring> >& p,
            const char *, pstring&)
    {
        throw std::logic_error(
                "TETNoOpConverter::convert_to_pstring: internal error: "
                "converter called although do_conversion() returns false");
    }
};

#define TETCPP_TRY      TET_TRY(tet)
#define TETCPP_CATCH  \
TET_CATCH(tet) {\
    const pstring message(reinterpret_cast<const typename pstring::value_type *> \
        (TET_get_errmsg(tet))); \
    const pstring apiname(reinterpret_cast<const typename pstring::value_type *> \
        (TET_get_apiname(tet))); \
    throw Exception(message, TET_get_errnum(tet), apiname, \
                        TET_get_opaque(tet)); \
}

template<class pstring, class conv>
class basic_TET
{
    friend class TETNoOpConverter<pstring>;

public:
    class Exception
    {
    public:
	Exception(const pstring& errmsg, int errnum, const pstring& apiname,
                    void *opaque) :
            m_errmsg(errmsg),
              m_errnum(errnum),
              m_apiname(apiname),
              m_opaque(opaque)
        {
        }
        pstring get_errmsg() const { return m_errmsg; }
        int get_errnum() const { return m_errnum; }
        pstring get_apiname() const { return m_apiname; }
        const void *get_opaque() const { return m_opaque; }
    private:
	pstring m_errmsg;
	int m_errnum;
	pstring m_apiname;
	void * m_opaque;
    }; // Exception
    
    basic_TET(tet_error_fp errorhandler = NULL, void *opaque = NULL) :
        tet(TET_new2(errorhandler, opaque))
    {
        if (!tet)
        {
            throw std::bad_alloc();
        }

        TETCPP_TRY
        {
            TET_set_option(tet, "objorient");
            if (conv::do_conversion())
            {
                TET_set_option(tet, "binding={C++ conv}");
            }
            else
            {
                switch (sizeof(typename pstring::value_type))
                {
                case sizeof(char):
                    TET_set_option(tet,
                            "binding={C++ legacy} outputformat=utf16 "
                            "apitextformat=utf8");
                    break;

                case utf16_wchar_t_size:
                    TET_set_option(tet,
                            "binding={C++} unicaplang=true outputformat=utf16 "
                            "apitextformat=utf16");
                    break;

                case utf32_wchar_t_size:
                    TET_set_option(tet,
                        "binding={C++} unicaplang=true outputformat=utf32 "
                        "apitextformat=utf32");
                    break;

                default:
                    bad_wchar_size("basic_TET<pstring, conv>::basic_TET");
                }
            }
        }
        TETCPP_CATCH;
    }

    ~basic_TET()
    {
        TET_delete(tet);
    }

    void close_document(int doc)
    {
        TETCPP_TRY
            TET_close_document(tet, doc);
        TETCPP_CATCH;
    }

    void close_page(int page)
    {
        TETCPP_TRY
            TET_close_page(tet, page);
        TETCPP_CATCH;
    }

    void create_pvf(const pstring& filename, const void *data, size_t size,
            const pstring& optlist)
    {
        std::string filename_param;
        const char *p_filename_param;
        int len_filename;
        param_to_0utf16(filename, filename_param, p_filename_param,
                len_filename);

        std::string optlist_param;
        const char *p_optlist_param;
        param_to_utf8(optlist, optlist_param, p_optlist_param);

        TETCPP_TRY
            TET_create_pvf(tet, p_filename_param, len_filename, data, size,
                    p_optlist_param);
        TETCPP_CATCH;
    }

    void delete_pvf(const pstring& filename)
    {
        std::string filename_param;
        const char *p_filename_param;
        int len_filename;
        param_to_0utf16(filename, filename_param, p_filename_param,
                len_filename);

        TETCPP_TRY
            TET_delete_pvf(tet, p_filename_param, len_filename);
        TETCPP_CATCH;
    }

    pstring get_apiname() const
    {
        const char *retval = NULL;
        pstring pstring_retval;

        TETCPP_TRY
            retval = TET_get_apiname(tet);
        TETCPP_CATCH;

        apistring_to_pstring(retval, pstring_retval);

        return pstring_retval;
    }

    pstring get_errmsg() const
    {
        const char *retval = NULL;
        pstring pstring_retval;

        TETCPP_TRY
            retval = TET_get_errmsg(tet);
        TETCPP_CATCH;

        apistring_to_pstring(retval, pstring_retval);

        return pstring_retval;
    }

    int get_errnum() const
    {
        int retval = 0;

        TETCPP_TRY
            retval = TET_get_errnum(tet);
        TETCPP_CATCH;

        return retval;
    }

    const TET_char_info * get_char_info(int page)
    {
        const TET_char_info *retval = 0;

        TETCPP_TRY
            retval = TET_get_char_info(tet, page);
        TETCPP_CATCH;

        return retval;
    }

    void * get_opaque()
    {
        void *retval = NULL;

        TETCPP_TRY
            retval = TET_get_opaque(tet);
        TETCPP_CATCH;

        return retval;
    }

    const char *get_image_data(int doc, size_t *length, int imageid,
	const pstring &optlist)
    {
        std::string optlist_param;
        const char *p_optlist_param;
        param_to_utf8(optlist, optlist_param, p_optlist_param);

        const char * retval = NULL;

        TETCPP_TRY
            retval = TET_get_image_data(tet, doc, length, imageid, p_optlist_param);
        TETCPP_CATCH;

        return retval;
    }

    const char *get_xml_data(int doc, size_t *length, const pstring &optlist)
    {
        std::string optlist_param;
        const char *p_optlist_param;
        param_to_utf8(optlist, optlist_param, p_optlist_param);

        const char *retval = NULL;

        TETCPP_TRY
            retval = TET_get_xml_data(tet, doc, length, p_optlist_param);
        TETCPP_CATCH;

        return retval;
    }

    const TET_image_info * get_image_info(int page)
    {
        const TET_image_info *retval = 0;

        TETCPP_TRY
            retval = TET_get_image_info(tet, page);
        TETCPP_CATCH;

        return retval;
    }

    /*
     * Return a text fragment. This function is special because in the legacy
     * case it must return an UTF-16 encoded std::string, whereas in the
     * wstring case it must return UTF-16 or UTF-32.
     */
    pstring get_text(int page)
    {
        const char *cretval = NULL;
        int len;

        TETCPP_TRY
            cretval = TET_get_text(tet, page, &len);
        TETCPP_CATCH;

        pstring pstring_retval;

        if (cretval)
        {
            switch (sizeof(typename pstring::value_type))
            {
            case sizeof(char):
                /*
                 * Legacy case: Put UTF-16 string into std::string.
                 */
                pstring_retval.assign(reinterpret_cast
                                        <const typename pstring::value_type *>
                                            (cretval), len * 2);
                break;

            case utf16_wchar_t_size:
            case utf32_wchar_t_size:
                outputstring_to_pstring(cretval, pstring_retval, len);
                break;

            default:
                bad_wchar_size("basic_TET<pstring, conv>::param_to_utf8");
            }
        }

        return pstring_retval;
    }

    int open_document(const pstring &filename, const pstring &optlist)
    {
        std::string filename_param;
        const char *p_filename_param;
        int len_filename;
        param_to_0utf16(filename, filename_param, p_filename_param,
                len_filename);

        std::string optlist_param;
        const char *p_optlist_param;
        param_to_utf8(optlist, optlist_param, p_optlist_param);

        int retval = 0;

        TETCPP_TRY
            retval = TET_open_document(tet, p_filename_param, len_filename, p_optlist_param);
        TETCPP_CATCH;

        return retval;
    }

    int open_document_callback(void *opaque, size_t filesize,
	size_t (*readproc)(void *opaque, void *buffer, size_t size),
	int (*seekproc)(void *opaque, long offset), const pstring& optlist)
    {
        std::string optlist_param;
        const char *p_optlist_param;
        param_to_utf8(optlist, optlist_param, p_optlist_param);

        int retval = 0;

        TETCPP_TRY
            retval = TET_open_document_callback(tet, opaque, filesize,
                    readproc, seekproc, p_optlist_param);
        TETCPP_CATCH;

        return retval;
    }

    int open_document_mem(const void *data, size_t length, const pstring &optlist)
    {
        std::string optlist_param;
        const char *p_optlist_param;
        param_to_utf8(optlist, optlist_param, p_optlist_param);

        int retval = 0;

        TETCPP_TRY
            retval = TET_open_document_mem(tet, data, length, p_optlist_param);
        TETCPP_CATCH;

        return retval;
    }

    int open_page(int doc, int pageno, const pstring &optlist)
    {
        std::string optlist_param;
        const char *p_optlist_param;
        param_to_utf8(optlist, optlist_param, p_optlist_param);

        int retval = 0;

        TETCPP_TRY
            retval = TET_open_page(tet, doc, pageno, p_optlist_param);
        TETCPP_CATCH;

        return retval;
    }

    double pcos_get_number(int doc, const pstring& path)
    {
        std::string path_param;
        const char *p_path_param;
        param_to_utf8(path, path_param, p_path_param);

        double retval = 0;

        TETCPP_TRY
            retval = TET_pcos_get_number(tet, doc, "%s", p_path_param);
        TETCPP_CATCH;

        return retval;
    }

    pstring pcos_get_string(int doc, const pstring& path)
    {
        std::string path_param;
        const char *p_path_param;
        param_to_utf8(path, path_param, p_path_param);

        const char *cretval = NULL;
        pstring pstring_retval;

        TETCPP_TRY
            cretval = TET_pcos_get_string(tet, doc, "%s", p_path_param);
        TETCPP_CATCH;

        apistring_to_pstring(cretval, pstring_retval);

        return pstring_retval;
    }

    const unsigned char * pcos_get_stream(int doc, int *length,
            const pstring& optlist, const pstring& path)
    {
        std::string optlist_param;
        const char *p_optlist_param;
        param_to_utf8(optlist, optlist_param, p_optlist_param);
        std::string path_param;
        const char *p_path_param;
        param_to_utf8(path, path_param, p_path_param);

        const unsigned char *retval = NULL;

        TETCPP_TRY
            retval = TET_pcos_get_stream(tet, doc, length, p_optlist_param,
                                                        "%s", p_path_param);
        TETCPP_CATCH;

        return retval;
    }

    int process_page(int doc, int pageno, const pstring& optlist)
    {
        std::string optlist_param;
        const char *p_optlist_param;
        param_to_utf8(optlist, optlist_param, p_optlist_param);

        int retval = 0;

        TETCPP_TRY
            retval = TET_process_page(tet, doc, pageno, p_optlist_param);
        TETCPP_CATCH;
        return retval;
    }

    void set_option(const pstring& optlist)
    {
        std::string optlist_param;
        const char *p_optlist_param;
        param_to_utf8(optlist, optlist_param, p_optlist_param);

        TETCPP_TRY
            TET_set_option(tet, p_optlist_param);
        TETCPP_CATCH;
    }

    std::string utf16_to_utf8(const std::string utf16string) const
    {
        const char *retval = NULL;

        TETCPP_TRY
            retval = TET_utf16_to_utf8(tet, utf16string.c_str(),
                    static_cast<int>(utf16string.length()), NULL);
        TETCPP_CATCH;

        if (retval)
            return retval;
        else
            return "";
    }

    std::string utf8_to_utf16(const std::string& utf8string,
                const std::string& ordering) const
    {
        int size;
        const char *buf;
        std::string retval;

        TETCPP_TRY
            buf = TET_utf8_to_utf16(tet, utf8string.c_str(), ordering.c_str(),
                    &size);
            if (buf)
                retval.assign(buf, static_cast<size_t>(size));
        TETCPP_CATCH;

        return retval;
    }

    std::string utf32_to_utf16(const std::string& utf32string,
            const std::string& ordering) const
    {
        int size;
        const char *buf;
        std::string retval;

        TETCPP_TRY
            buf = TET_utf32_to_utf16(tet, utf32string.data(),
                static_cast<int>(utf32string.length()), ordering.c_str(), &size);
            if (buf)
                retval.assign(buf, static_cast<size_t>(size));
        TETCPP_CATCH;

        return retval;
    }

    std::string utf16_to_utf32(const std::string& utf16string,
            const std::string& ordering)
    {
        int size;
        const char *buf;
        std::string retval;

        TETCPP_TRY
            buf = TET_utf16_to_utf32(tet, utf16string.data(),
                    static_cast<int>(utf16string.length()), ordering.c_str(),
                    &size);
            if (buf)
                retval.assign(buf, static_cast<size_t>(size));
        TETCPP_CATCH;

        return retval;
    }

    std::string utf8_to_utf32(const std::string& utf8string,
            const std::string& ordering) const
    {
        int size;
        const char *buf;
        std::string retval;

        TETCPP_TRY
            buf = TET_utf8_to_utf32(tet, utf8string.c_str(), ordering.c_str(),
                    &size);
            if (buf)
                retval.assign(buf, static_cast<size_t>(size));
        TETCPP_CATCH;

        return retval;
    }

    std::string utf32_to_utf8(const std::string& utf32string) const
    {
        int size;
        const char *buf;
        std::string retval;

        TETCPP_TRY
            buf = TET_utf32_to_utf8(tet, utf32string.data(),
                static_cast<int>(utf32string.length()), &size);
            if (buf)
                retval.assign(buf, static_cast<size_t>(size));
        TETCPP_CATCH;

        return retval;
    }

    int write_image_file(int doc, int imageid, const pstring& optlist)
    {
        std::string optlist_param;
        const char *p_optlist_param;
        param_to_utf8(optlist, optlist_param, p_optlist_param);

        int retval = -1;

        TETCPP_TRY
            retval = TET_write_image_file(tet, doc, imageid, p_optlist_param);
        TETCPP_CATCH;

        return retval;
    }

protected:
    TET_cpp *tet;

private:
    enum
    {
        utf16_wchar_t_size = 2,
        utf32_wchar_t_size = 4
    };

    void bad_wchar_size(const char *apiname) const
    {
        std::ostringstream exception_text;
        exception_text << apiname << ": unsupported wchar_t size: "
                        << sizeof(typename pstring::value_type);

        throw std::logic_error(exception_text.str());
    }

    void param_to_utf8(const pstring& param, std::string& tet_param,
                        const char *& tet_ptr) const
    {
        if (conv::do_conversion())
        {
            conv::convert_to_pdf_utf8(*this, param, tet_param);
            tet_ptr = tet_param.c_str();
        }
        else
        {
            const char *s = reinterpret_cast<const char *>(param.c_str());

            switch (sizeof(typename pstring::value_type))
            {
            case sizeof(char):
                /*
                 * Legacy case: Pass through user-supplied string.
                 */
                tet_ptr = s;
                break;

            case utf16_wchar_t_size:
                tet_ptr =
                    TET_utf16_to_utf8(tet, s,
                        static_cast<int>(param.length() * sizeof(wchar_t)), 0);
                break;

            case utf32_wchar_t_size:
                tet_ptr =
                    TET_utf32_to_utf8(tet, s,
                        static_cast<int>(param.length() * sizeof(wchar_t)), 0);
                break;

            default:
                bad_wchar_size("basic_TET<pstring, conv>::param_to_utf8");
            }
        }
    }

    void param_to_0utf16(const pstring& param, std::string& tet_param,
                        const char *& tet_ptr, int& len) const
    {
        if (conv::do_conversion())
        {
            conv::convert_to_pdf_utf16(*this, param, tet_param);
            tet_ptr = tet_param.c_str();
            len = static_cast<int>(tet_param.length());
        }
        else
        {
            const char *s = reinterpret_cast<const char *>(param.c_str());

            switch (sizeof(typename pstring::value_type))
            {
            case sizeof(char):
                /*
                 * Legacy case: Pass through user-supplied string with length 0,
                 * string must not contain 0 bytes.
                 */
                tet_ptr = s;
                len = 0;
                break;

            case utf16_wchar_t_size:
                /*
                 * UTF-16 can also be passed through directly
                 */
                tet_ptr = s;
                len = static_cast<int>(param.length() * utf16_wchar_t_size);
                break;

            case utf32_wchar_t_size:
                tet_ptr =
                    TET_utf32_to_utf16(tet, s,
                        static_cast<int>(param.length() * utf32_wchar_t_size),
                        "", &len);
                break;

            default:
                bad_wchar_size("basic_TET<pstring, conv>::param_to_utf16");
            }
        }
    }

    void param_to_utf16(const pstring& param, std::string& tet_param,
                        const char *& tet_ptr, int& len) const
    {
        if (conv::do_conversion())
        {
            conv::convert_to_pdf_utf16(*this, param, tet_param);
            tet_ptr = tet_param.c_str();
            len = static_cast<int>(tet_param.length());
        }
        else
        {
            const char *s = reinterpret_cast<const char *>(param.c_str());

            switch (sizeof(typename pstring::value_type))
            {
            case sizeof(char):
                /*
                 * Legacy case: Pass through user-supplied string including
                 * explicit length, string may contain 0 bytes.
                 */
                tet_ptr = s;
                len = static_cast<int>(param.length());
                break;

            case utf16_wchar_t_size:
                /*
                 * UTF-16 can also be passed through directly
                 */
                tet_ptr = s;
                len = static_cast<int>(param.length() * utf16_wchar_t_size);
                break;

            case utf32_wchar_t_size:
                tet_ptr =
                    TET_utf32_to_utf16(tet, s,
                        static_cast<int>(param.length() * utf32_wchar_t_size),
                        "", &len);
                break;

            default:
                bad_wchar_size("basic_TET<pstring, conv>::param_to_utf16");
            }
        }
    }

    void param_to_bytes(const pstring& param, std::string& tet_param,
                        const char *& tet_ptr) const
    {
        if (conv::do_conversion())
        {
            conv::convert_to_pdf_bytes(*this, param, tet_param);
            tet_ptr = tet_param.c_str();
        }
        else
        {
            const size_t size = sizeof(typename pstring::value_type);
            const char *s = reinterpret_cast<const char *>(param.c_str());

            switch (size)
            {
            case sizeof(char):
                tet_ptr = s;
                break;

            case utf16_wchar_t_size:
            case utf32_wchar_t_size:
                {
                    int highchar;

                    const char *deflated =
                            TET_deflate_unicode(tet, s,
                                    static_cast<int>(param.length() * size),
                                    size, &highchar);

                    if (!deflated)
                    {
                        std::ostringstream exception_text;

                        exception_text
                            << "basic_TET::param_to_bytes: high "
                                "Unicode character '0x"
                            << std::hex << highchar
                            << "' is not supported in this character string";

                        throw std::runtime_error(exception_text.str().c_str());
                    }

                    tet_ptr = deflated;
                }
                break;

            default:
                bad_wchar_size("basic_TET<pstring, conv>::param_to_bytes");
            }
        }
    }

    void apistring_to_pstring(const char * const tet_retval,
                        pstring& cpp_retval) const
    {
        if (conv::do_conversion())
        {
            if (tet_retval)
            {
                conv::convert_to_pstring(*this, tet_retval, cpp_retval);
            }
            else
            {
                cpp_retval.clear();
            }
        }
        else
        {
            if (tet_retval)
            {
                cpp_retval.assign(reinterpret_cast
                                    <const typename pstring::value_type *>
                                        (tet_retval));
            }
            else
            {
                cpp_retval.clear();
            }
        }
    }

    /**
     * Separate routine for converting output strings to pstrings. This is used
     * for the return value of get_text(), where the length is needed because
     * the wchar_t strings are not 0-terminated.
     */
    void outputstring_to_pstring(const char * const tet_retval,
                        pstring& cpp_retval, const int length) const
    {
        if (conv::do_conversion())
        {
            if (tet_retval)
            {
                conv::convert_to_pstring(*this, tet_retval, cpp_retval);
            }
            else
            {
                cpp_retval.clear();
            }
        }
        else
        {
            if (tet_retval)
            {
                cpp_retval.assign(reinterpret_cast
                                    <const typename pstring::value_type *>
                                        (tet_retval),
                                    length);
            }
            else
            {
                cpp_retval.clear();
            }
        }
    }
    
    // Prevent use of copy constructor and assignment operator, as it is
    // fatal to copy the TET_cpp pointer to another object.
    basic_TET(const basic_TET&);
    basic_TET& operator=(const basic_TET&);
};

#if TETCPP_TET_WSTRING
typedef basic_TET<std::wstring, TETNoOpConverter<std::wstring> > TET;
#else
typedef basic_TET<std::string, TETNoOpConverter<std::string> > TET;
#endif

#if TETCPP_USE_PDFLIB_NAMESPACE
} // end of PDFlib namespace
#endif

#if defined(_MSC_VER) && defined(_MANAGED)
#pragma managed
#endif

#endif	// TETLIB_HPP
