// Can the Modern C++ interface and the legacy APDFL C API coexist in one process?
// Build: build.bat   Run: mixtest.exe <mode>

#if defined(MODERN_FIRST)
#include <datalogics_interface/datalogics_interface.hpp>
#endif

#include "PDFLCalls.h"
#include "ASCalls.h"
#include "PDCalls.h"
#include "CosCalls.h"

#if !defined(MODERN_FIRST)
#include <datalogics_interface/datalogics_interface.hpp>
#endif

#include <windows.h>
#include <iostream>
#include <string>
#include <cstring>
#include <thread>

namespace dl = datalogics_interface;

static const char *kPdf =
    "C:/temp/deleteme/mcpp215/lib/Resources/Sample_Input/AddContent.pdf";
static const wchar_t *kLib = L"C:/temp/deleteme/mcpp215/lib";
static const wchar_t *kFontDir = L"C:/temp/deleteme/mcpp215/lib/Resources/Font";
static const wchar_t *kCMapDir = L"C:/temp/deleteme/mcpp215/lib/Resources/CMap";
static const wchar_t *kUniDir = L"C:/temp/deleteme/mcpp215/lib/Resources/Unicode";

static PDFLDataRec g_data;
static ASUTF16Val *g_fontDirs[2];
static ASUTF16Val *g_pluginDirs[1];
static PDDoc g_pd = NULL;

static int legacy_init() {
    memset(&g_data, 0, sizeof(PDFLDataRec));
    g_data.size = sizeof(PDFLDataRec);
    g_data.allocator = NULL;
    g_fontDirs[0] = (ASUTF16Val *)kFontDir;
    g_fontDirs[1] = (ASUTF16Val *)kCMapDir;
    g_data.dirList = g_fontDirs;
    g_data.listLen = 2;
    g_data.cMapDirectory = (ASUTF16Val *)kCMapDir;
    g_data.unicodeDirectory = (ASUTF16Val *)kUniDir;
    g_pluginDirs[0] = (ASUTF16Val *)kLib;
    g_data.pluginDirList = g_pluginDirs;
    g_data.pluginDirListLen = 1;
    SetDllDirectoryW(kLib);
    g_data.inst = LoadLibraryW(L"DL210pdfl.dll");
    if (!g_data.inst) {
        std::cout << "  [legacy] LoadLibrary(DL210pdfl.dll) FAILED " << GetLastError() << std::endl;
        return -1;
    }
    ASInt32 err = PDFLInitHFT(&g_data);
    std::cout << "  [legacy] PDFLInitHFT -> " << err << std::endl;
    return (int)err;
}

static void legacy_open() {
    volatile int pages = -1;
    DURING
        ASPathName p = ASFileSysCreatePathName(ASGetDefaultFileSys(),
                                               ASAtomFromString("Cstring"), (void *)kPdf, NULL);
        g_pd = PDDocOpen(p, ASGetDefaultFileSys(), NULL, true);
        ASFileSysReleasePath(ASGetDefaultFileSys(), p);
        pages = PDDocGetNumPages(g_pd);
    HANDLER
        std::cout << "  [legacy] open RAISED err=" << ERRORCODE << std::endl;
        return;
    END_HANDLER
    std::cout << "  [legacy] PDDocGetNumPages = " << pages << std::endl;
}

static void legacy_use(const char *tag) {
    volatile int pages = -1;
    DURING
        pages = PDDocGetNumPages(g_pd);
        CosDoc cd = PDDocGetCosDoc(g_pd);
        CosObj root = CosDocGetRoot(cd);
        (void)root;
    HANDLER
        std::cout << "  [legacy] " << tag << " RAISED err=" << ERRORCODE << std::endl;
        return;
    END_HANDLER
    std::cout << "  [legacy] " << tag << " OK, pages = " << pages << std::endl;
}

static void legacy_use_quiet() {
    DURING
        (void)PDDocGetNumPages(g_pd);
        CosDoc cd = PDDocGetCosDoc(g_pd);
        (void)CosDocGetRoot(cd);
    HANDLER
        std::cout << "  [legacy/main] RAISED err=" << ERRORCODE << std::endl;
    END_HANDLER
}

static void legacy_close() {
    DURING
        if (g_pd) { PDDocClose(g_pd); g_pd = NULL; }
    HANDLER
        std::cout << "  [legacy] close RAISED err=" << ERRORCODE << std::endl;
    END_HANDLER
}

static void legacy_term() {
    PDFLTermHFT();
    std::cout << "  [legacy] PDFLTermHFT done" << std::endl;
}

struct Tracer {
    const char *n;
    ~Tracer() { std::cout << "  [c++] ~Tracer(" << n << ") RAN" << std::endl; }
};

// Does a legacy ASRaise (longjmp) unwind past live C++ objects without
// running their destructors?
static void longjmp_over_cpp() {
    dl::Document outer_doc(kPdf);
    Tracer outer{"outer (before DURING)"};
    std::cout << "  [test] entering DURING with live C++ objects" << std::endl;
    DURING
        Tracer inner{"inner (inside DURING)"};
        ASRaise(GenError(genErrGeneral));
        std::cout << "  [test] NOT REACHED" << std::endl;
    HANDLER
        std::cout << "  [legacy] HANDLER caught err=" << ERRORCODE << std::endl;
    END_HANDLER
    std::cout << "  [test] past END_HANDLER" << std::endl;
}

static void modern_use(dl::Document &d, const char *tag) {
    try {
        std::cout << "  [modern] " << tag << " pages = " << d.get_num_pages() << std::endl;
    } catch (const std::exception &e) {
        std::cout << "  [modern] " << tag << " THREW: " << e.what() << std::endl;
    }
}

int main(int argc, char **argv) {
    std::string mode = argc > 1 ? argv[1] : "help";
    std::cout << "=== mode: " << mode << " ===" << std::endl;
    {
        wchar_t buf[MAX_PATH];
        HMODULE h = GetModuleHandleW(L"DL210pdfl.dll");
        if (h && GetModuleFileNameW(h, buf, MAX_PATH))
            std::wcout << L"  [dll] DL210pdfl.dll loaded from " << buf << std::endl;
        else
            std::cout << "  [dll] DL210pdfl.dll not yet loaded" << std::endl;
        h = GetModuleHandleW(L"datalogics_interface_api.dll");
        if (h && GetModuleFileNameW(h, buf, MAX_PATH))
            std::wcout << L"  [dll] datalogics_interface_api.dll from " << buf << std::endl;
    }

    if (mode == "legacy") {
        if (legacy_init() != 0) return 1;
        legacy_open();
        legacy_use("use");
        legacy_close();
        legacy_term();
        return 0;
    }

    if (mode == "modern") {
        try {
            dl::Library lib;
            std::cout << "  [modern] Library ok, apdfl=" << dl::Library::get_apdfl_version() << std::endl;
            dl::Document d(kPdf);
            modern_use(d, "first");
        } catch (const std::exception &e) {
            std::cout << "  [modern] THREW: " << e.what() << std::endl;
            return 1;
        }
        return 0;
    }

    if (mode == "legacy-first") {
        if (legacy_init() != 0) return 1;
        legacy_open();
        try {
            dl::Library lib;
            std::cout << "  [modern] Library ctor ok after legacy init" << std::endl;
            dl::Document d(kPdf);
            modern_use(d, "after-legacy");
            legacy_use("while-modern-live");
            modern_use(d, "interleaved");
        } catch (const std::exception &e) {
            std::cout << "  [modern] THREW: " << e.what() << std::endl;
        }
        std::cout << "  -- modern Library destroyed --" << std::endl;
        legacy_use("after-modern-teardown");
        legacy_close();
        legacy_term();
        return 0;
    }

    if (mode == "modern-first") {
        try {
            dl::Library lib;
            dl::Document d(kPdf);
            modern_use(d, "before-legacy");
            if (legacy_init() != 0) return 1;
            legacy_open();
            modern_use(d, "after-legacy-init");
            legacy_use("while-modern-live");
            legacy_close();
            legacy_term();
            std::cout << "  -- legacy terminated, modern still live --" << std::endl;
            modern_use(d, "after-legacy-term");
        } catch (const std::exception &e) {
            std::cout << "  [modern] THREW: " << e.what() << std::endl;
        }
        std::cout << "  (modern teardown follows)" << std::endl;
        return 0;
    }

    if (mode == "threads") {
        if (legacy_init() != 0) return 1;
        legacy_open();
        std::thread worker([] {
            try {
                dl::Library lib;   // modern requires its own Library per thread
                dl::Document d(kPdf);
                for (int i = 0; i < 200; ++i) {
                    if (d.get_num_pages() != 1) { std::cout << "  [modern/thr] BAD" << std::endl; return; }
                }
                std::cout << "  [modern/thr] 200 iterations OK" << std::endl;
            } catch (const std::exception &e) {
                std::cout << "  [modern/thr] THREW: " << e.what() << std::endl;
            }
        });
        for (int i = 0; i < 200; ++i) legacy_use_quiet();
        std::cout << "  [legacy/main] 200 iterations done" << std::endl;
        worker.join();
        legacy_close();
        legacy_term();
        return 0;
    }

    if (mode == "longjmp") {
        if (legacy_init() != 0) return 1;
        try {
            dl::Library lib;
            longjmp_over_cpp();
        } catch (const std::exception &e) {
            std::cout << "  [modern] THREW: " << e.what() << std::endl;
        }
        legacy_term();
        return 0;
    }

    std::cout << "usage: mixtest legacy|modern|legacy-first|modern-first|longjmp" << std::endl;
    return 2;
}
