/*
 *  Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
 *  Copyright (c) 2004 Bart Coppens <kde@bartcoppens.be>
 *
 *  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 <stack>

#include "tqbrush.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 <kcommand.h>
#include <tdelocale.h>

#include "kis_brush.h"
#include "kis_debug_areas.h"
#include "kis_image.h"
#include "kis_layer.h"
#include "kis_paint_device.h"
#include "kis_painter.h"
#include "kis_pattern.h"
#include "kis_rect.h"
#include "kis_colorspace.h"
#include "kis_transaction.h"
#include "kis_types.h"
#include "kis_vec.h"
#include "kis_selection.h"
#include "kis_fill_painter.h"
#include "kis_iterators_pixel.h"
#include "kis_iterator.h"
#include "kis_color.h"
#include "kis_selection.h"

namespace {
}

KisFillPainter::KisFillPainter()
    : super()
{
    m_width = m_height = -1;
    m_sampleMerged = false;
    m_careForSelection = false;
    m_fuzzy = false;
}

KisFillPainter::KisFillPainter(KisPaintDeviceSP device) : super(device)
{
    m_width = m_height = -1;
    m_sampleMerged = false;
    m_careForSelection = false;
    m_fuzzy = false;
}

// 'regular' filling
// XXX: This also needs renaming, since filling ought to keep the opacity and the composite op in mind,
//      this is more eraseToColor.
void KisFillPainter::fillRect(TQ_INT32 x1, TQ_INT32 y1, TQ_INT32 w, TQ_INT32 h, const KisColor& kc, TQ_UINT8 opacity)
{
    if (w > 0 && h > 0) {
        // Make sure we're in the right colorspace

        KisColor kc2(kc); // get rid of const
        kc2.convertTo(m_device->colorSpace());
        TQ_UINT8 * data = kc2.data();
        m_device->colorSpace()->setAlpha(data, opacity, 1);

        m_device->fill(x1, y1, w, h, data);

        addDirtyRect(TQRect(x1, y1, w, h));
    }
}

void KisFillPainter::fillRect(TQ_INT32 x1, TQ_INT32 y1, TQ_INT32 w, TQ_INT32 h, KisPattern * pattern) {
    if (!pattern) return;
    if (!pattern->valid()) return;
    if (!m_device) return;


    KisPaintDeviceSP patternLayer = pattern->image(m_device->colorSpace());

    int sx, sy, sw, sh;

    int y = y1;

    if (y >= 0) {
        sy = y % pattern->height();
    } else {
        sy = pattern->height() - (((-y - 1) % pattern->height()) + 1);
    }

    while (y < y1 + h) {
        sh = TQMIN((y1 + h) - y, pattern->height() - sy);

        int x = x1;

        if (x >= 0) {
            sx = x % pattern->width();
        } else {
            sx = pattern->width() - (((-x - 1) % pattern->width()) + 1);
        }

        while (x < x1 + w) {
            sw = TQMIN((x1 + w) - x, pattern->width() - sx);

            bitBlt(x, y, m_compositeOp, patternLayer.data(), m_opacity, sx, sy, sw, sh);
            x += sw; sx = 0;
        }

        y+=sh; sy = 0;
    }

    addDirtyRect(TQRect(x1, y1, w, h));
}

// flood filling

void KisFillPainter::fillColor(int startX, int startY) {
    genericFillStart(startX, startY);

    // Now create a layer and fill it
    KisPaintDeviceSP filled = new KisPaintDevice(m_device->colorSpace(), "filled");
    TQ_CHECK_PTR(filled);
    KisFillPainter painter(filled.data());
    painter.fillRect(0, 0, m_width, m_height, m_paintColor);
    painter.end();

    genericFillEnd(filled);
}

void KisFillPainter::fillPattern(int startX, int startY) {
    genericFillStart(startX, startY);

    // Now create a layer and fill it
    KisPaintDeviceSP filled = new KisPaintDevice(m_device->colorSpace(), "filled");
    TQ_CHECK_PTR(filled);
    KisFillPainter painter(filled.data());
    painter.fillRect(0, 0, m_width, m_height, m_pattern);
    painter.end();

    genericFillEnd(filled);
}

void KisFillPainter::genericFillStart(int startX, int startY) {
    m_cancelRequested = false;

    if (m_width < 0 || m_height < 0) {
        if (m_device->image()) {
            m_width = m_device->image()->width();
            m_height = m_device->image()->height();
        } else {
            m_width = m_height = 500;
        }
    }

    m_size = m_width * m_height;

    // Create a selection from the surrounding area
    m_selection = createFloodSelection(startX, startY);
}

void KisFillPainter::genericFillEnd(KisPaintDeviceSP filled) {
    if (m_cancelRequested) {
        m_width = m_height = -1;
        return;
    }

    TQRect rc = m_selection->selectedRect();

    bltSelection(rc.x(), rc.y(), m_compositeOp, filled, m_selection, m_opacity,
                 rc.x(), rc.y(), rc.width(), rc.height());

    emit notifyProgressDone();

    m_width = m_height = -1;
}

struct FillSegment {
    FillSegment(int x, int y/*, FillSegment* parent*/) : x(x), y(y)/*, parent(parent)*/ {}
    int x;
    int y;
//    FillSegment* parent;
};

typedef enum { None = 0, Added = 1, Checked = 2 } Status;

KisSelectionSP KisFillPainter::createFloodSelection(int startX, int startY) {
    if (m_width < 0 || m_height < 0) {
        if (m_device->hasSelection() && m_careForSelection) {

            TQRect rc = m_device->selection()->selectedRect();
            m_width = rc.width() - (startX - rc.x());
            m_height = rc.height() - (startY - rc.y());

        } else if (m_device->image()) {

            m_width = m_device->image()->width();
            m_height = m_device->image()->height();

        } else {
            m_width = m_height = 500;
        }
    }

    // Don't try to fill if we start outside the borders, just return an empty 'fill'
    if (startX < 0 || startY < 0 || startX >= m_width || startY >= m_height)
        return new KisSelection(m_device);

    KisPaintDeviceSP sourceDevice = 0;

    // sample merged?
    if (m_sampleMerged) {
        if (!m_device->image()) {
            return new KisSelection(m_device);
        }
        sourceDevice = m_device->image()->mergedImage();
    } else {
        sourceDevice = m_device;
    }

    m_size = m_width * m_height;

    KisSelectionSP selection = new KisSelection(m_device);
    KisColorSpace * colorSpace = selection->colorSpace();
    KisColorSpace * devColorSpace = sourceDevice->colorSpace();

    TQ_UINT8* source = new TQ_UINT8[sourceDevice->pixelSize()];
    KisHLineIteratorPixel pixelIt = sourceDevice->createHLineIterator(startX, startY, startX+1, false);

    memcpy(source, pixelIt.rawData(), sourceDevice->pixelSize());

    std::stack<FillSegment*> stack;

    stack.push(new FillSegment(startX, startY/*, 0*/));

    Status* map = new Status[m_size];

    memset(map, None, m_size * sizeof(Status));

    int progressPercent = 0; int pixelsDone = 0; int currentPercent = 0;
    emit notifyProgressStage(i18n("Making fill outline..."), 0);

    bool hasSelection = m_careForSelection && sourceDevice->hasSelection();
    KisSelectionSP srcSel = 0;
    if (hasSelection)
        srcSel = sourceDevice->selection();

    while(!stack.empty()) {
        FillSegment* segment = stack.top();
        stack.pop();
        if (map[m_width * segment->y + segment->x] == Checked) {
            delete segment;
            continue;
        }
        map[m_width * segment->y + segment->x] = Checked;

        int x = segment->x;
        int y = segment->y;

        /* We need an iterator that is valid in the range (0,y) - (width,y). Therefore,
        it is needed to start the iterator at the first position, and then skip to (x,y). */
        pixelIt = sourceDevice->createHLineIterator(0, y, m_width, false);
        pixelIt += x;
        TQ_UINT8 diff = devColorSpace->difference(source, pixelIt.rawData());

        if (diff >= m_threshold
            || (hasSelection && srcSel->selected(pixelIt.x(), pixelIt.y()) == MIN_SELECTED)) {
            delete segment;
            continue;
        }

        // Here as well: start the iterator at (0,y)
        KisHLineIteratorPixel selIt = selection->createHLineIterator(0, y, m_width, true);
        selIt += x;
        if (m_fuzzy)
            colorSpace->fromTQColor(TQt::white, MAX_SELECTED - diff, selIt.rawData());
        else
            colorSpace->fromTQColor(TQt::white, MAX_SELECTED, selIt.rawData());

        if (y > 0 && (map[m_width * (y - 1) + x] == None)) {
            map[m_width * (y - 1) + x] = Added;
            stack.push(new FillSegment(x, y-1));
        }
        if (y < (m_height - 1) && (map[m_width * (y + 1) + x] == None)) {
            map[m_width * (y + 1) + x] = Added;
            stack.push(new FillSegment(x, y+1));
        }

        ++pixelsDone;

        bool stop = false;

        --pixelIt;
        --selIt;
        --x;

        // go to the left
        while(!stop && x >= 0 && (map[m_width * y + x] != Checked) ) { // FIXME optimizeable?
            map[m_width * y + x] = Checked;
            diff = devColorSpace->difference(source, pixelIt.rawData());
            if (diff >= m_threshold
                || (hasSelection && srcSel->selected(pixelIt.x(), pixelIt.y()) == MIN_SELECTED)) {
                stop = true;
                continue;
            }

            if (m_fuzzy)
                colorSpace->fromTQColor(TQt::white, MAX_SELECTED - diff, selIt.rawData());
            else
                colorSpace->fromTQColor(TQt::white, MAX_SELECTED, selIt.rawData());

            if (y > 0 && (map[m_width * (y - 1) + x] == None)) {
                map[m_width * (y - 1) + x] = Added;
                stack.push(new FillSegment(x, y-1));
            }
            if (y < (m_height - 1) && (map[m_width * (y + 1) + x] == None)) {
                map[m_width * (y + 1) + x] = Added;
                stack.push(new FillSegment(x, y+1));
            }
            ++pixelsDone;
            --pixelIt;
            --selIt;
            --x;
        }

        x = segment->x + 1;
        delete segment;

        if (map[m_width * y + x] == Checked)
            continue;

        // and go to the right
        pixelIt = sourceDevice->createHLineIterator(x, y, m_width, false);
        selIt = selection->createHLineIterator(x, y, m_width, true);

        stop = false;
        while(!stop && x < m_width && (map[m_width * y + x] != Checked) ) {
            diff = devColorSpace->difference(source, pixelIt.rawData());
            map[m_width * y + x] = Checked;

            if (diff >= m_threshold
                || (hasSelection && srcSel->selected(pixelIt.x(), pixelIt.y()) == MIN_SELECTED) ) {
                stop = true;
                continue;
            }

            if (m_fuzzy)
                colorSpace->fromTQColor(TQt::white, MAX_SELECTED - diff, selIt.rawData());
            else
                colorSpace->fromTQColor(TQt::white, MAX_SELECTED, selIt.rawData());

            if (y > 0 && (map[m_width * (y - 1) + x] == None)) {
                map[m_width * (y - 1) + x] = Added;
                stack.push(new FillSegment(x, y-1));
            }
            if (y < (m_height - 1) && (map[m_width * (y + 1) + x] == None)) {
                map[m_width * (y + 1) + x] = Added;
                stack.push(new FillSegment(x, y+1));
            }
            ++pixelsDone;
            ++pixelIt;
            ++selIt;
            ++x;
        }

        if (m_size > 0) {
            progressPercent = (pixelsDone * 100) / m_size;
            if (progressPercent > currentPercent) {
                emit notifyProgress(progressPercent);
                currentPercent = progressPercent;
            }
        }
    }


    delete[] map;
    delete[] source;

    return selection;
}
