// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Authors:
 *   Christopher Brown <audiere@gmail.com>
 *   Ted Gould <ted@gould.cx>
 *
 * Copyright (C) 2007 Authors
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include <libintl.h>

#include <gtkmm/box.h>
#include <gtkmm/adjustment.h>
#include <gtkmm/spinbutton.h>

#include <glib/gstdio.h>

#include "desktop.h"

#include "selection.h"

#include "extension/effect.h"
#include "extension/system.h"

#include "imagemagick.h"
#include <Magick++.h>

#include "xml/href-attribute-helper.h"

namespace Inkscape {
namespace Extension {
namespace Internal {
namespace Bitmap {

class ImageMagickDocCache: public Inkscape::Extension::Implementation::ImplementationDocumentCache {
    friend class ImageMagick;
private:
    void readImage(char const *xlink, char const *id, Magick::Image *image);
protected:
    Inkscape::XML::Node** _nodes;    
    
    Magick::Image** _images;
    int _imageCount;
    char** _caches;
    unsigned* _cacheLengths;
    const char** _originals;
    SPItem** _imageItems;
public:
    ImageMagickDocCache(Inkscape::UI::View::View * view);
    ~ImageMagickDocCache ( ) override;
};

ImageMagickDocCache::ImageMagickDocCache(Inkscape::UI::View::View * view) :
    Inkscape::Extension::Implementation::ImplementationDocumentCache(view),
    _nodes(NULL),
    _images(NULL),
    _imageCount(0),
    _caches(NULL),
    _cacheLengths(NULL),
    _originals(NULL),
    _imageItems(NULL)
{
    SPDesktop *desktop = (SPDesktop*)view;
    auto selectedItemList = desktop->getSelection()->items();
    int selectCount = (int) boost::distance(selectedItemList);
    
    // Init the data-holders
    _nodes = new Inkscape::XML::Node*[selectCount];
    _originals = new const char*[selectCount];
    _caches = new char*[selectCount];
    _cacheLengths = new unsigned int[selectCount];
    _images = new Magick::Image*[selectCount];
    _imageCount = 0;
    _imageItems = new SPItem*[selectCount];

    // Loop through selected items
    for (auto i = selectedItemList.begin(); i != selectedItemList.end(); ++i) {
        SPItem *item = *i;
        Inkscape::XML::Node *node = reinterpret_cast<Inkscape::XML::Node *>(item->getRepr());
        if (!strcmp(node->name(), "image") || !strcmp(node->name(), "svg:image"))
        {
            _nodes[_imageCount] = node;    
            char const *xlink = Inkscape::getHrefAttribute(*node).second;
            char const *id = node->attribute("id");
            _originals[_imageCount] = xlink;
            _caches[_imageCount] = (char*)"";
            _cacheLengths[_imageCount] = 0;
            _images[_imageCount] = new Magick::Image();
            readImage(xlink, id, _images[_imageCount]);
            _imageItems[_imageCount] = item;
            _imageCount++;
        }
    }
}

ImageMagickDocCache::~ImageMagickDocCache ( ) {
    if (_nodes)
        delete _nodes;
    if (_originals)
        delete _originals;
    if (_caches)
        delete _caches;
    if (_cacheLengths)
        delete _cacheLengths;
    if (_images)
        delete _images;
    if (_imageItems)
        delete _imageItems;
    return;
}

void
ImageMagickDocCache::readImage(const char *xlink, const char *id, Magick::Image *image)
{
    // Find if the xlink:href is base64 data, i.e. if the image is embedded 
    gchar *search = g_strndup(xlink, 30);
    if (strstr(search, "base64") != (char*)NULL) {
        // 7 = strlen("base64") + strlen(",")
        const char* pureBase64 = strstr(xlink, "base64") + 7;        
        Magick::Blob blob;
        blob.base64(pureBase64);
        try {
            image->read(blob);
        } catch (Magick::Exception &error_) {
            g_warning("ImageMagick could not read '%s'\nDetails: %s", id, error_.what());
        }
    } else {
        gchar *path;
        if (strncmp (xlink,"file:", 5) == 0) {
            path = g_filename_from_uri(xlink, NULL, NULL);
        } else {
            path = g_strdup(xlink);
        }
        try {
            image->read(path);
        } catch (Magick::Exception &error_) {
            g_warning("ImageMagick could not read '%s' from '%s'\nDetails: %s", id, path, error_.what());
        }
        g_free(path);
    }
    g_free(search);
}

bool
ImageMagick::load(Inkscape::Extension::Extension */*module*/)
{
    return true;
}

Inkscape::Extension::Implementation::ImplementationDocumentCache *
ImageMagick::newDocCache (Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view) {
    return new ImageMagickDocCache(view);
}

void
ImageMagick::effect (Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache)
{
    refreshParameters(module);

    if (docCache == NULL) { // should never happen
        docCache = newDocCache(module, document);
    }
    ImageMagickDocCache * dc = dynamic_cast<ImageMagickDocCache *>(docCache);
    if (dc == NULL) { // should really never happen
        printf("AHHHHHHHHH!!!!!");
        std::terminate();
    }

    for (int i = 0; i < dc->_imageCount; i++)
    {
        try
        {
            Magick::Image effectedImage = *dc->_images[i]; // make a copy

            applyEffect(&effectedImage);

            // postEffect can be used to change things on the item itself
            // e.g. resize the image element, after the effecti is applied
            postEffect(&effectedImage, dc->_imageItems[i]);

//    dc->_nodes[i]->setAttribute("xlink:href", dc->_caches[i]);

            Magick::Blob *blob = new Magick::Blob();
            effectedImage.write(blob);

            std::string raw_string = blob->base64();
            const int raw_len = raw_string.length();
            const char *raw_i = raw_string.c_str();

            unsigned new_len = (int)(raw_len * (77.0 / 76.0) + 100);
            if (new_len > dc->_cacheLengths[i]) {
                dc->_cacheLengths[i] = (int)(new_len * 1.2);
                dc->_caches[i] = new char[dc->_cacheLengths[i]];
            }
            char *formatted_i = dc->_caches[i];
            const char *src;

            for (src = "data:image/"; *src; )
                *formatted_i++ = *src++;
            for (src = effectedImage.magick().c_str(); *src ; )
                *formatted_i++ = *src++;
            for (src = ";base64, \n" ; *src; )
                *formatted_i++ = *src++;

            int col = 0;
            while (*raw_i) {
               *formatted_i++ = *raw_i++;
               if (col++ > 76) {
                   *formatted_i++ = '\n';
                   col = 0;
               }
            }            
            if (col) {
               *formatted_i++ = '\n';
            }
            *formatted_i = '\0';

            Inkscape::setHrefAttribute(*dc->_nodes[i], dc->_caches[i]);
            dc->_nodes[i]->removeAttribute("sodipodi:absref");
            delete blob;
        }
        catch (Magick::Exception &error_) {
            printf("Caught exception: %s \n", error_.what());
        }

        //while(Gtk::Main::events_pending()) {
        //    Gtk::Main::iteration();
        //}
    }
}

/** \brief  A function to get the preferences for the grid
    \param  module  Module which holds the params
    \param  view     Unused today - may get style information in the future.

    Uses AutoGUI for creating the GUI.
*/
Gtk::Widget *
ImageMagick::prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View * view, sigc::signal<void ()> * changeSignal, Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/)
{
    SPDocument * current_document = view->doc();

    auto selected = ((SPDesktop *) view)->getSelection()->items();
    Inkscape::XML::Node * first_select = NULL;
    if (!selected.empty()) {
        first_select = (selected.front())->getRepr();
    }

    return module->autogui(current_document, first_select, changeSignal);
}

}; /* namespace Bitmap */
}; /* namespace Internal */
}; /* namespace Extension */
}; /* namespace Inkscape */
