/*
 *  This file is part of RawTherapee.
 *
 *  Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
 *
 *  RawTherapee 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  RawTherapee 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 RawTherapee.  If not, see <https://www.gnu.org/licenses/>.
 */

#include "lockablecolorpicker.h"
#include "options.h"
#include "../rtengine/color.h"
#include "../rtengine/improcfun.h"
#include "../rtengine/rt_math.h"
#include "../rtengine/utils.h"
#include "imagearea.h"
#include "multilangmgr.h"
#include "navigator.h"

namespace
{

const rtengine::procparams::ColorManagementParams DEFAULT_CMP;

}

LockableColorPicker::LockableColorPicker (CropWindow* cropWindow, rtengine::procparams::ColorManagementParams *color_management_params)
: cropWindow(cropWindow), displayedValues(ColorPickerType::RGB), position(0, 0), size(Size::S15),
  color_management_params(color_management_params), validity(Validity::OUTSIDE),
  r(0.f), g(0.f), b(0.f), rpreview(0.f), gpreview(0.f), bpreview(0.f), hue(0.f), sat(0.f), val(0.f), L(0.f), a(0.f), bb(0.f)
{}

void LockableColorPicker::updateBackBuffer ()
{
    int newW, newH;

    // -------------------- setting some key constants ---------------------
    constexpr double circlePadding = 3.0;  // keep this value odd
    constexpr double opacity = 0.62;
    // ---------------------------------------------------------------------

    if (validity == Validity::INSIDE) {
        Gtk::DrawingArea *iArea = cropWindow->getImageArea();

        Glib::RefPtr<Pango::Context> pangoContext = iArea->get_pango_context ();
        Pango::FontDescription fontd = iArea->get_style_context()->get_font();
        // set font family and size
        fontd.set_family(options.CPFontFamily == "default" ? "sans" : options.CPFontFamily);
        const int fontSize = options.CPFontFamily == "default" ? 8 : options.CPFontSize; // pt
        // Non-absolute size is defined in "Pango units" and shall be multiplied by
        // Pango::SCALE from "pt":
        fontd.set_size (fontSize * Pango::SCALE);
        fontd.set_weight(Pango::WEIGHT_NORMAL);
        pangoContext->set_font_description (fontd);

        Glib::RefPtr<Pango::Layout> layout[3][2];
        Glib::ustring s1, s2, s3;
        PointerMotionListener* navigator = cropWindow->getPointerMotionListener ();

        switch (displayedValues) {
        case ColorPickerType::RGB:
            navigator->getRGBText ((int)(r*255.f), (int)(g*255.f), (int)(b*255.f), s1, s2, s3);
            layout[0][0] = iArea->create_pango_layout(M("NAVIGATOR_R"));
            layout[0][1] = iArea->create_pango_layout(s1);
            layout[1][0] = iArea->create_pango_layout(M("NAVIGATOR_G"));
            layout[1][1] = iArea->create_pango_layout(s2);
            layout[2][0] = iArea->create_pango_layout(M("NAVIGATOR_B"));
            layout[2][1] = iArea->create_pango_layout(s3);
            break;
        case ColorPickerType::HSV:
            navigator->getHSVText (hue, sat, val, s1, s2, s3);
            layout[0][0] = iArea->create_pango_layout(M("NAVIGATOR_H"));
            layout[0][1] = iArea->create_pango_layout(s1);
            layout[1][0] = iArea->create_pango_layout(M("NAVIGATOR_S"));
            layout[1][1] = iArea->create_pango_layout(s2);
            layout[2][0] = iArea->create_pango_layout(M("NAVIGATOR_V"));
            layout[2][1] = iArea->create_pango_layout(s3);
            break;
        case ColorPickerType::LAB:
        default:
            navigator->getLABText (L, a, bb, s1, s2, s3);
            layout[0][0] = iArea->create_pango_layout(M("NAVIGATOR_LAB_L"));
            layout[0][1] = iArea->create_pango_layout(s1);
            layout[1][0] = iArea->create_pango_layout(M("NAVIGATOR_LAB_A"));
            layout[1][1] = iArea->create_pango_layout(s2);
            layout[2][0] = iArea->create_pango_layout(M("NAVIGATOR_LAB_B"));
            layout[2][1] = iArea->create_pango_layout(s3);
        }

        int w00, w01, w10, w11, w20, w21, h00, h01, h10, h11, h20, h21;
        layout[0][0]->get_pixel_size(w00, h00);
        layout[1][0]->get_pixel_size(w10, h10);
        layout[2][0]->get_pixel_size(w20, h20);
        layout[0][1]->get_pixel_size(w01, h01);
        layout[1][1]->get_pixel_size(w11, h11);
        layout[2][1]->get_pixel_size(w21, h21);
        int maxWCol0 = rtengine::max(w00, w10, w20);
        int maxWCol1 = rtengine::max(w01, w11, w21);
        int maxHRow0 = rtengine::max(h00, h01);
        int maxHRow1 = rtengine::max(h10, h11);
        int maxHRow2 = rtengine::max(h20, h21);

        // -------------------- setting some key constants ---------------------
        constexpr int textPadding = 3;
        const int textWidth = maxWCol0 + maxWCol1 + textPadding;
        const int textHeight = maxHRow0 + maxHRow1 + maxHRow2 + 2*textPadding;
        // ---------------------------------------------------------------------

        newW = rtengine::max<int>((int)size + 2 * circlePadding, textWidth + 2 * textPadding);
        newH = (int)size + 2 * circlePadding + textHeight + 2 * textPadding;

        setDrawRectangle(Cairo::FORMAT_ARGB32, 0, 0, newW, newH, true);

        Cairo::RefPtr<Cairo::Context> bbcr = BackBuffer::getContext();

        // cleaning the back buffer
        bbcr->set_source_rgba (0., 0., 0., 0.);
        bbcr->set_operator (Cairo::OPERATOR_CLEAR);
        bbcr->paint ();
        bbcr->set_operator (Cairo::OPERATOR_OVER);

        bbcr->set_antialias (Cairo::ANTIALIAS_SUBPIXEL);
        bbcr->set_line_width (0.);

        double center = static_cast<double>(size) / 2.0 + circlePadding;

        // black background of the whole color picker
        bbcr->set_line_width (0.);
        bbcr->set_source_rgba (0., 0., 0., opacity);
        bbcr->arc_negative (center, center, center, 0., rtengine::RT_PI);
        bbcr->line_to (0, 2. * center + textHeight);
        bbcr->arc_negative (2. * textPadding, 2. * center + textHeight, 2. * textPadding, rtengine::RT_PI, rtengine::RT_PI / 2.);
        bbcr->line_to (textWidth, 2. * center + textHeight + 2. * textPadding);
        bbcr->arc_negative (textWidth, 2. * center + textHeight, 2. * textPadding, rtengine::RT_PI / 2., 0.);
        bbcr->line_to (textWidth + 2. * textPadding, 2. * center + 2. * textPadding);
        bbcr->arc_negative (textWidth, 2. * center + 2. * textPadding, 2. * textPadding, 0., rtengine::RT_PI * 1.5);
        bbcr->line_to (2. * center, 2. * center);
        bbcr->close_path();
        bbcr->set_line_join (Cairo::LINE_JOIN_BEVEL);
        bbcr->set_line_cap (Cairo::LINE_CAP_SQUARE);
        bbcr->fill ();

        // light grey circle around the color mark
        bbcr->arc (center, center, center - circlePadding / 2., 0., 2. * (double)rtengine::RT_PI);
        bbcr->set_source_rgb (0.75, 0.75, 0.75);
        bbcr->set_line_width (circlePadding - 2.);
        bbcr->stroke ();

        // spot disc with picked color
        bbcr->arc (center, center, center - circlePadding, 0., 2. * (double)rtengine::RT_PI);
        bbcr->set_source_rgb (rpreview, gpreview, bpreview);  // <- set the picker color here
        bbcr->set_line_width (0.);
        bbcr->fill();

        // adding the font
        bbcr->set_line_width (0.);
        bbcr->set_line_join (Cairo::LINE_JOIN_ROUND);
        bbcr->set_line_cap (Cairo::LINE_CAP_ROUND);
        bbcr->set_source_rgb (1., 1., 1.);
        double txtOffsetX = textPadding;
        double txtOffsetY = (double)size + 2. * circlePadding + textPadding;
        switch (iArea->get_direction()) {
        case Gtk::TEXT_DIR_RTL:
            bbcr->move_to (txtOffsetX                         , txtOffsetY);
            layout[0][1]->add_to_cairo_context (bbcr);
            bbcr->fill ();
            bbcr->move_to (txtOffsetX + maxWCol1 + textPadding, txtOffsetY);
            layout[0][0]->add_to_cairo_context (bbcr);
            bbcr->fill ();

            bbcr->move_to (txtOffsetX                         , txtOffsetY + maxHRow0 + textPadding);
            layout[1][1]->add_to_cairo_context (bbcr);
            bbcr->fill ();
            bbcr->move_to (txtOffsetX + maxWCol1 + textPadding, txtOffsetY + maxHRow0 + textPadding);
            layout[1][0]->add_to_cairo_context (bbcr);
            bbcr->fill ();

            bbcr->move_to (txtOffsetX                         , txtOffsetY + maxHRow0 + maxHRow1 + 2*textPadding);
            layout[2][1]->add_to_cairo_context (bbcr);
            bbcr->fill ();
            bbcr->move_to (txtOffsetX + maxWCol1 + textPadding, txtOffsetY + maxHRow0 + maxHRow1 + 2*textPadding);
            layout[2][0]->add_to_cairo_context (bbcr);
            bbcr->fill ();
            break;
        default:
            bbcr->move_to (txtOffsetX                         , txtOffsetY);
            layout[0][0]->add_to_cairo_context (bbcr);
            bbcr->fill ();
            bbcr->move_to (txtOffsetX + maxWCol0 + textPadding, txtOffsetY);
            layout[0][1]->add_to_cairo_context (bbcr);
            bbcr->fill ();

            bbcr->move_to (txtOffsetX                         , txtOffsetY + maxHRow0 + textPadding);
            layout[1][0]->add_to_cairo_context (bbcr);
            bbcr->fill ();
            bbcr->move_to (txtOffsetX + maxWCol0 + textPadding, txtOffsetY + maxHRow0 + textPadding);
            layout[1][1]->add_to_cairo_context (bbcr);
            bbcr->fill ();

            bbcr->move_to (txtOffsetX                         , txtOffsetY + maxHRow0 + maxHRow1 + 2*textPadding);
            layout[2][0]->add_to_cairo_context (bbcr);
            bbcr->fill ();
            bbcr->move_to (txtOffsetX + maxWCol0 + textPadding, txtOffsetY + maxHRow0 + maxHRow1 + 2*textPadding);
            layout[2][1]->add_to_cairo_context (bbcr);
            bbcr->fill ();
        }

        anchorOffset.set (center, center);

        setDirty (false);
    } else if (validity == Validity::CROSSING) {
        newH = newW = (int)size + 2 * circlePadding;

        setDrawRectangle(Cairo::FORMAT_ARGB32, 0, 0, newW, newH, true);

        Cairo::RefPtr<Cairo::Context> bbcr = BackBuffer::getContext();

        // cleaning the back buffer
        bbcr->set_source_rgba (0., 0., 0., 0.);
        bbcr->set_operator (Cairo::OPERATOR_CLEAR);
        bbcr->paint ();
        bbcr->set_operator (Cairo::OPERATOR_OVER);

        bbcr->set_antialias(Cairo::ANTIALIAS_SUBPIXEL);

        double center = static_cast<double>(size) / 2. + circlePadding;

        // light grey circle around the color mark
        bbcr->arc (center, center, center - circlePadding / 2., 0., 2. * (double)rtengine::RT_PI);
        bbcr->set_source_rgba (0., 0., 0., opacity);
        bbcr->set_line_width(circlePadding);
        bbcr->stroke_preserve();
        bbcr->set_source_rgb (0.75, 0.75, 0.75);
        bbcr->set_line_width (circlePadding - 2.);
        bbcr->stroke ();

        anchorOffset.set (center, center);

        setDirty (false);
    }

}

void LockableColorPicker::draw (const Cairo::RefPtr<Cairo::Context> &cr)
{
    if (validity == Validity::OUTSIDE) {
        return;
    }

    if (isDirty()) {
        updateBackBuffer();
    }
    int px, py;
    cropWindow->imageCoordToScreen(position.x, position.y, px, py);
    setDestPosition(px - anchorOffset.x, py - anchorOffset.y);

    copySurface(cr);
}

void LockableColorPicker::setPosition (const rtengine::Coord &newPos)
{
    // we're not checking bounds here, this will be done at rendering time
    position = newPos;
}

void LockableColorPicker::setRGB (const float R, const float G, const float B, const float previewR, const float previewG, const float previewB)
{
    if (r==R && g==G && b==B) {
        return;
    }

    r = R;
    g = G;
    b = B;

    rpreview = previewR;
    gpreview = previewG;
    bpreview = previewB;

    rtengine::Color::rgb2hsv01(r, g, b, hue, sat, val);
    rtengine::ImProcFunctions::rgb2lab(
        static_cast<std::uint8_t>(255 * r),
        static_cast<std::uint8_t>(255 * g),
        static_cast<std::uint8_t>(255 * b),
        L, a, bb,
        color_management_params != nullptr ? *color_management_params : DEFAULT_CMP,
        true);
    L /= 327.68f;
    a /= 327.68f;
    bb /= 327.68f;

    if (validity != Validity::OUTSIDE) {
        setDirty(true);
    }
}

void LockableColorPicker::getImagePosition (rtengine::Coord &imgPos) const
{
    imgPos = position;
}

void LockableColorPicker::getScreenPosition (rtengine::Coord &screenPos) const
{
    if (cropWindow) {
        cropWindow->imageCoordToScreen(position.x, position.y, screenPos.x, screenPos.y);
    }
}

bool LockableColorPicker::isOver (int x, int y)
{
    if (!cropWindow) {
        return false;
    }
    rtengine::Coord pickerScreenPos;
    cropWindow->imageCoordToScreen(position.x, position.y, pickerScreenPos.x, pickerScreenPos.y);

    rtengine::Coord mousePos(x, y);
    rtengine::Coord wh(getWidth(), getHeight());
    rtengine::Coord tl(pickerScreenPos - anchorOffset);
    rtengine::Coord br(tl + wh);
    return mousePos >= tl && mousePos <= br;
}

void LockableColorPicker::setValidity (Validity validity)
{
    if (this->validity != validity) {
        setDirty(true);
    }
    this->validity = validity;
}

void LockableColorPicker::setSize (Size newSize)
{
    if (size != newSize)
    {
        size = newSize;
        setDirty(true);
    }
}

LockableColorPicker::Size LockableColorPicker::getSize () const
{
    return size;
}

void LockableColorPicker::rollDisplayedValues ()
{
    if (displayedValues < ColorPickerType::LAB) {
        displayedValues = (ColorPickerType)(rtengine::toUnderlying(displayedValues) + 1);
    } else {
        displayedValues = ColorPickerType::RGB;
    }
    setDirty(true);

}

bool LockableColorPicker::incSize ()
{
    if (size < Size::S30) {
        size = (Size)(rtengine::toUnderlying(size) + 5);
        setDirty(true);
        return true;
    }
    return false;
}

bool LockableColorPicker::decSize ()
{
    if (size > Size::S5) {
        size = (Size)(rtengine::toUnderlying(size) - 5);
        setDirty(true);
        return true;
    }
    return false;
}

// return true if the picker has to be redrawn
bool LockableColorPicker::cycleRGB ()
{
    if (displayedValues == ColorPickerType::RGB) {
        setDirty (true);
        return true;
    }
    return false;
}

// return true if the picker has to be redrawn
bool LockableColorPicker::cycleHSV ()
{
    if (displayedValues == ColorPickerType::HSV) {
        setDirty (true);
        return true;
    }
    return false;
}
