/*
 *  kis_tool_bezier.cpp -- part of Chalk
 *
 *  Copyright (c) 2006 Emanuele Tamponi <emanuele@valinor.it>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <math.h>

#include <tqpainter.h>
#include <tqlayout.h>
#include <tqrect.h>

#include <tdeaction.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <kdebug.h>
#include <knuminput.h>

#include "kis_global.h"
#include "kis_doc.h"
#include "kis_painter.h"
#include "kis_point.h"
#include "kis_canvas_subject.h"
#include "kis_canvas_controller.h"
#include "kis_canvas.h"
#include "kis_canvas_painter.h"
#include "kis_cursor.h"
#include "kis_vec.h"

#include "kis_curve_framework.h"
#include "kis_tool_bezier.h"

KisCurve::iterator KisCurveBezier::groupEndpoint (KisCurve::iterator it) const
{
    iterator temp = it;
    if ((*it).hint() == BEZIERNEXTCONTROLHINT)
        temp -= 1;
    if ((*it).hint() == BEZIERPREVCONTROLHINT)
        temp += 1;
    return temp;
}

KisCurve::iterator KisCurveBezier::groupPrevControl (KisCurve::iterator it) const
{
    iterator temp = it;
    if ((*it).hint() == BEZIERENDHINT)
        temp -= 1;
    if ((*it).hint() == BEZIERNEXTCONTROLHINT)
        temp -= 2;
    return temp;
}

KisCurve::iterator KisCurveBezier::groupNextControl (KisCurve::iterator it) const
{
    iterator temp = it;
    if ((*it).hint() == BEZIERENDHINT)
        temp += 1;
    if ((*it).hint() == BEZIERPREVCONTROLHINT)
        temp += 2;
    return temp;
}

bool KisCurveBezier::groupSelected (KisCurve::iterator it) const
{
    if ((*groupPrevControl(it)).isSelected() || (*groupEndpoint(it)).isSelected() || (*groupNextControl(it)).isSelected())
        return true;
    return false;
}

KisCurve::iterator KisCurveBezier::nextGroupEndpoint (KisCurve::iterator it) const
{
    iterator temp = it;
    if ((*it).hint() == BEZIERPREVCONTROLHINT) {
        temp += 2;
        temp = temp.nextPivot();
    }
    if ((*it).hint() == BEZIERENDHINT) {
        temp += 1;
        temp = temp.nextPivot();
    }
    if ((*it).hint() == BEZIERNEXTCONTROLHINT) {
        temp = temp.nextPivot();
    }
    temp = temp.nextPivot();
    return temp;
}

KisCurve::iterator KisCurveBezier::prevGroupEndpoint (KisCurve::iterator it) const
{
    iterator temp = it;
    if ((*it).hint() == BEZIERNEXTCONTROLHINT) {
        temp -= 1;
        temp = temp.previousPivot().previousPivot();
    }
    if ((*it).hint() == BEZIERENDHINT) {
        temp = temp.previousPivot().previousPivot();
    }
    if ((*it).hint() == BEZIERPREVCONTROLHINT) {
        temp = temp.previousPivot();
    }
    temp = temp.previousPivot();
    return temp;
}

KisPoint KisCurveBezier::midpoint (const KisPoint& P1, const KisPoint& P2)
{
    KisPoint temp;
    temp.setX((P1.x()+P2.x())/2);
    temp.setY((P1.y()+P2.y())/2);
    return temp;
}

void KisCurveBezier::recursiveCurve (const KisPoint& P1, const KisPoint& P2, const KisPoint& P3,
                                     const KisPoint& P4, int level, KisCurve::iterator it)
{
    if (level > m_maxLevel) {
        addPoint(it,midpoint(P1,P4),false,false,LINEHINT);
        return;
    }

    KisPoint L1, L2, L3, L4;
    KisPoint H, R1, R2, R3, R4;

    L1 = P1;
    L2 = midpoint(P1, P2);
    H  = midpoint(P2, P3);
    R3 = midpoint(P3, P4);
    R4 = P4;
    L3 = midpoint(L2, H);
    R2 = midpoint(R3, H);
    L4 = midpoint(L3, R2);
    R1 = L4;
    recursiveCurve(L1, L2, L3, L4, level + 1, it);
    recursiveCurve(R1, R2, R3, R4, level + 1, it);
}

void KisCurveBezier::calculateCurve(KisCurve::iterator tstart, KisCurve::iterator tend, KisCurve::iterator)
{
    if (pivots().count() < 4)
        return;

    iterator origin, dest, control1, control2;

    if ((*tstart).hint() == BEZIERENDHINT) {
        origin = tstart;
        control1 = tstart.nextPivot();
    } else if ((*tstart).hint() == BEZIERNEXTCONTROLHINT) {
        origin = tstart.previousPivot();
        control1 = tstart;
    } else if ((*tstart).hint() == BEZIERPREVCONTROLHINT) {
        origin = tstart.nextPivot();
        control1 = origin.nextPivot();
    } else
        return;
        
    if ((*tend).hint() == BEZIERENDHINT) {
        dest = tend;
        control2 = tend.previousPivot();
    } else if ((*tend).hint() == BEZIERPREVCONTROLHINT) {
        dest = tend.nextPivot();
        control2 = tend;
    } else if ((*tend).hint() == BEZIERNEXTCONTROLHINT) {
        dest = tend.previousPivot();
        control2 = dest.previousPivot();
    } else
        return;

    deleteCurve(control1,control2);
    recursiveCurve((*origin).point(),(*control1).point(),(*control2).point(),(*dest).point(),1,control2);
    
}

KisCurve::iterator KisCurveBezier::pushPivot (const KisPoint& point)
{
    iterator it;

    it = pushPoint(point,true,false,BEZIERENDHINT);
    if (count() > 1)
        addPoint(it,point,true,false,BEZIERPREVCONTROLHINT);
    
    it = pushPoint(point,true,false,BEZIERNEXTCONTROLHINT);
    
    return selectPivot(it);
}

KisCurve::iterator KisCurveBezier::movePivot(KisCurve::iterator it, const KisPoint& newPt)
{
    if (!(*it).isPivot())
        return end();

    int hint = (*it).hint();
    iterator thisEnd, prevEnd, nextEnd;

    thisEnd = groupEndpoint(it);
    prevEnd = prevGroupEndpoint(it);
    nextEnd = nextGroupEndpoint(it);

    if (hint == BEZIERENDHINT) {
        KisPoint trans = newPt - (*it).point();
        (*thisEnd).setPoint((*thisEnd).point()+trans);
        (*thisEnd.previous()).setPoint((*thisEnd.previous()).point()+trans);
        (*thisEnd.next()).setPoint((*thisEnd.next()).point()+trans);
    } else if (!(m_actionOptions & KEEPSELECTEDOPTION))
        (*it).setPoint(newPt);
    if (!(m_actionOptions & KEEPSELECTEDOPTION) && hint != BEZIERENDHINT) {
        if (nextEnd == end() || (m_actionOptions & SYMMETRICALCONTROLSOPTION)) {
            KisPoint trans = (*it).point() - (*thisEnd).point();
            trans = KisPoint(-trans.x()*2,-trans.y()*2);
            if (hint == BEZIERNEXTCONTROLHINT)
                (*groupPrevControl(it)).setPoint(newPt+trans);
            else
                (*groupNextControl(it)).setPoint(newPt+trans);
        }
    }
    
    if (nextEnd != end() && count() > 4)
        calculateCurve (thisEnd,nextEnd,iterator());
    if (prevEnd != thisEnd && count() > 4)
        calculateCurve (prevEnd,thisEnd,iterator());

    return it;
}

void KisCurveBezier::deletePivot (KisCurve::iterator it)
{
    if (!(*it).isPivot())
        return;

    iterator prevControl,thisEnd,nextControl;

    prevControl = prevGroupEndpoint(it).nextPivot();
    thisEnd = groupEndpoint(it);
    nextControl = nextGroupEndpoint(it).previousPivot();

    if ((*thisEnd) == first()) {
        deleteFirstPivot();
        deleteFirstPivot();
        deleteFirstPivot();
    } else if ((*thisEnd.next()) == last()) {
        deleteLastPivot();
        deleteLastPivot();
        deleteLastPivot();
    } else {
        deleteCurve(prevControl,nextControl);
        calculateCurve(prevControl,nextControl,iterator());
    }
}

KisToolBezier::KisToolBezier(const TQString& UIName)
    : super(UIName)
{
    m_derivated = new KisCurveBezier;
    m_curve = m_derivated;

    m_supportMinimalDraw = false;

    m_transactionMessage = i18n("Bezier Curve");
}

KisToolBezier::~KisToolBezier()
{

}

KisCurve::iterator KisToolBezier::handleUnderMouse(const TQPoint& pos)
{
    TQPoint qpos;
    KisCurve pivs = m_curve->pivots(), inHandle;
    KisCurve::iterator it;
    int hint;
    for (it = pivs.begin(); it != pivs.end(); it++) {
        qpos = m_subject->canvasController()->windowToView((*it).point().toTQPoint());
        hint = (*it).hint();
        if (hint != BEZIERENDHINT && !m_derivated->groupSelected(it))
            continue;
        if (hint == BEZIERENDHINT && (m_actionOptions & SHIFTOPTION))
            continue;
        if (pivotRect(qpos).contains(pos)) {
            inHandle.pushPoint((*it));
            if (hint == BEZIERENDHINT && !(m_actionOptions & SHIFTOPTION))
                break;
            if (hint != BEZIERENDHINT && (m_actionOptions & SHIFTOPTION))
                break;
        }
    }
    if (inHandle.isEmpty())
        return m_curve->end();

    return m_curve->find(inHandle.last());
}

KisCurve::iterator KisToolBezier::drawPoint (KisCanvasPainter& gc, KisCurve::iterator point)
{
    if ((*point).hint() != BEZIERENDHINT)
        return ++point;

    KisCanvasController *controller = m_subject->canvasController();

    // Now draw the bezier
    
    KisCurve::iterator origin,control1,control2,destination;

    origin = point;
    control1 = origin.next();
    control2 = control1.nextPivot();
    destination = control2.next();

    if (control2 != m_curve->end()) {
        point = control2;
        TQPointArray vec(4);
        vec[0] = controller->windowToView((*origin).point().toTQPoint());
        vec[1] = controller->windowToView((*control1).point().toTQPoint());
        vec[2] = controller->windowToView((*control2).point().toTQPoint());
        vec[3] = controller->windowToView((*destination).point().toTQPoint());
        gc.drawCubicBezier(vec);
    }

    point += 1;

    return point;
}

void KisToolBezier::drawPivotHandle (KisCanvasPainter& gc, KisCurve::iterator point)
{
    if ((*point).hint() != BEZIERENDHINT)
        return;

    KisCanvasController *controller = m_subject->canvasController();

    TQPoint endpPos = controller->windowToView((*point).point().toTQPoint());

    if (!m_derivated->groupSelected(point)) {
        gc.setPen(m_pivotPen);
        gc.drawRoundRect(pivotRect(endpPos),m_pivotRounding,m_pivotRounding);
    } else {
        TQPoint nextControlPos = controller->windowToView((*point.next()).point().toTQPoint());
        TQPoint prevControlPos = controller->windowToView((*point.previousPivot()).point().toTQPoint());

        gc.setPen(m_selectedPivotPen);
        gc.drawRoundRect(selectedPivotRect(endpPos),m_selectedPivotRounding,m_selectedPivotRounding);
        if ((prevControlPos != endpPos || nextControlPos != endpPos) && !(m_actionOptions & CONTROLOPTION)) {
            gc.drawRoundRect(pivotRect(nextControlPos),m_pivotRounding,m_pivotRounding);
            gc.drawLine(endpPos,nextControlPos);
            gc.drawRoundRect(pivotRect(prevControlPos),m_pivotRounding,m_pivotRounding);
            gc.drawLine(prevControlPos,endpPos);
        }
    }

    gc.setPen(m_drawingPen);
}

#include "kis_tool_bezier.moc"
