/*
 *  Copyright (c) 2004 Michael Thaler <michael.thaler@physik.tu-muenchen.de>
 *
 *  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 <tqapplication.h>
#include <tqwmatrix.h>
#include <tqrect.h>

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

#include "kis_paint_device.h"
#include "kis_rotate_visitor.h"
#include "kis_progress_display_interface.h"
#include "kis_iterators_pixel.h"
#include "kis_selection.h"
#include "kis_painter.h"

void KisRotateVisitor::rotate(double angle, bool rotateAboutImageCentre, KisProgressDisplayInterface *progress)
{
    KisPoint centreOfRotation;

    if (rotateAboutImageCentre) {
        centreOfRotation = KisPoint(m_dev->image()->width() / 2.0,  m_dev->image()->height() / 2.0);
    } else {
        TQRect r = m_dev->exactBounds();
        centreOfRotation = KisPoint(r.x() + (r.width() / 2.0), r.y() + (r.height() / 2.0));
    }

    m_progress = progress;

    KisPaintDeviceSP rotated = rotate(m_dev, angle, centreOfRotation);

    if (!m_dev->hasSelection()) {
        // Clear everything
        m_dev->clear();
    } else {
        // Clear selected pixels
        m_dev->clearSelection();
    }

    KisPainter p(m_dev);
    TQRect r = rotated->extent();

    // OVER ipv COPY
    p.bitBlt(r.x(), r.y(), COMPOSITE_OVER, rotated, OPACITY_OPAQUE, r.x(), r.y(), r.width(), r.height());
    p.end();
}

void KisRotateVisitor::shear(double angleX, double angleY, KisProgressDisplayInterface *progress)
{
    const double pi=3.1415926535897932385;
    double thetaX = angleX * pi / 180;
    double shearX = tan(thetaX);
    double thetaY = angleY * pi / 180;
    double shearY = tan(thetaY);

    TQRect r = m_dev->exactBounds();

    const int xShearSteps = r.height();
    const int yShearSteps = r.width();

    m_progress = progress;
    initProgress(xShearSteps + yShearSteps);


    KisPaintDeviceSP sheared;

    if (m_dev->hasSelection()) {
        sheared = new KisPaintDevice(m_dev->colorSpace(), "sheared");
        KisPainter p1(sheared);
        p1.bltSelection(r.x(), r.y(), COMPOSITE_OVER, m_dev, OPACITY_OPAQUE, r.x(), r.y(), r.width(), r.height());
        p1.end();
         sheared = xShear(sheared, shearX);
    }
    else {
        sheared = xShear(m_dev, shearX);
    }

     sheared = yShear(sheared, shearY);

     if (!m_dev->hasSelection()) {
        m_dev->clear();
     } else {
         // Clear selected pixels
         m_dev->clearSelection();
     }

    KisPainter p2(m_dev);
    r = sheared->extent();

    p2.bitBlt(r.x(), r.y(), COMPOSITE_OVER, sheared, OPACITY_OPAQUE, r.x(), r.y(), r.width(), r.height());
    p2.end();

    setProgressDone();
}

KisPaintDeviceSP KisRotateVisitor::rotateRight90(KisPaintDeviceSP src)
{
    KisPaintDeviceSP dst = new KisPaintDevice(src->colorSpace(), "rotateright90");
    dst->setX(src->getX());
    dst->setY(src->getY());

    TQ_INT32 pixelSize = src->pixelSize();
    TQRect r = src->exactBounds();
    TQ_INT32 x = 0;

    for (TQ_INT32 y = r.bottom(); y >= r.top(); --y) {
        KisHLineIteratorPixel hit = src->createHLineIterator(r.x(), y, r.width(), false);
        KisVLineIterator vit = dst->createVLineIterator(-y, r.x(), r.width(), true);

            while (!hit.isDone()) {
            if (hit.isSelected())  {
                memcpy(vit.rawData(), hit.rawData(), pixelSize);
            }
            ++hit;
            ++vit;
        }
        ++x;
        incrementProgress();
    }

    return dst;
}

KisPaintDeviceSP KisRotateVisitor::rotateLeft90(KisPaintDeviceSP src)
{
    KisPaintDeviceSP dst = new KisPaintDevice(src->colorSpace(), "rotateleft90");

    TQ_INT32 pixelSize = src->pixelSize();
    TQRect r = src->exactBounds();
    TQ_INT32 x = 0;

    for (TQ_INT32 y = r.top(); y <= r.bottom(); ++y) {
        // Read the horizontal line from back to front, write onto the vertical column
        KisHLineIteratorPixel hit = src->createHLineIterator(r.x(), y, r.width(), false);
        KisVLineIterator vit = dst->createVLineIterator(y, -r.x() - r.width(), r.width(), true);

        hit += r.width() - 1;
        while (!vit.isDone()) {
            if (hit.isSelected()) {
                memcpy(vit.rawData(), hit.rawData(), pixelSize);
            }
            --hit;
            ++vit;
        }
        ++x;
        incrementProgress();
    }

    return dst;
}

KisPaintDeviceSP KisRotateVisitor::rotate180(KisPaintDeviceSP src)
{
    KisPaintDeviceSP dst = new KisPaintDevice(src->colorSpace(), "rotate180");
    dst->setX(src->getX());
    dst->setY(src->getY());

    TQ_INT32 pixelSize = src->pixelSize();
    TQRect r = src->exactBounds();

    for (TQ_INT32 y = r.top(); y <= r.bottom(); ++y) {
        KisHLineIteratorPixel srcIt = src->createHLineIterator(r.x(), y, r.width(), false);
        KisHLineIterator dstIt = dst->createHLineIterator( -r.x() - r.width(), -y, r.width(), true);

        srcIt += r.width() - 1;
        while (!dstIt.isDone()) {
            if (srcIt.isSelected())  {
                memcpy(dstIt.rawData(), srcIt.rawData(), pixelSize);
            }
            --srcIt;
            ++dstIt;
        }
        incrementProgress();
    }

    return dst;
}

KisPaintDeviceSP KisRotateVisitor::rotate(KisPaintDeviceSP src, double angle, KisPoint centreOfRotation)
{
    const double pi = 3.1415926535897932385;

    if (angle >= 315 && angle < 360) {
        angle = angle - 360;
    } else if (angle > -360 && angle < -45) {
        angle = angle + 360;
    }

    TQRect r = src->exactBounds();

    const int xShearSteps = r.height();
    const int yShearSteps = r.width();
    const int fixedRotateSteps = r.height();

    KisPaintDeviceSP dst;

    if (angle == 90) {
        initProgress(fixedRotateSteps);
        dst = rotateRight90(src);
    } else if (angle == 180) {
        initProgress(fixedRotateSteps);
        dst = rotate180(src);
    } else if (angle == 270) {
        initProgress(fixedRotateSteps);
        dst = rotateLeft90(src);
    } else {
        double theta;

        if (angle >= -45 && angle < 45) {

            theta = angle * pi / 180;
            dst = src;
            initProgress(yShearSteps + (2 * xShearSteps));
        }
        else if (angle >= 45 && angle < 135) {

            initProgress(fixedRotateSteps + yShearSteps + (2 * xShearSteps));
            dst = rotateRight90(src);
            theta = (angle - 90) * pi / 180;
        }
        else if (angle >= 135 && angle < 225) {

            initProgress(fixedRotateSteps + yShearSteps + (2 * xShearSteps));
            dst = rotate180(src);
            theta = (angle - 180) * pi / 180;

        } else {

            initProgress(fixedRotateSteps + yShearSteps + (2 * xShearSteps));
            dst = rotateLeft90(src);
            theta = (angle - 270) * pi / 180;
        }

        double shearX = tan(theta / 2);
        double shearY = sin(theta);

        //first perform a shear along the x-axis by tan(theta/2)
        dst = xShear(dst, shearX);
        //next perform a shear along the y-axis by sin(theta)
        dst = yShear(dst, shearY);
        //again perform a shear along the x-axis by tan(theta/2)
        dst = xShear(dst, shearX);
    }

    double sinAngle = sin(angle * pi / 180);
    double cosAngle = cos(angle * pi / 180);

    KisPoint rotatedCentreOfRotation(
                                centreOfRotation.x() * cosAngle - centreOfRotation.y() * sinAngle,
                                centreOfRotation.x() * sinAngle + centreOfRotation.y() * cosAngle);

    dst->setX((TQ_INT32)(dst->getX() + centreOfRotation.x() - rotatedCentreOfRotation.x()));
    dst->setY((TQ_INT32)(dst->getY() + centreOfRotation.y() - rotatedCentreOfRotation.y()));

    setProgressDone();

    return dst;
}

KisPaintDeviceSP KisRotateVisitor::xShear(KisPaintDeviceSP src, double shearX)
{
    KisPaintDeviceSP dst = new KisPaintDevice(src->colorSpace(), "xShear");
    dst->setX(src->getX());
    dst->setY(src->getY());

    TQRect r = src->exactBounds();

        double displacement;
        TQ_INT32 displacementInt;
        double weight;

    for (TQ_INT32 y = r.top(); y <= r.bottom(); y++) {

        //calculate displacement
        displacement = -y * shearX;

        displacementInt = (TQ_INT32)(floor(displacement));
        weight = displacement - displacementInt;

        TQ_UINT8 pixelWeights[2];

        pixelWeights[0] = static_cast<TQ_UINT8>(weight * 255 + 0.5);
        pixelWeights[1] = 255 - pixelWeights[0];

        KisHLineIteratorPixel srcIt = src->createHLineIterator(r.x(), y, r.width() + 1, false);
        KisHLineIteratorPixel leftSrcIt = src->createHLineIterator(r.x() - 1, y, r.width() + 1, false);
        KisHLineIteratorPixel dstIt = dst->createHLineIterator(r.x() + displacementInt, y, r.width() + 1, true);

        while (!srcIt.isDone()) {

            const TQ_UINT8 *pixelPtrs[2];

            pixelPtrs[0] = leftSrcIt.rawData();
            pixelPtrs[1] = srcIt.rawData();

            src->colorSpace()->mixColors(pixelPtrs, pixelWeights, 2, dstIt.rawData());

            ++srcIt;
            ++leftSrcIt;
            ++dstIt;
        }
        incrementProgress();
    }

    return dst;
}

KisPaintDeviceSP KisRotateVisitor::yShear(KisPaintDeviceSP src, double shearY)
{
    KisPaintDeviceSP dst = new KisPaintDevice(src->colorSpace(), "yShear");
    dst->setX(src->getX());
    dst->setY(src->getY());

    TQRect r = src->exactBounds();

        double displacement;
        TQ_INT32 displacementInt;
        double weight;

    for (TQ_INT32 x = r.left(); x <= r.right(); x++) {

        //calculate displacement
        displacement = x * shearY;

        displacementInt = (TQ_INT32)(floor(displacement));
        weight = displacement - displacementInt;

        TQ_UINT8 pixelWeights[2];

        pixelWeights[0] = static_cast<TQ_UINT8>(weight * 255 + 0.5);
        pixelWeights[1] = 255 - pixelWeights[0];

        KisVLineIteratorPixel srcIt = src->createVLineIterator(x, r.y(), r.height() + 1, false);
        KisVLineIteratorPixel leftSrcIt = src->createVLineIterator(x, r.y() - 1, r.height() + 1, false);
        KisVLineIteratorPixel dstIt = dst->createVLineIterator(x, r.y() + displacementInt, r.height() + 1, true);

        while (!srcIt.isDone()) {

            const TQ_UINT8 *pixelPtrs[2];

            pixelPtrs[0] = leftSrcIt.rawData();
            pixelPtrs[1] = srcIt.rawData();

            src->colorSpace()->mixColors(pixelPtrs, pixelWeights, 2, dstIt.rawData());

            ++srcIt;
            ++leftSrcIt;
            ++dstIt;
        }
        incrementProgress();
    }

        return dst;
}

void KisRotateVisitor::initProgress(TQ_INT32 totalSteps)
{
    if (!m_progress) return;

    m_progressTotalSteps = totalSteps;
    m_progressStep = 0;
    m_lastProgressPerCent = 0;


    m_progress->setSubject(this, true, false);
    emit notifyProgress(0);

}

void KisRotateVisitor::incrementProgress()
{
    if (!m_progress) return;

    m_progressStep++;
    TQ_INT32 progressPerCent = (m_progressStep * 100) / m_progressTotalSteps;

    if (progressPerCent != m_lastProgressPerCent) {
        m_lastProgressPerCent = progressPerCent;
        emit notifyProgress(progressPerCent);
    }
}

void KisRotateVisitor::setProgressDone()
{
    if (!m_progress) return;

    emit notifyProgressDone();
}