/*
 * Resource-based image extractor based on PDFlib TET
 *
 * $Id: image_resources.cpp,v 1.4 2010/07/22 10:44:55 rjs Exp $
 */

#include <iostream>
#include <iomanip>
#include <fstream>
#include <algorithm>

#include "tet.hpp"

using namespace std;
using namespace pdflib;

namespace
{

/*  global option list */
const wstring globaloptlist =
	    L"searchpath={{../data}}";

/* document-specific  option list */
const wstring docoptlist = L"";

/* page-specific option list */
const wstring pageoptlist = L"";

/* here you can insert basic image extract options (more below) */
const wstring baseimageoptlist = L"";

wstring get_wstring(const TET& tet, const string& utf8_string);

} // end of anonymous namespace

int main(int argc, char **argv)
{
    ofstream out;
    int pageno = 0;
    
    try
    {
        TET tet;

        if (argc != 2)
        {
            wcerr << L"usage: image_resources <infilename>";
            return(2);
        }

	/*
	 * Prepare wide character representation of outfilebase for use in
	 * option list, under the assumption that the program arguments are
	 * encoded as UTF-8.
	 */
	string outfilebase = argv[1];
	wstring woutfilebase(get_wstring(tet, outfilebase));

        tet.set_option(globaloptlist);

        /*
         * Caution: For simplicity we assume that the program arguments are
         * encoded as UTF-8, which might not be true in all cases!
         */
        wstring doc_name(get_wstring(tet, string(argv[1])));
        const int doc = tet.open_document(doc_name, docoptlist);

        if (doc == -1)
        {
            wcerr << L"Error " << tet.get_errnum()
                << L" in " << tet.get_apiname() << L"(): "
                << tet.get_errmsg() << endl;
            return 2;
        }

        /* Images will only be merged upon opening a page.
         * In order to enumerate all merged image resources
         * we open all pages before extracting the images.
         */

        /* get number of pages in the document */
        const int n_pages = (int) tet.pcos_get_number(doc, L"length:pages");

        /* loop over pages in the document */
        for (pageno = 1; pageno <= n_pages; ++pageno)
        {
            const int page = tet.open_page(doc, pageno, pageoptlist);

            if (page == -1)
            {
                wcerr << L"Error " << tet.get_errnum()
                    << L" in " << tet.get_apiname()
                    << L"(): " << tet.get_errmsg() << endl;
                continue;                        /* try next page */
            }

            if (tet.get_errnum() != 0)
            {
                wcerr << L"Error " << tet.get_errnum()
                    << L" in " << tet.get_apiname()
                    << L"() on page " << pageno
                    << L": " << tet.get_errmsg() << endl;
            }

            tet.close_page(page);
	}

        /* get number of image resources in the document */
        const int n_images = (int) tet.pcos_get_number(doc, L"length:images");
	int imageid;

	/* loop over image resources in the document */
	for (imageid = 0; imageid < n_images; imageid++)
	{
	    wostringstream path;
	    wostringstream path2;

	    /* examine image type */
	    path.str(L"");
	    path << L"images[" << imageid << L"]/mergetype";
	    const int mergetype = (int) tet.pcos_get_number(doc, path.str());

	    /* skip images which have been consumed by merging */
	    if (mergetype == 0 || mergetype == 1)
	    {
		/* Print the following information for each image:
		 * - page and image number
		 * - pCOS id (required for indexing the images[] array)
		 * - physical size of the placed image on the page
		 * - pixel size of the underlying PDF image
		 * - number of components, bits per component, and colorspace
		 * - mergetype if different from "normal", i.e. "artificial"
		 *  (=merged) or "consumed"
		 */
                int width, height, bpc, cs, mergetype;

                path.str(L"");
                path << L"images[" << imageid << L"]/Width";
                width = (int) tet.pcos_get_number(doc, path.str());

                path.str(L"");
                path << L"images[" << imageid << L"]/Height";
                height = (int) tet.pcos_get_number(doc, path.str());

                path.str(L"");
                path << L"images[" << imageid << L"]/bpc";
                bpc = (int) tet.pcos_get_number(doc, path.str());

                path.str(L"");
                path << L"images[" << imageid << L"]/colorspaceid";
                cs = (int) tet.pcos_get_number(doc, path.str());

                wcout << L"image I" << imageid
                        << L": " << fixed << setprecision(2)
                        << width << L"x" <<  height << L" pixel, ";

                if (cs != -1)
                {
                    path.str(L"");
                    path << L"colorspaces[" << cs << L"]/components";

                    path2.str(L"");
                    path2 << L"colorspaces[" << cs << L"]/name";

                    wcout << (int) tet.pcos_get_number(doc, path.str()) << L"x"
                            << bpc << L" bit "
                            << tet.pcos_get_string(doc, path2.str());
                }
                else
                {
                    /* cs==-1 may happen for some JPEG 2000 images. bpc,
                     * colorspace name and number of components are not
                     * available in this case.
                     */
                    wcout << L"JPEG2000";
                }

		path.str(L"");
                path << L"images[" << imageid << L"]/mergetype";
		mergetype = (int) tet.pcos_get_number(doc, path.str());

                if (mergetype != 0)
                {
		    wcout << L", mergetype=";
		    if (mergetype == 1)
			wcout << L"artificial";
		    else
			wcout << L"consumed";
                }

		wcout << endl;

		wostringstream imageoptlist;

	       /*
		* Fetch the image data and write it to a disk file. The
		* output filenames are generated from the input filename
		* by appending page number and image number.
		*/
		imageoptlist << baseimageoptlist
                            << L" filename={" << woutfilebase << L"_I"
                            << imageid << L"}";

		if (tet.write_image_file(doc, imageid,
                                    imageoptlist.str()) == -1)
		{
                        wcerr << L"Error " << tet.get_errnum()
                            << L" in " << tet.get_apiname()
                            << L"(): " << tet.get_errmsg() << endl;
                        continue; /* process next image */
                }
            }
        }

        tet.close_document(doc);
    }
    catch (TET::Exception &ex) {
	wcerr << L"Error " << ex.get_errnum()
                << L" in " << ex.get_apiname()
                << L"(): " << ex.get_errmsg() << endl;
        return 2;
    }

    out.close();
    return 0;
}

namespace
{

/*
 * Get a wstring for the given string, assuming the string is encoded in
 * UTF-8.
 */
wstring get_wstring(const TET& tet, const string& utf8_string)
{
    const size_t size = sizeof(wstring::value_type);
    string wide_string;

    switch (size)
    {
    case 2:
        wide_string = tet.utf8_to_utf16(utf8_string, "");
        break;

    case 4:
        wide_string = tet.utf8_to_utf32(utf8_string, "");
        break;

    default:
        throw std::logic_error("Unsupported wchar_t size");
    }

    return wstring(reinterpret_cast<const wchar_t *>(wide_string.data()),
            wide_string.length() / size);
}

} // end anonymous namespace
