//========================================================================
//
// JSInfo.cc
//
// This file is licensed under the GPLv2 or later
//
// Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2017, 2020, 2021, 2024-2026 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
// Copyright (C) 2020 Oliver Sander <oliver.sander@tu-dresden.de>
// Copyright (C) 2020 Nelson Benítez León <nbenitezl@gmail.com>
// Copyright (C) 2024, 2025 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================

#include "config.h"
#include <cstdio>
#include "Annot.h"
#include "PDFDoc.h"
#include "JSInfo.h"
#include "Link.h"
#include "Form.h"
#include "UnicodeMap.h"
#include "UTF.h"
// #include "Win32Console.h"

JSInfo::JSInfo(PDFDoc *docA, int firstPage)
{
    doc = docA;
    currentPage = firstPage + 1;
}

void JSInfo::printJS(std::string_view js)
{
    char buf[8];

    if (js.empty()) {
        return;
    }

    std::vector<Unicode> u = TextStringToUCS4(js);
    for (auto &c : u) {
        int n = uniMap->mapUnicode(c, buf, sizeof(buf));
        fwrite(buf, 1, n, file);
    }
}

void JSInfo::scanLinkAction(const LinkAction *link, const char *action)
{
    if (!link) {
        return;
    }

    if (link->getKind() == actionJavaScript) {
        hasJS = true;
        if (print) {
            const auto *linkjs = static_cast<const LinkJavaScript *>(link);
            if (linkjs->isOk()) {
                const std::string &s = linkjs->getScript();
                fprintf(file, "%s:\n", action);
                printJS(s);
                fputs("\n\n", file);
            }
        }
    }

    if (link->getKind() == actionRendition) {
        const auto *linkr = static_cast<const LinkRendition *>(link);
        if (!linkr->getScript().empty()) {
            hasJS = true;
            if (print) {
                fprintf(file, "%s (Rendition):\n", action);
                const std::string &s(linkr->getScript());
                printJS(s);
                fputs("\n\n", file);
            }
        }
    }
}

void JSInfo::scanJS(int nPages)
{
    print = false;
    file = nullptr;
    onlyFirstJS = false;
    scan(nPages);
}

void JSInfo::scanJS(int nPages, FILE *fout, const UnicodeMap *uMap)
{
    print = true;
    file = fout;
    uniMap = uMap;
    onlyFirstJS = false;
    scan(nPages);
}

void JSInfo::scanJS(int nPages, bool stopOnFirstJS)
{
    print = false;
    file = nullptr;
    onlyFirstJS = stopOnFirstJS;
    scan(nPages);
}

void JSInfo::scan(int nPages)
{
    Page *page;
    Annots *annots;
    int lastPage;

    hasJS = false;

    // Names
    int numNames = doc->getCatalog()->numJS();
    if (numNames > 0) {
        hasJS = true;
        if (onlyFirstJS) {
            return;
        }
        if (print) {
            for (int i = 0; i < numNames; i++) {
                fprintf(file, "Name Dictionary \"%s\":\n", doc->getCatalog()->getJSName(i)->c_str());
                std::string js = doc->getCatalog()->getJS(i);
                printJS(js);
                fputs("\n\n", file);
            }
        }
    }

    // document actions
    scanLinkAction(doc->getCatalog()->getOpenAction().get(), "Open Document Action");
    scanLinkAction(doc->getCatalog()->getAdditionalAction(Catalog::actionCloseDocument).get(), "Before Close Document");
    scanLinkAction(doc->getCatalog()->getAdditionalAction(Catalog::actionSaveDocumentStart).get(), "Before Save Document");
    scanLinkAction(doc->getCatalog()->getAdditionalAction(Catalog::actionSaveDocumentFinish).get(), "After Save Document");
    scanLinkAction(doc->getCatalog()->getAdditionalAction(Catalog::actionPrintDocumentStart).get(), "Before Print Document");
    scanLinkAction(doc->getCatalog()->getAdditionalAction(Catalog::actionPrintDocumentFinish).get(), "After Print Document");

    if (onlyFirstJS && hasJS) {
        return;
    }
    // form field actions
    if (doc->getCatalog()->getFormType() == Catalog::AcroForm) {
        Form *form = doc->getCatalog()->getForm();
        for (int i = 0; i < form->getNumFields(); i++) {
            FormField *field = form->getRootField(i);
            for (int j = 0; j < field->getNumWidgets(); j++) {
                FormWidget *widget = field->getWidget(j);
                scanLinkAction(widget->getActivationAction(), "Field Activated");
                scanLinkAction(widget->getAdditionalAction(Annot::actionFieldModified).get(), "Field Modified");
                scanLinkAction(widget->getAdditionalAction(Annot::actionFormatField).get(), "Format Field");
                scanLinkAction(widget->getAdditionalAction(Annot::actionValidateField).get(), "Validate Field");
                scanLinkAction(widget->getAdditionalAction(Annot::actionCalculateField).get(), "Calculate Field");
                if (onlyFirstJS && hasJS) {
                    return;
                }
            }
        }
    }

    // scan pages

    if (currentPage > doc->getNumPages()) {
        return;
    }

    lastPage = currentPage + nPages;
    if (lastPage > doc->getNumPages() + 1) {
        lastPage = doc->getNumPages() + 1;
    }

    for (int pg = currentPage; pg < lastPage; ++pg) {
        page = doc->getPage(pg);
        if (!page) {
            continue;
        }

        // page actions (open, close)
        scanLinkAction(page->getAdditionalAction(Page::actionOpenPage).get(), "Page Open");
        scanLinkAction(page->getAdditionalAction(Page::actionClosePage).get(), "Page Close");

        if (onlyFirstJS && hasJS) {
            return;
        }
        // annotation actions (links, screen, widget)
        annots = page->getAnnots();
        for (const std::shared_ptr<Annot> &a : annots->getAnnots()) {
            if (a->getType() == Annot::typeLink) {
                auto *annot = static_cast<AnnotLink *>(a.get());
                scanLinkAction(annot->getAction(), "Link Annotation Activated");
                if (onlyFirstJS && hasJS) {
                    return;
                }
            } else if (a->getType() == Annot::typeScreen) {
                auto *annot = static_cast<AnnotScreen *>(a.get());
                scanLinkAction(annot->getAction(), "Screen Annotation Activated");
                scanLinkAction(annot->getAdditionalAction(Annot::actionCursorEntering).get(), "Screen Annotation Cursor Enter");
                scanLinkAction(annot->getAdditionalAction(Annot::actionCursorLeaving).get(), "Screen Annotation Cursor Leave");
                scanLinkAction(annot->getAdditionalAction(Annot::actionMousePressed).get(), "Screen Annotation Mouse Pressed");
                scanLinkAction(annot->getAdditionalAction(Annot::actionMouseReleased).get(), "Screen Annotation Mouse Released");
                scanLinkAction(annot->getAdditionalAction(Annot::actionFocusIn).get(), "Screen Annotation Focus In");
                scanLinkAction(annot->getAdditionalAction(Annot::actionFocusOut).get(), "Screen Annotation Focus Out");
                scanLinkAction(annot->getAdditionalAction(Annot::actionPageOpening).get(), "Screen Annotation Page Open");
                scanLinkAction(annot->getAdditionalAction(Annot::actionPageClosing).get(), "Screen Annotation Page Close");
                scanLinkAction(annot->getAdditionalAction(Annot::actionPageVisible).get(), "Screen Annotation Page Visible");
                scanLinkAction(annot->getAdditionalAction(Annot::actionPageInvisible).get(), "Screen Annotation Page Invisible");

                if (onlyFirstJS && hasJS) {
                    return;
                }
            } else if (a->getType() == Annot::typeWidget) {
                auto *annot = static_cast<AnnotWidget *>(a.get());
                scanLinkAction(annot->getAction(), "Widget Annotation Activated");
                scanLinkAction(annot->getAdditionalAction(Annot::actionCursorEntering).get(), "Widget Annotation Cursor Enter");
                scanLinkAction(annot->getAdditionalAction(Annot::actionCursorLeaving).get(), "Widget Annotation Cursor Leave");
                scanLinkAction(annot->getAdditionalAction(Annot::actionMousePressed).get(), "Widget Annotation Mouse Pressed");
                scanLinkAction(annot->getAdditionalAction(Annot::actionMouseReleased).get(), "Widget Annotation Mouse Released");
                scanLinkAction(annot->getAdditionalAction(Annot::actionFocusIn).get(), "Widget Annotation Focus In");
                scanLinkAction(annot->getAdditionalAction(Annot::actionFocusOut).get(), "Widget Annotation Focus Out");
                scanLinkAction(annot->getAdditionalAction(Annot::actionPageOpening).get(), "Widget Annotation Page Open");
                scanLinkAction(annot->getAdditionalAction(Annot::actionPageClosing).get(), "Widget Annotation Page Close");
                scanLinkAction(annot->getAdditionalAction(Annot::actionPageVisible).get(), "Widget Annotation Page Visible");
                scanLinkAction(annot->getAdditionalAction(Annot::actionPageInvisible).get(), "Widget Annotation Page Invisible");
                if (onlyFirstJS && hasJS) {
                    return;
                }
            }
        }
    }

    currentPage = lastPage;
}

bool JSInfo::containsJS() const
{
    return hasJS;
}
