/**
 * @file
 * Pixel art tracing settings dialog - implementation.
 */
/* Authors:
 *   Bob Jamison <rjamison@titan.com>
 *   Stéphane Gimenez <dev@gim.name>
 *   Vinícius dos Santos Oliveira <vini.ipsmaker@gmail.com>
 *   Other dudes from The Inkscape Organization
 *
 * Copyright (C) 2004-2013 Authors
 *
 * Released under GNU GPL, read the file 'COPYING' for more information
 */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#ifdef GLIBMM_DISABLE_DEPRECATED
#  undef GLIBMM_DISABLE_DEPRECATED
#  include <glibmm/thread.h>
#  include <glibmm/dispatcher.h>
#  define GLIBMM_DISABLE_DEPRECATED 1
#else // GLIBMM_DISABLE_DEPRECATED
#  include <glibmm/thread.h>
#  include <glibmm/dispatcher.h>
#endif // GLIBMM_DISABLE_DEPRECATED

#include "pixelartdialog.h"
#include <gtkmm/radiobutton.h>
#include <gtkmm/stock.h>
#include <gtkmm/messagedialog.h>

#include <gtk/gtk.h> //for GTK_RESPONSE* types
#include <glibmm/i18n.h>

#include "ui/widget/spinbutton.h"
#include "ui/widget/frame.h"

#include "desktop.h"
#include "desktop-tracker.h"
#include "message-stack.h"
#include "selection.h"
#include "preferences.h"

#include "sp-image.h"
#include "display/cairo-utils.h"
#include "libdepixelize/kopftracer2011.h"
#include <algorithm>
#include "document.h"
#include "xml/repr.h"
#include "xml/document.h"
#include "svg/svg.h"
#include "svg/svg-color.h"
#include "color.h"
#include "svg/css-ostringstream.h"
#include "document-undo.h"

#ifdef HAVE_OPENMP
#include <omp.h>
#endif // HAVE_OPENMP

namespace Inkscape {
namespace UI {
namespace Dialog {

/**
 * A dialog for adjusting pixel art -> vector tracing parameters
 */
class PixelArtDialogImpl : public PixelArtDialog
{
public:
    PixelArtDialogImpl();

    ~PixelArtDialogImpl();

private:
    struct Input
    {
        Glib::RefPtr<Gdk::Pixbuf> pixbuf;
        SVGLength x;
        SVGLength y;
    };
    struct Output
    {
        Output(Tracer::Splines splines, SVGLength x, SVGLength y) :
            splines(splines), x(x), y(y)
        {}

        Tracer::Splines splines;
        SVGLength x;
        SVGLength y;
    };

    void setDesktop(SPDesktop *desktop);
    void setTargetDesktop(SPDesktop *desktop);

    //############ Events

    void responseCallback(int response_id);

    //############ UI Logic

    Tracer::Kopf2011::Options options();

    void vectorize();
    void processLibdepixelize(const Input &image);
    void importOutput(const Output &out);
    void setDefaults();
    void updatePreview();

    bool ignorePreview;
    bool pendingPreview;

    //############ UI

    Gtk::HBox             buttonsHBox;

    Gtk::Button           *mainOkButton;
    Gtk::Button           *mainCancelButton;
    Gtk::Button           *mainResetButton;

    Gtk::VBox         heuristicsVBox;
    UI::Widget::Frame heuristicsFrame;

    Gtk::HBox                        curvesMultiplierHBox;
    Gtk::Label                       curvesMultiplierLabel;
    Inkscape::UI::Widget::SpinButton curvesMultiplierSpinner;

    Gtk::HBox                        islandsWeightHBox;
    Gtk::Label                       islandsWeightLabel;
    Inkscape::UI::Widget::SpinButton islandsWeightSpinner;

    Gtk::HBox                        sparsePixelsMultiplierHBox;
    Gtk::Label                       sparsePixelsMultiplierLabel;
    Inkscape::UI::Widget::SpinButton sparsePixelsMultiplierSpinner;

    Gtk::HBox                        sparsePixelsRadiusHBox;
    Gtk::Label                       sparsePixelsRadiusLabel;
    Inkscape::UI::Widget::SpinButton sparsePixelsRadiusSpinner;

    Gtk::VBox             outputVBox;
    UI::Widget::Frame     outputFrame;
    Gtk::RadioButtonGroup outputGroup;

    Gtk::RadioButton voronoiRadioButton;
    Gtk::RadioButton noOptimizeRadioButton;
#if LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH
    Gtk::RadioButton optimizeRadioButton;
#endif // LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH

    //############ UI Logic data

    SPDesktop *desktop;
    DesktopTracker deskTrack;
    sigc::connection desktopChangeConn;

    //############ Threads
    void workerThread();
    void onWorkerThreadFinished();
    Glib::Thread *thread;
    volatile gint abortThread; // C++11's atomic stuff is sooo much nicer
    Glib::Dispatcher dispatcher;
    std::vector<Input> queue;
    std::vector<Output> output;
    Tracer::Kopf2011::Options lastOptions;
};

void PixelArtDialogImpl::setDesktop(SPDesktop *desktop)
{
    Panel::setDesktop(desktop);
    deskTrack.setBase(desktop);
}

void PixelArtDialogImpl::setTargetDesktop(SPDesktop *desktop)
{
    this->desktop = desktop;
}

PixelArtDialogImpl::PixelArtDialogImpl() :
    PixelArtDialog(),
    ignorePreview(false),
    pendingPreview(false)
{

    Gtk::Box *contents = _getContents();

    // Heuristics
    {
        curvesMultiplierLabel.set_label(_("_Curves (multiplier):"));
        curvesMultiplierLabel.set_use_underline(true);
        curvesMultiplierLabel.set_mnemonic_widget(curvesMultiplierSpinner);
        curvesMultiplierLabel.set_tooltip_text(_("Favors connections that are part of a long curve"));
        curvesMultiplierSpinner.set_increments(0.125, 0);
        curvesMultiplierSpinner.set_digits(3);
        curvesMultiplierSpinner.set_range(-10, 10);
        curvesMultiplierSpinner.get_adjustment()->signal_value_changed()
            .connect(sigc::mem_fun(*this, &PixelArtDialogImpl::updatePreview));

        curvesMultiplierHBox.pack_start(curvesMultiplierLabel, false, false);
        curvesMultiplierHBox.pack_end(curvesMultiplierSpinner, false, false);
        heuristicsVBox.pack_start(curvesMultiplierHBox, false, false);

        islandsWeightLabel.set_label(_("_Islands (weight):"));
        islandsWeightLabel.set_use_underline(true);
        islandsWeightLabel.set_mnemonic_widget(islandsWeightSpinner);
        islandsWeightLabel.set_tooltip_text(_("Avoid single disconnected pixels"));

        islandsWeightSpinner.set_tooltip_text(_("A constant vote value"));
        islandsWeightSpinner.set_increments(1, 0);
        islandsWeightSpinner.set_range(-20, 20);
        islandsWeightSpinner.get_adjustment()->signal_value_changed()
            .connect(sigc::mem_fun(*this, &PixelArtDialogImpl::updatePreview));

        islandsWeightHBox.pack_start(islandsWeightLabel, false, false);
        islandsWeightHBox.pack_end(islandsWeightSpinner, false, false);
        heuristicsVBox.pack_start(islandsWeightHBox, false, false);

        sparsePixelsRadiusLabel.set_label(_("Sparse pixels (window _radius):"));
        sparsePixelsRadiusLabel.set_use_underline(true);
        sparsePixelsRadiusLabel.set_mnemonic_widget(sparsePixelsRadiusSpinner);

        sparsePixelsRadiusSpinner.set_increments(1, 0);
        sparsePixelsRadiusSpinner.set_range(2, 8);
        sparsePixelsRadiusSpinner.get_adjustment()->signal_value_changed()
            .connect(sigc::mem_fun(*this, &PixelArtDialogImpl::updatePreview));

        sparsePixelsRadiusSpinner.set_tooltip_text(_("The radius of the window analyzed"));
        sparsePixelsMultiplierLabel.set_label(_("Sparse pixels (_multiplier):"));
        sparsePixelsMultiplierLabel.set_use_underline(true);
        sparsePixelsMultiplierLabel.set_mnemonic_widget(sparsePixelsMultiplierSpinner);

        sparsePixelsMultiplierSpinner.set_increments(0.125, 0);
        sparsePixelsMultiplierSpinner.set_digits(3);
        sparsePixelsMultiplierSpinner.set_range(-10, 10);
        sparsePixelsMultiplierSpinner.get_adjustment()->signal_value_changed()
            .connect(sigc::mem_fun(*this, &PixelArtDialogImpl::updatePreview));

        {
            char const *str = _("Favors connections that are part of foreground color");
            sparsePixelsRadiusLabel.set_tooltip_text(str);
            sparsePixelsMultiplierLabel.set_tooltip_text(str);
        }

        {
            char const *str = _("The heuristic computed vote will be multiplied by this value");
            curvesMultiplierSpinner.set_tooltip_text(str);
            sparsePixelsMultiplierSpinner.set_tooltip_text(str);
        }

        sparsePixelsRadiusHBox.pack_start(sparsePixelsRadiusLabel, false, false);
        sparsePixelsRadiusHBox.pack_end(sparsePixelsRadiusSpinner, false, false);
        heuristicsVBox.pack_start(sparsePixelsRadiusHBox, false, false);

        sparsePixelsMultiplierHBox.pack_start(sparsePixelsMultiplierLabel, false, false);
        sparsePixelsMultiplierHBox.pack_end(sparsePixelsMultiplierSpinner, false, false);
        heuristicsVBox.pack_start(sparsePixelsMultiplierHBox, false, false);

        heuristicsFrame.set_label(_("Heuristics"));
        heuristicsFrame.add(heuristicsVBox);
        contents->pack_start(heuristicsFrame, false, false);
    }

    // Output
    {
        voronoiRadioButton.set_label(_("_Voronoi diagram"));
        voronoiRadioButton.set_tooltip_text(_("Output composed of straight lines"));
        voronoiRadioButton.set_use_underline(true);
        outputGroup = voronoiRadioButton.get_group();

        outputVBox.pack_start(voronoiRadioButton, false, false);

        noOptimizeRadioButton.set_label(_("Convert to _B-spline curves"));
        noOptimizeRadioButton.set_tooltip_text(_("Preserve staircasing artifacts"));
        noOptimizeRadioButton.set_use_underline(true);
        noOptimizeRadioButton.set_group(outputGroup);

        outputVBox.pack_start(noOptimizeRadioButton, false, false);

#if LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH
        optimizeRadioButton.set_label(_("_Smooth curves"));
        optimizeRadioButton.set_tooltip_text(_("The Kopf-Lischinski algorithm"));
        optimizeRadioButton.set_use_underline(true);
        optimizeRadioButton.set_group(outputGroup);

        outputVBox.pack_start(optimizeRadioButton, false, false);
#endif // LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH

        outputFrame.set_label(_("Output"));
        outputFrame.add(outputVBox);
        contents->pack_start(outputFrame, true, false);
    }

    // Buttons
    {
        mainResetButton = addResponseButton(_("Reset"), GTK_RESPONSE_HELP, true);
        mainResetButton ->set_tooltip_text(_("Reset all settings to defaults"));

        //## The OK button
        mainCancelButton = addResponseButton(Gtk::Stock::STOP, GTK_RESPONSE_CANCEL);
        if (mainCancelButton) {
            mainCancelButton->set_tooltip_text(_("Abort a trace in progress"));
            mainCancelButton->set_sensitive(false);
        }
        mainOkButton = addResponseButton(Gtk::Stock::OK, GTK_RESPONSE_OK);
        mainOkButton->set_tooltip_text(_("Execute the trace"));

        contents->pack_start(buttonsHBox);
    }

    setDefaults();

    show_all_children();

    desktopChangeConn = deskTrack.connectDesktopChanged( sigc::mem_fun(*this, &PixelArtDialogImpl::setTargetDesktop) );
    deskTrack.connect(GTK_WIDGET(gobj()));

    signalResponse().connect(sigc::mem_fun(*this, &PixelArtDialogImpl::responseCallback));

    dispatcher.connect(
        sigc::mem_fun(*this, &PixelArtDialogImpl::onWorkerThreadFinished) );
}

void PixelArtDialogImpl::responseCallback(int response_id)
{
    if (response_id == GTK_RESPONSE_OK) {
        vectorize();
    } else if (response_id == GTK_RESPONSE_CANCEL) {
        // libdepixelize's interface need to be extended to allow aborts
        g_atomic_int_set(&abortThread, true);
    } else if (response_id == GTK_RESPONSE_HELP) {
        setDefaults();
    } else {
        hide();
        return;
    }
}

Tracer::Kopf2011::Options PixelArtDialogImpl::options()
{
    Tracer::Kopf2011::Options options;

    options.curvesMultiplier = curvesMultiplierSpinner.get_value();
    options.islandsWeight = islandsWeightSpinner.get_value_as_int();
    options.sparsePixelsMultiplier = sparsePixelsMultiplierSpinner.get_value();
    options.sparsePixelsRadius = sparsePixelsRadiusSpinner.get_value_as_int();
#if LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH
    options.optimize = optimizeRadioButton.get_active();
#else // LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH
    options.optimize = false;
#endif // LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH

    options.nthreads = Inkscape::Preferences::get()
        ->getIntLimited("/options/threading/numthreads",
#ifdef HAVE_OPENMP
                        omp_get_num_procs(),
#else
                        1,
#endif // HAVE_OPENMP
                        1, 256);

    return options;
}

void PixelArtDialogImpl::vectorize()
{
    Inkscape::MessageStack *msgStack = desktop->messageStack();

    if ( !desktop->selection ) {
        char *msg = _("Select an <b>image</b> to trace");
        msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
        return;
    }

    for ( GSList const *list = desktop->selection->itemList() ; list
              ; list = list->next ) {
        if ( !SP_IS_IMAGE(list->data) )
            continue;

        SPImage *img = SP_IMAGE(list->data);
        Input input;
        input.pixbuf = Glib::wrap(img->pixbuf->getPixbufRaw(), true);
        input.x = img->x;
        input.y = img->y;

        if ( input.pixbuf->get_width() > 256
             || input.pixbuf->get_height() > 256 ) {
            char *msg = _("Image looks too big. Process may take a while and it is"
                          " wise to save your document before continuing."
                          "\n\nContinue the procedure (without saving)?");
            Gtk::MessageDialog dialog(msg, false, Gtk::MESSAGE_WARNING,
                                      Gtk::BUTTONS_OK_CANCEL, true);

            if ( dialog.run() != Gtk::RESPONSE_OK )
                continue;
        }

        queue.push_back(input);
    }

    if ( !queue.size() ) {
        char *msg = _("Select an <b>image</b> to trace");
        msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
        return;
    }

    mainCancelButton->set_sensitive(true);
    mainOkButton->set_sensitive(false);

    lastOptions = options();

    g_atomic_int_set(&abortThread, false);
    thread = Glib::Thread::create(sigc::mem_fun(*this, &PixelArtDialogImpl::workerThread),
                                  /*joinable =*/true);
}

void PixelArtDialogImpl::processLibdepixelize(const Input &input)
{
    Tracer::Splines out;

    if ( input.pixbuf->get_width() > 256 || input.pixbuf->get_height() > 256 ) {
        char *msg = _("Image looks too big. Process may take a while and it is"
                      " wise to save your document before continuing."
                      "\n\nContinue the procedure (without saving)?");
        Gtk::MessageDialog dialog(msg, false, Gtk::MESSAGE_WARNING,
                                  Gtk::BUTTONS_OK_CANCEL, true);

        if ( dialog.run() != Gtk::RESPONSE_OK )
            return;
    }

    if ( voronoiRadioButton.get_active() ) {
        output.push_back(Output(Tracer::Kopf2011::to_voronoi(input.pixbuf,
                                                             lastOptions),
                                input.x, input.y));
    } else {
        output.push_back(Output(Tracer::Kopf2011::to_splines(input.pixbuf,
                                                             lastOptions),
                                input.x, input.y));
    }
}

void PixelArtDialogImpl::importOutput(const Output &output)
{
    Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
    Inkscape::XML::Node *group = xml_doc->createElement("svg:g");

    for ( Tracer::Splines::const_iterator it = output.splines.begin(),
              end = output.splines.end() ; it != end ; ++it ) {
        Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");

        {
            SPCSSAttr *css = sp_repr_css_attr_new();

            {
                gchar b[64];
                sp_svg_write_color(b, sizeof(b),
                                   SP_RGBA32_U_COMPOSE(unsigned(it->rgba[0]),
                                                       unsigned(it->rgba[1]),
                                                       unsigned(it->rgba[2]),
                                                       unsigned(it->rgba[3])));

                sp_repr_css_set_property(css, "fill", b);
            }

            {
                Inkscape::CSSOStringStream osalpha;
                osalpha << float(it->rgba[3]) / 255.;
                sp_repr_css_set_property(css, "fill-opacity",
                                         osalpha.str().c_str());
            }

            sp_repr_css_set(repr, css, "style");
            sp_repr_css_attr_unref(css);
        }

        gchar *str = sp_svg_write_path(it->pathVector);
        repr->setAttribute("d", str);
        g_free(str);

        group->appendChild(repr);

        Inkscape::GC::release(repr);
    }

    {
        group->setAttribute("transform",
                            (std::string("translate(")
                             + sp_svg_length_write_with_units(output.x)
                             + ' ' + sp_svg_length_write_with_units(output.y)
                             + ')').c_str());
    }

    desktop->currentLayer()->appendChildRepr(group);

    Inkscape::GC::release(group);

    DocumentUndo::done(desktop->doc(), SP_VERB_SELECTION_PIXEL_ART,
                       _("Trace pixel art"));

    // Flush pending updates
    desktop->doc()->ensureUpToDate();
}

void PixelArtDialogImpl::setDefaults()
{
    ignorePreview = true;

    curvesMultiplierSpinner.set_value(Tracer::Kopf2011::Options
                                      ::CURVES_MULTIPLIER);

    islandsWeightSpinner.set_value(Tracer::Kopf2011::Options::ISLANDS_WEIGHT);

    sparsePixelsRadiusSpinner.set_value(Tracer::Kopf2011::Options
                                        ::SPARSE_PIXELS_RADIUS);

    sparsePixelsMultiplierSpinner.set_value(Tracer::Kopf2011::Options
                                            ::SPARSE_PIXELS_MULTIPLIER);

#if LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH
    optimizeRadioButton.set_active();
#else // LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH
    noOptimizeRadioButton.set_active();
#endif // LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH

    ignorePreview = false;

    if ( pendingPreview )
        updatePreview();
}

void PixelArtDialogImpl::updatePreview()
{
    if ( ignorePreview ) {
        pendingPreview = true;
        return;
    }

    // TODO: update preview
    pendingPreview = false;
}

void PixelArtDialogImpl::workerThread()
{
    for ( std::vector<Input>::iterator it = queue.begin(), end = queue.end()
              ; it != end && !g_atomic_int_get(&abortThread) ; ++it ) {
        processLibdepixelize(*it);
    }
    queue.clear();
    dispatcher();
}

void PixelArtDialogImpl::onWorkerThreadFinished()
{
    thread->join();
    thread = NULL;
    for ( std::vector<Output>::const_iterator it = output.begin(),
              end = output.end() ; it != end ; ++it ) {
        importOutput(*it);
    }
    output.clear();
    mainCancelButton->set_sensitive(false);
    mainOkButton->set_sensitive(true);
}

/**
 * Factory method.  Use this to create a new PixelArtDialog
 */
PixelArtDialog &PixelArtDialog::getInstance()
{
    PixelArtDialog *dialog = new PixelArtDialogImpl();
    return *dialog;
}

PixelArtDialogImpl::~PixelArtDialogImpl()
{
    desktopChangeConn.disconnect();
}


} //namespace Dialog
} //namespace UI
} //namespace Inkscape

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
