/*
 *  Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
 *
 *  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 <stdlib.h>
#include <string.h>
#include <cfloat>

#include "tqbrush.h"
#include "tqcolor.h"
#include "tqfontinfo.h"
#include "tqfontmetrics.h"
#include "tqpen.h"
#include "tqregion.h"
#include "tqwmatrix.h"
#include <tqimage.h>
#include <tqmap.h>
#include <tqpainter.h>
#include <tqpixmap.h>
#include <tqpointarray.h>
#include <tqrect.h>
#include <tqstring.h>

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

#include "kis_brush.h"
#include "kis_debug_areas.h"
#include "kis_gradient.h"
#include "kis_image.h"
#include "kis_iterators_pixel.h"
#include "kis_layer.h"
#include "kis_paint_device.h"
#include "kis_pattern.h"
#include "kis_rect.h"
#include "kis_colorspace.h"
#include "kis_types.h"
#include "kis_vec.h"
#include "kis_selection.h"
#include "kis_gradient_painter.h"
#include "kis_meta_registry.h"
#include "kis_colorspace_factory_registry.h"

namespace {

    class GradientShapeStrategy {
    public:
        GradientShapeStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd);
        virtual ~GradientShapeStrategy() {}

        virtual double valueAt(double x, double y) const = 0;

    protected:
        KisPoint m_gradientVectorStart;
        KisPoint m_gradientVectorEnd;
    };

    GradientShapeStrategy::GradientShapeStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd)
        : m_gradientVectorStart(gradientVectorStart), m_gradientVectorEnd(gradientVectorEnd)
    {
    }


    class LinearGradientStrategy : public GradientShapeStrategy {
        typedef GradientShapeStrategy super;
    public:
        LinearGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd);

        virtual double valueAt(double x, double y) const;

    protected:
        double m_normalisedVectorX;
        double m_normalisedVectorY;
        double m_vectorLength;
    };

    LinearGradientStrategy::LinearGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd)
        : super(gradientVectorStart, gradientVectorEnd)
    {
        double dx = gradientVectorEnd.x() - gradientVectorStart.x();
        double dy = gradientVectorEnd.y() - gradientVectorStart.y();

        m_vectorLength = sqrt((dx * dx) + (dy * dy));

        if (m_vectorLength < DBL_EPSILON) {
            m_normalisedVectorX = 0;
            m_normalisedVectorY = 0;
        }
        else {
            m_normalisedVectorX = dx / m_vectorLength;
            m_normalisedVectorY = dy / m_vectorLength;
        }
    }

    double LinearGradientStrategy::valueAt(double x, double y) const
    {
        double vx = x - m_gradientVectorStart.x();
        double vy = y - m_gradientVectorStart.y();

        // Project the vector onto the normalised gradient vector.
        double t = vx * m_normalisedVectorX + vy * m_normalisedVectorY;

        if (m_vectorLength < DBL_EPSILON) {
            t = 0;
        }
        else {
            // Scale to 0 to 1 over the gradient vector length.
            t /= m_vectorLength;
        }

        return t;
    }


    class BiLinearGradientStrategy : public LinearGradientStrategy {
        typedef LinearGradientStrategy super;
    public:
        BiLinearGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd);

        virtual double valueAt(double x, double y) const;
    };

    BiLinearGradientStrategy::BiLinearGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd)
        : super(gradientVectorStart, gradientVectorEnd)
    {
    }

    double BiLinearGradientStrategy::valueAt(double x, double y) const
    {
        double t = super::valueAt(x, y);

        // Reflect
        if (t < -DBL_EPSILON) {
            t = -t;
        }

        return t;
    }


    class RadialGradientStrategy : public GradientShapeStrategy {
        typedef GradientShapeStrategy super;
    public:
        RadialGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd);

        virtual double valueAt(double x, double y) const;

    protected:
        double m_radius;
    };

    RadialGradientStrategy::RadialGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd)
        : super(gradientVectorStart, gradientVectorEnd)
    {
        double dx = gradientVectorEnd.x() - gradientVectorStart.x();
        double dy = gradientVectorEnd.y() - gradientVectorStart.y();

        m_radius = sqrt((dx * dx) + (dy * dy));
    }

    double RadialGradientStrategy::valueAt(double x, double y) const
    {
        double dx = x - m_gradientVectorStart.x();
        double dy = y - m_gradientVectorStart.y();

        double distance = sqrt((dx * dx) + (dy * dy));

        double t;

        if (m_radius < DBL_EPSILON) {
            t = 0;
        }
        else {
            t = distance / m_radius;
        }

        return t;
    }


    class SquareGradientStrategy : public GradientShapeStrategy {
        typedef GradientShapeStrategy super;
    public:
        SquareGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd);

        virtual double valueAt(double x, double y) const;

    protected:
        double m_normalisedVectorX;
        double m_normalisedVectorY;
        double m_vectorLength;
    };

    SquareGradientStrategy::SquareGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd)
        : super(gradientVectorStart, gradientVectorEnd)
    {
        double dx = gradientVectorEnd.x() - gradientVectorStart.x();
        double dy = gradientVectorEnd.y() - gradientVectorStart.y();

        m_vectorLength = sqrt((dx * dx) + (dy * dy));

        if (m_vectorLength < DBL_EPSILON) {
            m_normalisedVectorX = 0;
            m_normalisedVectorY = 0;
        }
        else {
            m_normalisedVectorX = dx / m_vectorLength;
            m_normalisedVectorY = dy / m_vectorLength;
        }
    }

    double SquareGradientStrategy::valueAt(double x, double y) const
    {
        double px = x - m_gradientVectorStart.x();
        double py = y - m_gradientVectorStart.y();

        double distance1 = 0;
        double distance2 = 0;

        if (m_vectorLength > DBL_EPSILON) {

            // Point to line distance is:
            // distance = ((l0.y() - l1.y()) * p.x() + (l1.x() - l0.x()) * p.y() + l0.x() * l1.y() - l1.x() * l0.y()) / m_vectorLength;
            //
            // Here l0 = (0, 0) and |l1 - l0| = 1

            distance1 = -m_normalisedVectorY * px + m_normalisedVectorX * py;
            distance1 = fabs(distance1);

            // Rotate point by 90 degrees and get the distance to the perpendicular
            distance2 = -m_normalisedVectorY * -py + m_normalisedVectorX * px;
            distance2 = fabs(distance2);
        }

        double t = TQMAX(distance1, distance2) / m_vectorLength;

        return t;
    }


    class ConicalGradientStrategy : public GradientShapeStrategy {
        typedef GradientShapeStrategy super;
    public:
        ConicalGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd);

        virtual double valueAt(double x, double y) const;

    protected:
        double m_vectorAngle;
    };

    ConicalGradientStrategy::ConicalGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd)
        : super(gradientVectorStart, gradientVectorEnd)
    {
        double dx = gradientVectorEnd.x() - gradientVectorStart.x();
        double dy = gradientVectorEnd.y() - gradientVectorStart.y();

        // Get angle from 0 to 2 PI.
        m_vectorAngle = atan2(dy, dx) + M_PI;
    }

    double ConicalGradientStrategy::valueAt(double x, double y) const
    {
        double px = x - m_gradientVectorStart.x();
        double py = y - m_gradientVectorStart.y();

        double angle = atan2(py, px) + M_PI;

        angle -= m_vectorAngle;

        if (angle < 0) {
            angle += 2 * M_PI;
        }

        double t = angle / (2 * M_PI);

        return t;
    }


    class ConicalSymetricGradientStrategy : public GradientShapeStrategy {
        typedef GradientShapeStrategy super;
    public:
        ConicalSymetricGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd);

        virtual double valueAt(double x, double y) const;

    protected:
        double m_vectorAngle;
    };

    ConicalSymetricGradientStrategy::ConicalSymetricGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd)
        : super(gradientVectorStart, gradientVectorEnd)
    {
        double dx = gradientVectorEnd.x() - gradientVectorStart.x();
        double dy = gradientVectorEnd.y() - gradientVectorStart.y();

        // Get angle from 0 to 2 PI.
        m_vectorAngle = atan2(dy, dx) + M_PI;
    }

    double ConicalSymetricGradientStrategy::valueAt(double x, double y) const
    {
        double px = x - m_gradientVectorStart.x();
        double py = y - m_gradientVectorStart.y();

        double angle = atan2(py, px) + M_PI;

        angle -= m_vectorAngle;

        if (angle < 0) {
            angle += 2 * M_PI;
        }

        double t;

        if (angle < M_PI) {
            t = angle / M_PI;
        }
        else {
            t = 1 - ((angle - M_PI) / M_PI);
        }

        return t;
    }


    class GradientRepeatStrategy {
    public:
        GradientRepeatStrategy() {}
        virtual ~GradientRepeatStrategy() {}

        virtual double valueAt(double t) const = 0;
    };


    class GradientRepeatNoneStrategy : public GradientRepeatStrategy {
    public:
        static GradientRepeatNoneStrategy *instance();

        virtual double valueAt(double t) const;

    private:
        GradientRepeatNoneStrategy() {}

        static GradientRepeatNoneStrategy *m_instance;
    };

    GradientRepeatNoneStrategy *GradientRepeatNoneStrategy::m_instance = 0;

    GradientRepeatNoneStrategy *GradientRepeatNoneStrategy::instance()
    {
        if (m_instance == 0) {
            m_instance = new GradientRepeatNoneStrategy();
            TQ_CHECK_PTR(m_instance);
        }

        return m_instance;
    }

    // Output is clamped to 0 to 1.
    double GradientRepeatNoneStrategy::valueAt(double t) const
    {
        double value = t;

        if (t < DBL_EPSILON) {
            value = 0;
        }
        else
            if (t > 1 - DBL_EPSILON) {
                value = 1;
            }

        return value;
    }


    class GradientRepeatForwardsStrategy : public GradientRepeatStrategy {
    public:
        static GradientRepeatForwardsStrategy *instance();

        virtual double valueAt(double t) const;

    private:
        GradientRepeatForwardsStrategy() {}

        static GradientRepeatForwardsStrategy *m_instance;
    };

    GradientRepeatForwardsStrategy *GradientRepeatForwardsStrategy::m_instance = 0;

    GradientRepeatForwardsStrategy *GradientRepeatForwardsStrategy::instance()
    {
        if (m_instance == 0) {
            m_instance = new GradientRepeatForwardsStrategy();
            TQ_CHECK_PTR(m_instance);
        }

        return m_instance;
    }

    // Output is 0 to 1, 0 to 1, 0 to 1...
    double GradientRepeatForwardsStrategy::valueAt(double t) const
    {
        int i = static_cast<int>(t);

        if (t < DBL_EPSILON) {
            i--;
        }

        double value = t - i;

        return value;
    }


    class GradientRepeatAlternateStrategy : public GradientRepeatStrategy {
    public:
        static GradientRepeatAlternateStrategy *instance();

        virtual double valueAt(double t) const;

    private:
        GradientRepeatAlternateStrategy() {}

        static GradientRepeatAlternateStrategy *m_instance;
    };

    GradientRepeatAlternateStrategy *GradientRepeatAlternateStrategy::m_instance = 0;

    GradientRepeatAlternateStrategy *GradientRepeatAlternateStrategy::instance()
    {
        if (m_instance == 0) {
            m_instance = new GradientRepeatAlternateStrategy();
            TQ_CHECK_PTR(m_instance);
        }

        return m_instance;
    }

    // Output is 0 to 1, 1 to 0, 0 to 1, 1 to 0...
    double GradientRepeatAlternateStrategy::valueAt(double t) const
    {
        if (t < 0) {
            t = -t;
        }

        int i = static_cast<int>(t);

                double value = t - i;

        if (i % 2 == 1) {
            value = 1 - value;
        }

        return value;
    }
}

KisGradientPainter::KisGradientPainter()
    : super()
{
    m_gradient = 0;
}

KisGradientPainter::KisGradientPainter(KisPaintDeviceSP device) : super(device), m_gradient(0)
{
}

bool KisGradientPainter::paintGradient(const KisPoint& gradientVectorStart,
                       const KisPoint& gradientVectorEnd,
                       enumGradientShape shape,
                       enumGradientRepeat repeat,
                       double antiAliasThreshold,
                       bool reverseGradient,
                       TQ_INT32 startx,
                       TQ_INT32 starty,
                       TQ_INT32 width,
                       TQ_INT32 height)
{
    m_cancelRequested = false;

    if (!m_gradient) return false;

    GradientShapeStrategy *shapeStrategy = 0;

    switch (shape) {
    case GradientShapeLinear:
        shapeStrategy = new LinearGradientStrategy(gradientVectorStart, gradientVectorEnd);
        break;
    case GradientShapeBiLinear:
        shapeStrategy = new BiLinearGradientStrategy(gradientVectorStart, gradientVectorEnd);
        break;
    case GradientShapeRadial:
        shapeStrategy = new RadialGradientStrategy(gradientVectorStart, gradientVectorEnd);
        break;
    case GradientShapeSquare:
        shapeStrategy = new SquareGradientStrategy(gradientVectorStart, gradientVectorEnd);
        break;
    case GradientShapeConical:
        shapeStrategy = new ConicalGradientStrategy(gradientVectorStart, gradientVectorEnd);
        break;
    case GradientShapeConicalSymetric:
        shapeStrategy = new ConicalSymetricGradientStrategy(gradientVectorStart, gradientVectorEnd);
        break;
    }
    TQ_CHECK_PTR(shapeStrategy);

    GradientRepeatStrategy *repeatStrategy = 0;

    switch (repeat) {
    case GradientRepeatNone:
        repeatStrategy = GradientRepeatNoneStrategy::instance();
        break;
    case GradientRepeatForwards:
        repeatStrategy = GradientRepeatForwardsStrategy::instance();
        break;
    case GradientRepeatAlternate:
        repeatStrategy = GradientRepeatAlternateStrategy::instance();
        break;
    }
    Q_ASSERT(repeatStrategy != 0);


    //If the device has a selection only iterate over that selection
    TQRect r;
    if( m_device->hasSelection() ) {
        r = m_device->selection()->selectedExactRect();
        startx = r.x();
        starty = r.y();
        width = r.width();
        height = r.height();
    }

    TQ_INT32 endx = startx + width - 1;
    TQ_INT32 endy = starty + height - 1;

    TQImage layer (width, height, 32);
    layer.setAlphaBuffer(true);

    int pixelsProcessed = 0;
    int lastProgressPercent = 0;

    emit notifyProgressStage(i18n("Rendering gradient..."), 0);

    int totalPixels = width * height;

    if (antiAliasThreshold < 1 - DBL_EPSILON) {
        totalPixels *= 2;
    }

    for (int y = starty; y <= endy; y++) {
        for (int x = startx; x <= endx; x++) {

            double t = shapeStrategy->valueAt( x, y);
            t = repeatStrategy->valueAt(t);

            if (reverseGradient) {
                t = 1 - t;
            }

            TQColor color;
            TQ_UINT8 opacity;

            m_gradient->colorAt(t, &color, &opacity);

            layer.setPixel(x - startx, y - starty,
                           tqRgba(color.red(), color.green(), color.blue(), opacity));

            pixelsProcessed++;

            int progressPercent = (pixelsProcessed * 100) / totalPixels;

            if (progressPercent > lastProgressPercent) {
                emit notifyProgress(progressPercent);
                lastProgressPercent = progressPercent;

                if (m_cancelRequested) {
                    break;
                }
            }
            if (m_cancelRequested) {
                break;
            }
        }
    }

    if (!m_cancelRequested && antiAliasThreshold < 1 - DBL_EPSILON) {

        TQColor color;
        emit notifyProgressStage(i18n("Anti-aliasing gradient..."), lastProgressPercent);
        TQ_UINT8 * layerPointer = layer.bits();
        for (int y = starty; y <= endy; y++) {
            for (int x = startx; x <= endx; x++) {

                double maxDistance = 0;

                TQ_UINT8 redThis = layerPointer[2];
                TQ_UINT8 greenThis = layerPointer[1];
                TQ_UINT8 blueThis = layerPointer[0];
                TQ_UINT8 thisPixelOpacity = layerPointer[3];

                for (int yOffset = -1; yOffset < 2; yOffset++) {
                    for (int xOffset = -1; xOffset < 2; xOffset++) {

                        if (xOffset != 0 || yOffset != 0) {
                            int sampleX = x + xOffset;
                            int sampleY = y + yOffset;

                            if (sampleX >= startx && sampleX <= endx && sampleY >= starty && sampleY <= endy) {
                                uint x = sampleX - startx;
                                uint y = sampleY - starty;
                                TQ_UINT8 * pixelPos = layer.bits() + (y * width * 4) + (x * 4);
                                TQ_UINT8 red = *(pixelPos +2);
                                TQ_UINT8 green = *(pixelPos + 1);
                                TQ_UINT8 blue = *pixelPos;
                                TQ_UINT8 opacity = *(pixelPos + 3);

                                double dRed = (red * opacity - redThis * thisPixelOpacity) / 65535.0;
                                double dGreen = (green * opacity - greenThis * thisPixelOpacity) / 65535.0;
                                double dBlue = (blue * opacity - blueThis * thisPixelOpacity) / 65535.0;

                                #define SQRT_3 1.7320508

                                double distance =/* sqrt(*/dRed * dRed + dGreen * dGreen + dBlue * dBlue/*) / SQRT_3*/;

                                if (distance > maxDistance) {
                                    maxDistance = distance;
                                }
                            }
                        }
                    }
                }

                if (maxDistance > 3.*antiAliasThreshold*antiAliasThreshold) {
                    const int numSamples = 4;

                    int totalRed = 0;
                    int totalGreen = 0;
                    int totalBlue = 0;
                    int totalOpacity = 0;

                    for (int ySample = 0; ySample < numSamples; ySample++) {
                        for (int xSample = 0; xSample < numSamples; xSample++) {

                            double sampleWidth = 1.0 / numSamples;

                            double sampleX = x - 0.5 + (sampleWidth / 2) + xSample * sampleWidth;
                            double sampleY = y - 0.5 + (sampleWidth / 2) + ySample * sampleWidth;

                            double t = shapeStrategy->valueAt(sampleX, sampleY);
                            t = repeatStrategy->valueAt(t);

                            if (reverseGradient) {
                                t = 1 - t;
                            }

                            TQ_UINT8 opacity;

                            m_gradient->colorAt(t, &color, &opacity);

                            totalRed += color.red();
                            totalGreen += color.green();
                            totalBlue += color.blue();
                            totalOpacity += opacity;
                        }
                    }

                    int red = totalRed / (numSamples * numSamples);
                    int green = totalGreen / (numSamples * numSamples);
                    int blue = totalBlue / (numSamples * numSamples);
                    int opacity = totalOpacity / (numSamples * numSamples);

                    layer.setPixel(x - startx, y - starty, tqRgba(red, green, blue, opacity));
                }

                pixelsProcessed++;

                int progressPercent = (pixelsProcessed * 100) / totalPixels;

                if (progressPercent > lastProgressPercent) {
                    emit notifyProgress(progressPercent);
                    lastProgressPercent = progressPercent;

                    if (m_cancelRequested) {
                        break;
                    }
                }
                layerPointer += 4;
            }

            if (m_cancelRequested) {
                break;
            }
        }
    }

    if (!m_cancelRequested) {
        kdDebug() << "Have we got a selection? " << m_device->hasSelection() << endl;
        KisPaintDeviceSP dev = new KisPaintDevice(KisMetaRegistry::instance()->csRegistry()->getRGB8(), "temporary device for gradient");
        dev->writeBytes(layer.bits(), startx, starty, width, height);
        bltSelection(startx, starty, m_compositeOp, dev, m_opacity, startx, starty, width, height);
    }
    delete shapeStrategy;

    emit notifyProgressDone();

    return !m_cancelRequested;
}
