/** \file
 * LPE <envelope-perspective> implementation
 
 */
/*
 * Authors:
 *   Jabiertxof Code migration from python extensions envelope and perspective
 *   Aaron Spike, aaron@ekips.org from envelope and perspective phyton code
 *   Dmitry Platonov, shadowjack@mail.ru, 2006 perspective approach & math
 *   Jose Hevia (freon) Transform algorithm from envelope
 *   
 * Copyright (C) 2007-2014 Authors 
 *
 * Released under GNU GPL, read the file 'COPYING' for more information
 */

#include "live_effects/lpe-envelope-perspective.h"
#include "helper/geom.h"
#include "display/curve.h"
#include "svg/svg.h"
#include <gtkmm/separator.h>
#include <gtkmm/stock.h>
#include <tools-switch.h>
#include <gsl/gsl_linalg.h>
#include "desktop.h"

using namespace Geom;

namespace Inkscape {
namespace LivePathEffect {

LPEEnvelopePerspective::LPEEnvelopePerspective(LivePathEffectObject *lpeobject) :
    Effect(lpeobject),
    // initialise your parameters here:
    perspective(_("Perspective"), _("Perspective"), "Perspective", &wr, this, false),
    Up_Left_Point(_("Top Left:"), _("Top Left - Ctrl+Alt+Click to reset"), "Up_Left_Point", &wr, this),
    Up_Right_Point(_("Top Right:"), _("Top Right - Ctrl+Alt+Click to reset"), "Up_Right_Point", &wr, this),
    Down_Left_Point(_("Down Left:"), _("Down Left - Ctrl+Alt+Click to reset"), "Down_Left_Point", &wr, this),
    Down_Right_Point(_("Down Right:"), _("Down Right - Ctrl+Alt+Click to reset"), "Down_Right_Point", &wr, this)
{
    // register all your parameters here, so Inkscape knows which parameters this effect has:
    registerParameter( dynamic_cast<Parameter *>(&perspective));
    registerParameter( dynamic_cast<Parameter *>(&Up_Left_Point) );
    registerParameter( dynamic_cast<Parameter *>(&Up_Right_Point) );
    registerParameter( dynamic_cast<Parameter *>(&Down_Left_Point) );
    registerParameter( dynamic_cast<Parameter *>(&Down_Right_Point) );
}

LPEEnvelopePerspective::~LPEEnvelopePerspective()
{
}

void LPEEnvelopePerspective::doEffect(SPCurve *curve) {
    using Geom::X;
    using Geom::Y;
    double projmatrix[3][3];
    if(perspective){
        std::vector<Geom::Point> handles(4);
        handles[0] = Down_Left_Point;
        handles[1] = Up_Left_Point;
        handles[2] = Up_Right_Point;
        handles[3] = Down_Right_Point;
        std::vector<Geom::Point> sourceHandles(4);
        sourceHandles[0] = Geom::Point(boundingbox_X.min(), boundingbox_Y.max());
        sourceHandles[1] = Geom::Point(boundingbox_X.min(), boundingbox_Y.min());
        sourceHandles[2] = Geom::Point(boundingbox_X.max(), boundingbox_Y.min());
        sourceHandles[3] = Geom::Point(boundingbox_X.max(), boundingbox_Y.max());
        double solmatrix[8][8] = {{0}};
        double free_term[8] = {0};
        double gslSolmatrix[64];
        for(unsigned int i = 0; i < 4; ++i){
            solmatrix[i][0] = sourceHandles[i][X];
            solmatrix[i][1] = sourceHandles[i][Y];
            solmatrix[i][2] = 1;
            solmatrix[i][6] = -handles[i][X] * sourceHandles[i][X];
            solmatrix[i][7] = -handles[i][X] * sourceHandles[i][Y];
            solmatrix[i+4][3] = sourceHandles[i][X];
            solmatrix[i+4][4] = sourceHandles[i][Y];
            solmatrix[i+4][5] = 1;
            solmatrix[i+4][6] = -handles[i][Y] * sourceHandles[i][X];
            solmatrix[i+4][7] = -handles[i][Y] * sourceHandles[i][Y];
            free_term[i] = handles[i][X];
            free_term[i+4] = handles[i][Y];
        }
        int h = 0;
        for( int i = 0; i < 8; i++ ) {
            for( int j = 0; j < 8; j++ ) {
                gslSolmatrix[h] = solmatrix[i][j];
                h++;
            }
        }
        //this is get by this page:
        //http://www.gnu.org/software/gsl/manual/html_node/Linear-Algebra-Examples.html#Linear-Algebra-Examples
        gsl_matrix_view m = gsl_matrix_view_array (gslSolmatrix, 8, 8);
        gsl_vector_view b = gsl_vector_view_array (free_term, 8);
        gsl_vector *x = gsl_vector_alloc (8);
        int s;
        gsl_permutation * p = gsl_permutation_alloc (8);
        gsl_linalg_LU_decomp (&m.matrix, p, &s);
        gsl_linalg_LU_solve (&m.matrix, p, &b.vector, x);
        h = 0;
        for( int i = 0; i < 3; i++ ) {
            for( int j = 0; j < 3; j++ ) {
                if(h==8){
                    projmatrix[2][2] = 1.0;
                    continue;
                }
                projmatrix[i][j] = gsl_vector_get(x, h);
                h++;
            }
        }
        gsl_permutation_free (p);
        gsl_vector_free (x);
    }
    Geom::PathVector const original_pathv = pathv_to_linear_and_cubic_beziers(curve->get_pathvector());
    curve->reset();
    Geom::CubicBezier const *cubic = NULL;
    Geom::Point pointAt1(0, 0);
    Geom::Point pointAt2(0, 0);
    Geom::Point pointAt3(0, 0);
    for (Geom::PathVector::const_iterator path_it = original_pathv.begin(); path_it != original_pathv.end(); ++path_it) {
        //Si está vacío...
        if (path_it->empty())
          continue;
        //Itreadores
        SPCurve *nCurve = new SPCurve();
        Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve
        Geom::Path::const_iterator curve_it2 =
            ++(path_it->begin());                                // outgoing curve
        Geom::Path::const_iterator curve_endit =
            path_it->end_default(); // this determines when the loop has to stop
        
        if (path_it->closed()) {
          // if the path is closed, maybe we have to stop a bit earlier because the
          // closing line segment has zerolength.
          const Geom::Curve &closingline =
              path_it->back_closed(); // the closing line segment is always of type
                                      // Geom::LineSegment.
          if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
            // closingline.isDegenerate() did not work, because it only checks for
            // *exact* zero length, which goes wrong for relative coordinates and
            // rounding errors...
            // the closing line segment has zero-length. So stop before that one!
            curve_endit = path_it->end_open();
          }
        }
        if(perspective){
            nCurve->moveto(project_point(curve_it1->initialPoint(),projmatrix));
        }else{
            nCurve->moveto(project_point(curve_it1->initialPoint()));
        }
        while (curve_it2 != curve_endit) {
          cubic = dynamic_cast<Geom::CubicBezier const *>(&*curve_it1);
          if (cubic) {
            pointAt1 = (*cubic)[1];
            pointAt2 = (*cubic)[2];
          } else {
            pointAt1 = curve_it1->initialPoint();
            pointAt2 = curve_it1->finalPoint();
          }
          pointAt3 = curve_it1->finalPoint();
          if(perspective){
            pointAt1 = project_point(pointAt1,projmatrix);
            pointAt2 = project_point(pointAt2,projmatrix);
            pointAt3 = project_point(pointAt3,projmatrix);
          }else{
            pointAt1 = project_point(pointAt1);
            pointAt2 = project_point(pointAt2);
            pointAt3 = project_point(pointAt3);
          }
          nCurve->curveto(pointAt1, pointAt2, pointAt3);
          ++curve_it1;
          ++curve_it2;
        }
        cubic = dynamic_cast<Geom::CubicBezier const *>(&*curve_it1);
        if (cubic) {
            pointAt1 = (*cubic)[1];
            pointAt2 = (*cubic)[2];
        } else {
            pointAt1 = curve_it1->initialPoint();
            pointAt2 = curve_it1->finalPoint();
        }
        pointAt3 = curve_it1->finalPoint();
        if(perspective){
            pointAt1 = project_point(pointAt1,projmatrix);
            pointAt2 = project_point(pointAt2,projmatrix);
            pointAt3 = project_point(pointAt3,projmatrix);
        }else{
            pointAt1 = project_point(pointAt1);
            pointAt2 = project_point(pointAt2);
            pointAt3 = project_point(pointAt3);
        }
        nCurve->curveto(pointAt1, pointAt2, pointAt3);
        if(perspective){
            nCurve->move_endpoints(project_point(path_it->begin()->initialPoint(),projmatrix), pointAt3);
        }else{
            nCurve->move_endpoints(project_point(path_it->begin()->initialPoint()), pointAt3);
        }
        //y cerramos la curva
        if (path_it->closed()) {
          nCurve->closepath_current();
        }
        curve->append(nCurve, false);
        nCurve->reset();
        delete nCurve;
    }
}

Geom::Point 
LPEEnvelopePerspective::project_point(Geom::Point p){
    double width = boundingbox_X.extent();
    double height = boundingbox_Y.extent();
    Geom::Coord xratio = abs(Geom::Point(boundingbox_X.min(), boundingbox_Y.max())[X]-p[X])/width;
    Geom::Coord yratio = abs(Geom::Point(boundingbox_X.min(), boundingbox_Y.max())[Y]-p[Y])/height;
    Geom::Line* horiz = new Geom::Line();
    Geom::Line* vert = new Geom::Line();
    vert->setPoints (pointAtRatio(yratio,Down_Left_Point,Up_Left_Point),pointAtRatio(yratio,Down_Right_Point,Up_Right_Point));
    horiz->setPoints (pointAtRatio(xratio,Down_Left_Point,Down_Right_Point),pointAtRatio(xratio,Up_Left_Point,Up_Right_Point));

    OptCrossing crossPoint = intersection(*horiz,*vert);
    if(crossPoint){
        return horiz->pointAt(Geom::Coord(crossPoint->ta));
    }else{
        return p;
    }
}

Geom::Point 
LPEEnvelopePerspective::project_point(Geom::Point p, double m[][3]){
    Geom::Coord x = p[0];
    Geom::Coord y = p[1];
    return Geom::Point(
                Geom::Coord((x*m[0][0] + y*m[0][1] + m[0][2])/(x*m[2][0]+y*m[2][1]+m[2][2])),
                Geom::Coord((x*m[1][0] + y*m[1][1] + m[1][2])/(x*m[2][0]+y*m[2][1]+m[2][2])));
}

Geom::Point
LPEEnvelopePerspective::pointAtRatio(Geom::Coord ratio,Geom::Point A, Geom::Point B){
        Geom::Coord x = A[X] + (ratio * (B[X]-A[X]));
        Geom::Coord y = A[Y]+ (ratio * (B[Y]-A[Y]));
        return Point(x, y);
}


Gtk::Widget *
LPEEnvelopePerspective::newWidget()
{
    // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
    Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox(Effect::newWidget()) );

    vbox->set_border_width(5);
    vbox->set_homogeneous(false);
    vbox->set_spacing(6);
    Gtk::HBox * hbox = Gtk::manage(new Gtk::HBox(false,0));

    Gtk::Button* resetButton = Gtk::manage(new Gtk::Button(Gtk::Stock::CLEAR));
    resetButton->signal_clicked().connect(sigc::mem_fun (*this,&LPEEnvelopePerspective::resetGrid));
    resetButton->set_size_request(140,45);
    vbox->pack_start(*hbox, true,true,2);
    hbox->pack_start(*resetButton, false, false,2);
    std::vector<Parameter *>::iterator it = param_vector.begin();
    Gtk::HBox * hboxUpHandles = Gtk::manage(new Gtk::HBox(false,0));
    Gtk::HBox * hboxDownHandles = Gtk::manage(new Gtk::HBox(false,0));
    while (it != param_vector.end()) {
        if ((*it)->widget_is_visible) {
            Parameter * param = *it;
            Gtk::Widget * widg = dynamic_cast<Gtk::Widget *>(param->param_newWidget());
            if (param->param_key == "Up_Left_Point" ||
                param->param_key == "Up_Right_Point" ||
                param->param_key == "Down_Left_Point" ||
                param->param_key == "Down_Right_Point")
            {
                Gtk::HBox * pointParameter = dynamic_cast<Gtk::HBox *>(widg);
                std::vector< Gtk::Widget* > childList = pointParameter->get_children();
                Gtk::HBox * pointParameterHBox = dynamic_cast<Gtk::HBox *>(childList[0]);
                std::vector< Gtk::Widget* > childList2 = pointParameterHBox->get_children();
                pointParameterHBox->remove(childList2[0][0]);
                Glib::ustring * tip = param->param_getTooltip();
                if (widg) {
                    if(param->param_key == "Up_Left_Point"){
                        Gtk::Label* handles = Gtk::manage(new Gtk::Label(Glib::ustring(_("Handles:")),Gtk::ALIGN_START));
                        vbox->pack_start(*handles, false, false, 2);
                        hboxUpHandles->pack_start(*widg, true, true, 2);
                        hboxUpHandles->pack_start(*Gtk::manage(new Gtk::VSeparator()), Gtk::PACK_EXPAND_WIDGET);
                    }else if(param->param_key == "Up_Right_Point"){
                        hboxUpHandles->pack_start(*widg, true, true, 2);
                    }else if(param->param_key == "Down_Left_Point"){
                        hboxDownHandles->pack_start(*widg, true, true, 2);
                        hboxDownHandles->pack_start(*Gtk::manage(new Gtk::VSeparator()), Gtk::PACK_EXPAND_WIDGET);
                    }else{
                        hboxDownHandles->pack_start(*widg, true, true, 2);
                    }
                    if (tip) {
                        widg->set_tooltip_text(*tip);
                    } else {
                        widg->set_tooltip_text("");
                        widg->set_has_tooltip(false);
                    }
                }
            }else{
                Glib::ustring * tip = param->param_getTooltip();
                if (widg) {
                    vbox->pack_start(*widg, true, true, 2);
                    if (tip) {
                        widg->set_tooltip_text(*tip);
                    } else {
                        widg->set_tooltip_text("");
                        widg->set_has_tooltip(false);
                    }
                }
            }
        }

        ++it;
    }
    vbox->pack_start(*hboxUpHandles,true, true, 2);
    Gtk::HBox * hboxMiddle = Gtk::manage(new Gtk::HBox(true,2));
    hboxMiddle->pack_start(*Gtk::manage(new Gtk::HSeparator()), Gtk::PACK_EXPAND_WIDGET);
    hboxMiddle->pack_start(*Gtk::manage(new Gtk::HSeparator()), Gtk::PACK_EXPAND_WIDGET);
    vbox->pack_start(*hboxMiddle, false, true, 2);
    vbox->pack_start(*hboxDownHandles, true, true, 2);
    return dynamic_cast<Gtk::Widget *>(vbox);
}

void
LPEEnvelopePerspective::doBeforeEffect (SPLPEItem const* lpeitem)
{
    original_bbox(lpeitem);
    setDefaults();
}

void
LPEEnvelopePerspective::setDefaults()
{
    Geom::Point Up_Left(boundingbox_X.min(), boundingbox_Y.min());
    Geom::Point Up_Right(boundingbox_X.max(), boundingbox_Y.min());
    Geom::Point Down_Left(boundingbox_X.min(), boundingbox_Y.max());
    Geom::Point Down_Right(boundingbox_X.max(), boundingbox_Y.max());

    Up_Left_Point.param_update_default(Up_Left);
    Up_Right_Point.param_update_default(Up_Right);
    Down_Right_Point.param_update_default(Down_Right);
    Down_Left_Point.param_update_default(Down_Left);
}

void
LPEEnvelopePerspective::resetGrid()
{
    Up_Left_Point.param_set_and_write_default();
    Up_Right_Point.param_set_and_write_default();
    Down_Right_Point.param_set_and_write_default();
    Down_Left_Point.param_set_and_write_default();
    //todo:this hack is only to reposition the knots on reser grid button
    //Better update path effect in LPEITEM
    SPDesktop * desktop = inkscape_active_desktop();
    tools_switch(desktop, TOOLS_SELECT);
    tools_switch(desktop, TOOLS_NODES);
}

void
LPEEnvelopePerspective::resetDefaults(SPItem const* item)
{
    Effect::resetDefaults(item);
    original_bbox(SP_LPE_ITEM(item));
    setDefaults();
    resetGrid();
}

void
LPEEnvelopePerspective::calculateCurve(Geom::Point a,Geom::Point b, SPCurve* c, bool horizontal, bool move)
{
    using Geom::X;
    using Geom::Y;
    if(move) c->moveto(a);
    Geom::Point cubic1 = a + (1./3)* (b - a);
    Geom::Point cubic2 = b + (1./3)* (a - b);
    if(horizontal) c->curveto(Geom::Point(cubic1[X],a[Y]),Geom::Point(cubic2[X],b[Y]),b);
    else c->curveto(Geom::Point(a[X],cubic1[Y]),Geom::Point(b[X],cubic2[Y]),b);
}

void
LPEEnvelopePerspective::addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec)
{
    hp_vec.clear();

    SPCurve *c = new SPCurve();
    c->reset();
    c->moveto(Up_Left_Point);
    c->lineto(Up_Right_Point);
    c->lineto(Down_Right_Point);
    c->lineto(Down_Left_Point);
    c->lineto(Up_Left_Point);
    hp_vec.push_back(c->get_pathvector());
}


/* ######################## */

} //namespace LivePathEffect
} /* 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 :
