/***************************************************************************
 *   Copyright (C) 2004 by Predrag Viceic                                  *
 *   viceic@net2000.ch                                            *
 *                                                                         *
 *   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.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "splineline.h"

SplineLine::SplineLine(QCanvas* canvas)
 : PolyLine(canvas)
{
    maxValue=1;
    minValue=0;
    zoomRatio=1;
}


SplineLine::~SplineLine()
{
}

/*!
    \fn SplineLine::rtti()
 */
int SplineLine::rtti() const
{
    return RTTI_SPLINELINE;
}

void SplineLine::drawShape ( QPainter & p ){
    QPen pen=p.pen();
    QPen mypen(darkGreen,1);
    p.setPen(mypen);
    qHeapSort(splinePoints);
    for (uint i=0;i<splinePoints.count();i++){
        SplinePoint current(splinePoints[i]);
        SplinePoint next(splinePoints[i+1]);
        if(current.selected() || next.selected()){
            mypen.setColor(red);
            p.setPen(mypen);
        }else{
            mypen.setColor(darkGreen);
            p.setPen(mypen);
        }

        mypen.setStyle(Qt::DotLine);
        p.setPen(mypen);

        if(current!=splinePoints.first()){
            p.drawLine(current,current.cp1);
            if(current.cp1.selected()){
                p.fillRect(current.cp1.x()-1,
                           current.cp1.y()-1,3,3,QBrush(red));
            }else{
                p.fillRect(current.cp1.x()-1,
                           current.cp1.y()-1,3,3,QBrush(yellow));
            }
        }
        if(splinePoints[i]!=splinePoints.last()){
            p.drawLine(current,current.cp2);
            if(current.cp2.selected()){
                p.fillRect(current.cp2.x()-1,
                        current.cp2.y()-1,3,3,QBrush(red));
            }else{
                p.fillRect(current.cp2.x()-1,
                           current.cp2.y()-1,3,3,QBrush(yellow));
            }
        }

        mypen.setStyle(Qt::SolidLine);
        p.setPen(mypen);

        if(splinePoints.count()>=2 && current!=splinePoints.last()){
            p.drawCubicBezier(toBezierPointArray(i));
        }
        mypen.setColor(darkGreen);
        p.setPen(mypen);
        if(splinePoints[i].selected()){
            p.fillRect(current.x()-3,current.y()-3,7,7,QBrush(red));
        }else{
            p.fillRect(current.x()-3,current.y()-3,7,7,QBrush(green));
        }
        p.drawRect(current.x()-3,current.y()-3,7,7);
    }
    p.setPen(pen);
}

void SplineLine::addPoint(QPoint p, long sample_pos,float value)
{
    if(p.x()>=0 && p.y()>=0 && sample_pos>=0){
        SortablePoint* moving=movingPoint(p);
        if (moving){
            moving->setSelected(TRUE);
        }else{
            SplinePoint sp(p);
            sp.setSamplePos(sample_pos);
            sp.setValue(value);
            sp.cp1.setX(sp.x()-30);
            sp.cp1.setY(sp.y());
            sp.cp1.setSamplePos(sample_pos-30*(p.x()>0?(sample_pos/p.x()):0));
            sp.cp1.setValue(value);
            sp.cp2.setX(sp.x()+30);
            sp.cp2.setY(sp.y());
            sp.cp2.setSamplePos(sample_pos+30*(p.x()>0?(sample_pos/p.x()):0));
            sp.cp2.setValue(value);
            splinePoints.append(sp);
            deselectAll();
        }
        qHeapSort(splinePoints);
        toSortablePoints();
        setPoints(toPointArray());
        update();
    }
}

/*!
    \fn SplineLine::toPointArray()
 */
QPointArray SplineLine::toBezierPointArray(uint i)
{
    if(splinePoints.count()<i+1){
        return QPointArray(0);
    }
    QPointArray array(4);
    array.setPoint(0,splinePoints[i]);
    array.setPoint(1,splinePoints[i].cp2);
    array.setPoint(2,splinePoints[i+1].cp1);
    array.setPoint(3,splinePoints[i+1]);
    return array;
}




/*!
    \fn SplineLine::movingPoint(QPoint p)
 */
SortablePoint* SplineLine::movingPoint(QPoint p)
{
    SortablePoint sp(p);
    for (uint i=0;i<splinePoints.count();i++){
        if(splinePoints[i]==sp) return &splinePoints[i];
        if(splinePoints[i].cp1==sp) return &splinePoints[i].cp1;
        if(splinePoints[i].cp2==sp) return &splinePoints[i].cp2;
    }
    return 0;
}

/*!
    \fn SplineLine::moveSelectedPoint(QPoint to,long sample_pos, float value)
 */
void SplineLine::moveSelectedPoint(QPoint to,long sample_pos, float value)
{
    for (uint i=0;i<splinePoints.count();i++){

        if(splinePoints[i].selected()){
            if((splinePoints.contains(SplinePoint(to))<=1) && containsSameX(to)<=1){
                int diffx=splinePoints[i].x()-to.x();
                int diffy=splinePoints[i].y()-to.y();
                long diff_sample=splinePoints[i].getSamplePos()-sample_pos;
                float diff_value=splinePoints[i].getValue()-value;
                splinePoints[i].cp1.rx()-=diffx;
                splinePoints[i].cp1.ry()-=diffy;
                splinePoints[i].cp1.setSamplePos(
                        splinePoints[i].cp1.getSamplePos()-diff_sample);
                splinePoints[i].cp1.setValue(
                        splinePoints[i].cp1.getValue()-diff_value);
                splinePoints[i].cp2.rx()-=diffx;
                splinePoints[i].cp2.ry()-=diffy;
                splinePoints[i].cp2.setSamplePos(
                        splinePoints[i].cp2.getSamplePos()-diff_sample);
                splinePoints[i].cp2.setValue(
                        splinePoints[i].cp2.getValue()-diff_value);
                splinePoints[i].setX(to.x());
                splinePoints[i].setY(to.y());
                splinePoints[i].setSamplePos(sample_pos);
                splinePoints[i].setValue(value);
                break;
            }
        }
        else if(splinePoints[i].cp1.selected()){
            splinePoints[i].cp1.setX(to.x());
            splinePoints[i].cp1.setY(to.y());
            splinePoints[i].cp1.setSamplePos(sample_pos);
            splinePoints[i].cp1.setValue(value);
            break;
        }
        else if(splinePoints[i].cp2.selected()){
            splinePoints[i].cp2.setX(to.x());
            splinePoints[i].cp2.setY(to.y());
            splinePoints[i].cp2.setSamplePos(sample_pos);
            splinePoints[i].cp2.setValue(value);
            break;
        }
    }
    qHeapSort(splinePoints);
    toSortablePoints();
    if(sortablePoints.first()==to || sortablePoints.last()==to){
            canvas()->setAllChanged();
        }
    setPoints(toPointArray());
    update();
}

/*!
    \fn SplineLine::deselectAll()
 */
void SplineLine::deselectAll()
{
    for (uint i=0;i<splinePoints.count();i++){
        splinePoints[i].setSelected(FALSE);
        splinePoints[i].cp1.setSelected(FALSE);
        splinePoints[i].cp2.setSelected(FALSE);
    }
    update();
}


/*!
    \fn SplineLine::removePoint(QPoint p)
 */
void SplineLine::removePoint(QPoint p)
{
    SplinePoint sp(p);
    if (splinePoints.contains(sp)){
        if(splinePoints.first()==sp || splinePoints.last()==sp){
            canvas()->setAllChanged();
        }
        splinePoints.remove(sp);
    }
    qHeapSort(splinePoints);
    toSortablePoints();
    setPoints(toPointArray());
    deselectAll();
    canvas()->setAllChanged();
    update();
}

/*!
    \fn SplineLine::updateSize(QSize size)
 */
void SplineLine::updateSize(QSize /*size*/)
{
    for(uint i=0;i<splinePoints.count();i++){
        long sample_pos=splinePoints[i].getSamplePos();
        float value=splinePoints[i].getValue();
        splinePoints[i].setX(sample_pos/zoomRatio);
        splinePoints[i].setY(valToYPos(value));
        sample_pos=splinePoints[i].cp1.getSamplePos();
        value=splinePoints[i].cp1.getValue();
        splinePoints[i].cp1.setX(sample_pos/zoomRatio);
        splinePoints[i].cp1.setY(valToYPos(value));
        sample_pos=splinePoints[i].cp2.getSamplePos();
        value=splinePoints[i].cp2.getValue();
        splinePoints[i].cp2.setX(sample_pos/zoomRatio);
        splinePoints[i].cp2.setY(valToYPos(value));
    }
    qHeapSort(splinePoints);
    setPoints(toPointArray());
    toSortablePoints();
    setX(sortablePoints[0].x());
    canvas()->setAllChanged();
    update();
}

/*!
    \fn SplineLine::getYAtX(int)
 */
int SplineLine::getYAtX(int x)
{
for (uint i=1;i<splinePoints.count();i++){
        if(splinePoints[i].x()>x){
            float dt=0.001;
            SortablePoint A(splinePoints[i-1]);
            SortablePoint D(splinePoints[i]);
            return int(MyMath::PointOnCubicBezierAt(A,splinePoints[i-1].cp2,splinePoints[i].cp1,D,dt,x));
        }
    }
    return -1;
}

/*!
    \fn SplineLine::getValueAtSample(long sample)
 */
float SplineLine::getValueAtSample(long sample)
{
    for (uint i=1;i<splinePoints.count();i++){
        if(splinePoints[i].getSamplePos()>sample &&
        splinePoints[i].getSamplePos()!=splinePoints[i-1].getSamplePos()){
            float dt=1024.0/float(splinePoints[i].getSamplePos()-splinePoints[i-1].getSamplePos());
            MyMath::SPoint2D a,b,c,d;
            a.rx=splinePoints[i-1].getSamplePos();
            a.ry=splinePoints[i-1].getValue();
            b.rx=splinePoints[i-1].cp2.getSamplePos();
            b.ry=splinePoints[i-1].cp2.getValue();
            c.rx=splinePoints[i].cp1.getSamplePos();
            c.ry=splinePoints[i].cp1.getValue();
            d.rx=splinePoints[i].getSamplePos();
            d.ry=splinePoints[i].getValue();
            return (float)MyMath::PointOnCubicBezierAt(a,b,c,d,dt,sample);
        }
    }
    return -1;
}


/*!
    \fn SplineLine::count()
 */
int SplineLine::count()
{
    return splinePoints.count();
}

/*!
    \fn SplineLine::clear()
 */
void SplineLine::clear()
{
    splinePoints.clear();
    sortablePoints.clear();
    canvas()->setAllChanged();
    update();
}

/*!
    \fn SplineLine::toPointArray()
 */
QPointArray SplineLine::toPointArray()
{
    toSortablePoints();
    QPointArray array(sortablePoints.count());
    qHeapSort(sortablePoints);
    for (uint i=0;i<sortablePoints.count();i++){
        array.setPoint(i,QPoint(sortablePoints[i].x(),sortablePoints[i].y()));
    }
    return array;
}

/*!
    \fn SplineLine::toSortablePoints()
 */
void SplineLine::toSortablePoints()
{
    sortablePoints.clear();
    //int count=0;
    for (uint i=0;i<splinePoints.count();i++){
        sortablePoints.append(splinePoints[i]);
        if(i!=0) sortablePoints.append(splinePoints[i].cp1);
        if(i!=splinePoints.count()-1) sortablePoints.append(splinePoints[i].cp2);
    }
    qHeapSort(sortablePoints);
}







/*!
    \fn SplineLine::setMinValue(float)
 */
void SplineLine::setMinValue(float min)
{
    minValue=min;
}


/*!
    \fn SplineLine::setMaxValue(float)
 */
void SplineLine::setMaxValue(float max)
{
    maxValue=max;
}


/*!
    \fn SplineLine::getSplinePoints()
 */
QValueList<SplinePoint> SplineLine::getSplinePoints()
{
    return splinePoints;
}


/*!
    \fn SplineLine::setSplinePoints(QValueList<SplinePoint> pts)
 */
void SplineLine::setSplinePoints(QValueList<SplinePoint> pts)
{
    splinePoints=pts;
    qHeapSort(splinePoints);
    toSortablePoints();
}

void SplineLine::setZoomRatio(long zr){
    zoomRatio=zr;
}

/*!
    \fn SplineLine::valToYPos(float val)
 */
int SplineLine::valToYPos(float val)
{
    return int(MyMath::linear_regression(minValue,maxValue,canvas()->height(),0,val));
}


/*!
    \fn SplineLine::crop(long start, long end)
 */
void SplineLine::crop(long start, long end)
{
    cout<<"cropping spline\n";
     //first move boundaries
    for (uint i=0;i<splinePoints.count();i++){
        if(splinePoints[i].getSamplePos()<start && 
            i<splinePoints.count()-1 && splinePoints[i+1].getSamplePos()>start){
                        long next_sample_pos=splinePoints[i+1].getSamplePos();
                        float next_value=splinePoints[i+1].getValue();
                        long old_sample_pos=splinePoints[i].getSamplePos();
                        float old_value=splinePoints[i].getValue();
                        long new_sample_pos=start;
                        float new_value=MyMath::linear_regression(old_sample_pos,next_sample_pos,
                                                              old_value,next_value,new_sample_pos);
                        splinePoints[i].setSamplePos(new_sample_pos);
                        splinePoints[i].setValue(new_value);
                        
                        next_sample_pos=splinePoints[i+1].cp1.getSamplePos();
                        next_value=splinePoints[i+1].cp1.getValue();
                        old_sample_pos=splinePoints[i].cp1.getSamplePos();
                        old_value=splinePoints[i].cp1.getValue();
                        new_sample_pos=
                            start+(splinePoints[i].getSamplePos()-splinePoints[i].cp1.getSamplePos());
                        new_value=MyMath::linear_regression(old_sample_pos,next_sample_pos,
                                                              old_value,next_value,new_sample_pos);
                        splinePoints[i].cp1.setSamplePos(new_sample_pos);
                        splinePoints[i].cp1.setValue(new_value);
                        
                        next_sample_pos=splinePoints[i+1].cp2.getSamplePos();
                        next_value=splinePoints[i+1].cp2.getValue();
                        old_sample_pos=splinePoints[i].cp2.getSamplePos();
                        old_value=splinePoints[i].cp2.getValue();
                        new_sample_pos=
                            start+(splinePoints[i].getSamplePos()-splinePoints[i].cp2.getSamplePos());
                        new_value=MyMath::linear_regression(old_sample_pos,next_sample_pos,
                                                              old_value,next_value,new_sample_pos);
                        splinePoints[i].cp2.setSamplePos(new_sample_pos);
                        splinePoints[i].cp2.setValue(new_value);
        }
        if(splinePoints[i].getSamplePos()>end && i>1 && splinePoints[i-1].getSamplePos()<end){
                        long previous_sample_pos=splinePoints[i-1].getSamplePos();
                        float previous_value=splinePoints[i-1].getValue();
                        long old_sample_pos=splinePoints[i].getSamplePos();
                        float old_value=splinePoints[i].getValue();
                        long new_sample_pos=end;
                        float new_value=MyMath::linear_regression(previous_sample_pos,old_sample_pos,
                                                              previous_value,old_value,new_sample_pos);
                        splinePoints[i].setSamplePos(new_sample_pos);
                        splinePoints[i].setValue(new_value);
                        
                        previous_sample_pos=splinePoints[i-1].cp1.getSamplePos();
                        previous_value=splinePoints[i-1].cp1.getValue();
                        old_sample_pos=splinePoints[i].cp1.getSamplePos();
                        old_value=splinePoints[i].cp1.getValue();
                        new_sample_pos=
                            end-(splinePoints[i].cp1.getSamplePos()-splinePoints[i].getSamplePos());
                        new_value=MyMath::linear_regression(previous_sample_pos,old_sample_pos,
                                                              previous_value,old_value,new_sample_pos);
                        splinePoints[i].cp1.setSamplePos(new_sample_pos);
                        splinePoints[i].cp1.setValue(new_value);
                        
                        previous_sample_pos=splinePoints[i-1].cp2.getSamplePos();
                        previous_value=splinePoints[i-1].cp2.getValue();
                        old_sample_pos=splinePoints[i].cp2.getSamplePos();
                        old_value=splinePoints[i].cp2.getValue();
                        new_sample_pos=
                            end-(splinePoints[i].cp2.getSamplePos()-splinePoints[i].getSamplePos());
                        new_value=MyMath::linear_regression(previous_sample_pos,old_sample_pos,
                                                              previous_value,old_value,new_sample_pos);
                        splinePoints[i].cp2.setSamplePos(new_sample_pos);
                        splinePoints[i].cp2.setValue(new_value);
        }
    }
    qHeapSort(splinePoints);
    //then remove before and after and change space
    typedef QValueList <SplinePoint> SplinePointList;
    SplinePointList::iterator it=splinePoints.begin();
    while( it != splinePoints.end()){
        if((*it).getSamplePos()<start){
                    it=splinePoints.remove(it);
        }
        else if((*it).getSamplePos()>end){
                    it=splinePoints.remove(it);
        }else{
            (*it).setSamplePos((*it).getSamplePos()-start);
            (*it).cp1.setSamplePos((*it).cp1.getSamplePos()-start);
            (*it).cp2.setSamplePos((*it).cp2.getSamplePos()-start);
            it++;
        }
    }
    updateSize(canvas()->size());
}
