/*
 *  Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
 *
 *  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 <qrect.h>
#include <qcheckbox.h>

#include <kdebug.h>

#include <kis_brush.h>
#include <kis_debug_areas.h>
#include <kis_paint_device.h>
#include <kis_painter.h>
#include <kis_types.h>
#include <kis_paintop.h>
#include <kis_iterators_pixel.h>
#include <kis_layer.h>
#include <kis_meta_registry.h>
#include <kis_colorspace_factory_registry.h>
#include "kis_input_device.h"

#include "kis_wetop.h"
#include "kis_wet_colorspace.h"

KisWetOpSettings::KisWetOpSettings(QWidget *parent)
    : super(parent)
{
    m_options = new WetPaintOptions(parent, "wet option widget");
}

bool KisWetOpSettings::varySize() const
{
    return m_options->checkSize->isChecked();
}

bool KisWetOpSettings::varyWetness() const
{
    return m_options->checkWetness->isChecked();
}

bool KisWetOpSettings::varyStrength() const
{
    return m_options->checkStrength->isChecked();
}

KisPaintOp * KisWetOpFactory::createOp(const KisPaintOpSettings *settings, KisPainter * painter)
{
    const KisWetOpSettings *wetopSettings = dynamic_cast<const KisWetOpSettings *>(settings);
     Q_ASSERT(settings == 0 || wetopSettings != 0);

    KisPaintOp * op = new KisWetOp(wetopSettings, painter);
     Q_CHECK_PTR(op);
    return op;
}

KisPaintOpSettings* KisWetOpFactory::settings(QWidget * parent, const KisInputDevice& inputDevice)
{
    if (inputDevice == KisInputDevice::mouse()) {
        // No options for mouse, only tablet devices
        return 0;
    } else {
        return new KisWetOpSettings(parent);
    }
}

KisWetOp::KisWetOp(const KisWetOpSettings * settings, KisPainter * painter)
    : super(painter)
{
    if (settings) {
        m_size = settings->varySize();
        m_wetness = settings->varyWetness();
        m_strength = settings->varyStrength();
    } else {
        m_size = false;
        m_wetness = false;
        m_strength = false;
    }
}

KisWetOp::~KisWetOp()
{
}

void KisWetOp::paintAt(const KisPoint &pos, const KisPaintInformation& info)
{
    if (!m_painter) return;

    if (!m_painter->device()) return;
    KisPaintDeviceSP device = m_painter->device();
    if (!m_painter->device()) return;

    KisBrush *brush = m_painter->brush();
     Q_ASSERT(brush);

    if (! brush->canPaintFor(info) )
        return;

    KisPaintInformation inf(info);

    if (!m_size)
        inf.pressure = PRESSURE_DEFAULT;

    KisPaintDeviceSP dab = 0;

    if (brush->brushType() == IMAGE || brush->brushType() == PIPE_IMAGE) {
        dab = brush->image(KisMetaRegistry::instance()->csRegistry()->getAlpha8(), inf);
    }
    else {
        KisAlphaMaskSP mask = brush->mask(inf);
        dab = computeDab(mask, KisMetaRegistry::instance()->csRegistry()->getAlpha8());
    }

    KisColorSpace * cs = device->colorSpace();

    if (cs->id() != KisID("WET","")) {
        kdDebug(DBG_AREA_CMS) << "You cannot paint wet paint on dry pixels.\n";
        return;
    }

    KisColor paintColor = m_painter->paintColor();
    paintColor.convertTo(cs);
    // hopefully this does
    // nothing, conversions are bad ( wet->rgb->wet gives horrible mismatches, due to
    // the conversion to rgb actually rendering the paint above white

    WetPack* paintPack = reinterpret_cast<WetPack*>(paintColor.data());
    WetPix paint = paintPack->paint;

    // Get the paint info (we store the strength in the otherwise unused (?) height field of
    // the paint
    // double wetness = paint.w; // XXX: Was unused
    // strength is a double in the 0 - 2 range, but upscaled to Q_UINT16:
    //kdDebug() << "Original strength as in paint.h: " << paint.h << endl;

    double strength = 2.0 * static_cast<double>(paint.h) / (double)(0xffff);

    //kdDebug() << "Before strength: " << strength << endl;

    if (m_strength)
        strength = strength * (strength + info.pressure) * 0.5;
    else
        strength = strength * (strength + PRESSURE_DEFAULT) * 0.5;

    double pressure = 0.75 + 0.25 * info.pressure;

    //kdDebug() << "info.pressure " << info.pressure << ", local pressure: " << pressure << ", strength: " << strength << endl;

    WetPack currentPack;
    WetPix currentPix;
    double eff_height;
    double press, contact;

    int maskW = brush->maskWidth(inf);
    int maskH = brush->maskHeight(inf);
    KoPoint dest = (pos - (brush->hotSpot(inf)));
    int xStart = (int)dest.x();
    int yStart = (int)dest.y();

    for (int y = 0; y < maskH; y++) {
        KisHLineIteratorPixel dabIt = dab->createHLineIterator(0, y, maskW, false);
        KisHLineIteratorPixel it = device->createHLineIterator(xStart, yStart+y, maskW, true);

        while (!dabIt.isDone()) {
            // This only does something with .paint, and not with adsorb.
            currentPack = *(reinterpret_cast<WetPack*>(it.rawData()));
            WetPix currentData = currentPack.adsorb;
            currentPix = currentPack.paint;

            // Hardcoded threshold for the dab 'strength': above it, it will get painted
            if (*dabIt.rawData() > 125)
                press = pressure * 0.25;
            else
                press = -1;
            //kdDebug() << "After mysterious line, press becomes: " << press << ", this is the same as in the orignal. Good" << endl;
            // XXX - 192 is probably only useful for paper with a texture...
            eff_height = (currentData.h + currentData.w - 192.0) * (1.0 / 255.0);
            contact = (press + eff_height) * 0.2;
            double old_contact = contact;
            if (contact > 0.5)
                contact = 1.0 - 0.5 * exp(-2.0 * contact - 1.0);

            //kdDebug() << "Contact was " << old_contact << " and has become: " << contact << endl;
            if (contact > 0.0001) {
                int v;
                double rnd = rand() * (1.0 / RAND_MAX);

                v = currentPix.rd;
                currentPix.rd = floor(v + (paint.rd * strength - v) * contact + rnd);
                //kdDebug() << "Rd was " << v << " and has become " << currentPix.rd << endl;
                v = currentPix.rw;
                currentPix.rw = floor(v + (paint.rw * strength - v) * contact + rnd);
                v = currentPix.gd;
                currentPix.gd = floor(v + (paint.gd * strength - v) * contact + rnd);
                v = currentPix.gw;
                currentPix.gw = floor(v + (paint.gw * strength - v) * contact + rnd);
                v = currentPix.bd;
                currentPix.bd = floor(v + (paint.bd * strength - v) * contact + rnd);
                v = currentPix.bw;
                currentPix.bw = floor(v + (paint.bw * strength - v) * contact + rnd);
                v = currentPix.w;
                if (m_wetness)
                    currentPix.w = (CLAMP(floor(
                          v + (paint.w * (0.5 + pressure) - v) * contact + rnd), 0, 512));
                else
                    currentPix.w = floor(v + (paint.w - v) * contact + rnd);

                currentPack.paint = currentPix;
                *(reinterpret_cast<WetPack*>(it.rawData())) = currentPack;
            }
            ++dabIt;
            ++it;
        }
    }

    m_painter->addDirtyRect(QRect(xStart, yStart, maskW, maskH));
}
