//========================================================================
//
// pdf-inspector.cc
//
// Copyright 2005 Jonathan Blandford <jrb@redhat.com>
// Copyright 2018 Adam Reichold <adam.reichold@t-online.de>
// Copyright 2019, 2022, 2025 Albert Astals Cid <aacid@kde.org>
//
//========================================================================

#include <config.h>

#include <goo/gmem.h>
#include <goo/GooTimer.h>
#include <splash/SplashTypes.h>
#include <splash/SplashBitmap.h>
#include "Object.h"
#include "ProfileData.h"
#include "GfxState.h"

#include <gdk/gdk.h>
#include "CairoOutputDev.h"

#include "PDFDoc.h"
#include "GlobalParams.h"
#include "ErrorCodes.h"
#include <gtk/gtk.h>

// Mapping
#include "pdf-operators.h"

enum
{
    OP_STRING,
    OP_COUNT,
    OP_TOTAL,
    OP_MIN,
    OP_MAX,
    N_COLUMNS
};

class PdfInspector
{
public:
    PdfInspector();

    void set_file_name(const char *file_name);
    void load(const char *file_name);
    void run();
    static void error_dialog(const char *error_message);
    void analyze_page(int page);

private:
    static void on_file_activated(GtkWidget *widget, PdfInspector *inspector);
    static void on_selection_changed(GtkTreeSelection *selection, PdfInspector *inspector);
    static void on_analyze_clicked(GtkWidget *widget, PdfInspector *inspector);

    GtkBuilder *builder;
    GtkTreeModel *model;
    PDFDoc *doc;
    CairoOutputDev *output;
};

PdfInspector::PdfInspector()
{
    GtkWidget *widget;
    GError *error = nullptr;

    builder = gtk_builder_new();

    if (!gtk_builder_add_from_file(builder, SRC_DIR "/pdf-inspector.ui", &error)) {
        g_warning("Couldn't load builder file: %s", error->message);
        g_error_free(error);
    }

    widget = GTK_WIDGET(gtk_builder_get_object(builder, "pdf_file_chooser_button"));
    g_signal_connect(widget, "selection-changed", G_CALLBACK(on_file_activated), this);

    widget = GTK_WIDGET(gtk_builder_get_object(builder, "analyze_button"));
    g_signal_connect(widget, "clicked", G_CALLBACK(on_analyze_clicked), this);

    // setup the TreeView
    widget = GTK_WIDGET(gtk_builder_get_object(builder, "pdf_tree_view"));
    g_signal_connect(gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)), "changed", G_CALLBACK(on_selection_changed), this);
    model = reinterpret_cast<GtkTreeModel *>(gtk_list_store_new(N_COLUMNS, G_TYPE_STRING, G_TYPE_INT, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_DOUBLE));
    gtk_tree_view_set_model(GTK_TREE_VIEW(widget), model);
    gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(widget), 0, "Operation", gtk_cell_renderer_text_new(), "text", OP_STRING, NULL);

    gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(widget), 1, "Count", gtk_cell_renderer_text_new(), "text", OP_COUNT, NULL);

    gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(widget), 2, "Elapsed", gtk_cell_renderer_text_new(), "text", OP_TOTAL, NULL);

    gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(widget), 3, "Min", gtk_cell_renderer_text_new(), "text", OP_MIN, NULL);

    gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(widget), 4, "Max", gtk_cell_renderer_text_new(), "text", OP_MAX, NULL);

    for (int i = 0; i < N_COLUMNS; i++) {
        GtkTreeViewColumn *column;

        column = gtk_tree_view_get_column(GTK_TREE_VIEW(widget), i);
        gtk_tree_view_column_set_sort_column_id(column, i);
    }
    doc = nullptr;
    output = new CairoOutputDev();
    output->setPrinting(false);

    // set up initial widgets
    load(nullptr);
}

void PdfInspector::set_file_name(const char *file_name)
{
    GtkWidget *widget;

    widget = GTK_WIDGET(gtk_builder_get_object(builder, "pdf_file_chooser_button"));
    gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(widget), file_name);
}

void PdfInspector::on_file_activated(GtkWidget *widget, PdfInspector *inspector)
{
    gchar *file_name;

    file_name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
    if (file_name) {
        inspector->load(file_name);
    }

    g_free(file_name);
}

void PdfInspector::on_selection_changed(GtkTreeSelection *selection, PdfInspector *inspector)
{
    GtkWidget *label;
    size_t i;
    GtkTreeModel *model;
    GtkTreeIter iter;
    gchar *op = nullptr;

    label = GTK_WIDGET(gtk_builder_get_object(inspector->builder, "description_label"));
    gtk_label_set_markup(GTK_LABEL(label), "<i>No Description</i>");

    if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
        gtk_tree_model_get(model, &iter, OP_STRING, &op, -1);
    }

    if (op == nullptr) {
        return;
    }

    for (i = 0; i < G_N_ELEMENTS(op_mapping); i++) {

        if (!strcmp(op, op_mapping[i].op)) {
            gchar *text;
            text = g_strdup_printf("<i>%s</i>", op_mapping[i].description);
            gtk_label_set_markup(GTK_LABEL(label), text);
            g_free(text);
            break;
        }
    }

    g_free(op);
}

void PdfInspector::on_analyze_clicked(GtkWidget * /*widget*/, PdfInspector *inspector)
{
    GtkWidget *spin;
    int page;

    spin = GTK_WIDGET(gtk_builder_get_object(inspector->builder, "pdf_spin"));

    page = static_cast<int>(gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin)));

    inspector->analyze_page(page);
}

void PdfInspector::analyze_page(int page)
{
    GtkWidget *label;
    char *text;
    cairo_t *cr;
    cairo_surface_t *surface;

    label = GTK_WIDGET(gtk_builder_get_object(builder, "pdf_total_label"));

    output->startProfile();
    gtk_list_store_clear(GTK_LIST_STORE(model));

    GooTimer timer;
    surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, doc->getPageCropWidth(page + 1), doc->getPageCropHeight(page + 1));
    cr = cairo_create(surface);
    cairo_surface_destroy(surface);
    output->setCairo(cr);
    cairo_destroy(cr);
    doc->displayPage(output, page + 1, 72, 72, 0, false, true, false);
    output->setCairo(nullptr);

    // Total time;
    text = g_strdup_printf("%g", timer.getElapsed());
    gtk_label_set_text(GTK_LABEL(label), text);
    g_free(text);

    // Individual times;
    auto hash = output->endProfile();
    for (const auto &kvp : *hash) {
        GtkTreeIter tree_iter;
        const auto *const data_p = &kvp.second;

        gtk_list_store_append(GTK_LIST_STORE(model), &tree_iter);
        gtk_list_store_set(GTK_LIST_STORE(model), &tree_iter, OP_STRING, kvp.first.c_str(), OP_COUNT, data_p->getCount(), OP_TOTAL, data_p->getTotal(), OP_MIN, data_p->getMin(), OP_MAX, data_p->getMax(), -1);
    }
}

void PdfInspector::load(const char *file_name)
{
    GtkWidget *spin;
    GtkWidget *button;
    GtkWidget *label;

    // kill the old PDF file
    if (doc != nullptr) {
        delete doc;
        doc = nullptr;
    }

    // load the new file
    if (file_name) {
        doc = new PDFDoc(std::make_unique<GooString>(file_name));
    }

    if (doc && !doc->isOk()) {
        PdfInspector::error_dialog("Failed to load file.");
        delete doc;
        doc = nullptr;
    }

    spin = GTK_WIDGET(gtk_builder_get_object(builder, "pdf_spin"));
    button = GTK_WIDGET(gtk_builder_get_object(builder, "analyze_button"));
    label = GTK_WIDGET(gtk_builder_get_object(builder, "pdf_total_label"));
    gtk_label_set_text(GTK_LABEL(label), "");

    if (doc) {
        gtk_widget_set_sensitive(spin, TRUE);
        gtk_widget_set_sensitive(button, TRUE);
        gtk_widget_set_sensitive(label, TRUE);
        gtk_spin_button_set_range(GTK_SPIN_BUTTON(spin), 0, doc->getNumPages() - 1);
        gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), 0);

        output->startDoc(doc);
    } else {
        gtk_widget_set_sensitive(spin, FALSE);
        gtk_widget_set_sensitive(button, FALSE);
        gtk_widget_set_sensitive(label, FALSE);
    }
}

void PdfInspector::error_dialog(const char *error_message)
{
    g_warning("%s", error_message);
}

void PdfInspector::run()
{
    GtkWidget *dialog;

    dialog = GTK_WIDGET(gtk_builder_get_object(builder, "pdf_dialog"));

    gtk_dialog_run(GTK_DIALOG(dialog));
}

int main(int argc, char *argv[])
{
    const char *file_name = nullptr;
    PdfInspector *inspector;

    gtk_init(&argc, &argv);

    globalParams = std::make_unique<GlobalParams>();
    globalParams->setProfileCommands(true);
    globalParams->setPrintCommands(true);

    if (argc == 2) {
        file_name = argv[1];
    } else if (argc > 2) {
        fprintf(stderr, "usage: %s [PDF-FILE]\n", argv[0]);
        return -1;
    }

    inspector = new PdfInspector();

    if (file_name) {
        inspector->set_file_name(file_name);
    }

    inspector->run();

    delete inspector;

    return 0;
}
