/*
 * Copyright (c) 2020, the SerenityOS developers.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <LibGUI/Action.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Button.h>
#include <LibGUI/CheckBox.h>
#include <LibGUI/Dialog.h>
#include <LibGUI/Menu.h>
#include <LibGUI/Painter.h>
#include <LibGUI/TextBox.h>
#include <LibGfx/Filters/BoxBlurFilter.h>
#include <LibGfx/Filters/GenericConvolutionFilter.h>
#include <LibGfx/Filters/GrayscaleFilter.h>
#include <LibGfx/Filters/InvertFilter.h>
#include <LibGfx/Filters/LaplacianFilter.h>
#include <LibGfx/Filters/SepiaFilter.h>
#include <LibGfx/Filters/SharpenFilter.h>
#include <LibGfx/Filters/SpatialGaussianBlurFilter.h>

namespace PixelPaint {

template<typename Filter>
struct FilterParameters {
};

template<size_t N>
class GenericConvolutionFilterInputDialog : public GUI::Dialog {
    C_OBJECT(GenericConvolutionFilterInputDialog);

public:
    Matrix<N, float> const& matrix() const { return m_matrix; }
    bool should_wrap() const { return m_should_wrap; }

private:
    explicit GenericConvolutionFilterInputDialog(GUI::Window* parent_window)
        : Dialog(parent_window)
    {
        // FIXME: Help! Make this GUI less ugly.
        StringBuilder builder;
        builder.appendff("{}x{}", N, N);
        builder.append(" Convolution"sv);
        set_title(builder.string_view());

        resize(200, 250);
        auto& main_widget = set_main_widget<GUI::Frame>();
        main_widget.set_frame_shape(Gfx::FrameShape::Container);
        main_widget.set_frame_shadow(Gfx::FrameShadow::Raised);
        main_widget.set_fill_with_background_color(true);
        auto& layout = main_widget.template set_layout<GUI::VerticalBoxLayout>();
        layout.set_margins(4);

        size_t index = 0;
        size_t columns = N;
        size_t rows = N;

        for (size_t row = 0; row < rows; ++row) {
            auto& horizontal_container = main_widget.template add<GUI::Widget>();
            horizontal_container.template set_layout<GUI::HorizontalBoxLayout>();
            for (size_t column = 0; column < columns; ++column) {
                if (index < columns * rows) {
                    auto& textbox = horizontal_container.template add<GUI::TextBox>();
                    textbox.set_min_width(22);
                    textbox.on_change = [&, row = row, column = column] {
                        auto& element = m_matrix.elements()[row][column];
                        char* endptr = nullptr;
                        auto value = strtof(textbox.text().characters(), &endptr);
                        if (endptr != nullptr)
                            element = value;
                        else
                            textbox.set_text(""sv);
                    };
                } else {
                    horizontal_container.template add<GUI::Widget>();
                }
            }
        }

        auto& norm_checkbox = main_widget.template add<GUI::CheckBox>("Normalize");
        norm_checkbox.set_checked(false);

        auto& wrap_checkbox = main_widget.template add<GUI::CheckBox>("Wrap");
        wrap_checkbox.set_checked(m_should_wrap);

        auto& button = main_widget.template add<GUI::Button>("Done");
        button.on_click = [&](auto) {
            m_should_wrap = wrap_checkbox.is_checked();
            if (norm_checkbox.is_checked())
                normalize(m_matrix);
            done(ExecResult::OK);
        };
    }

    Matrix<N, float> m_matrix {};
    bool m_should_wrap { false };
};

template<size_t N>
struct FilterParameters<Gfx::SpatialGaussianBlurFilter<N>> {
    static OwnPtr<typename Gfx::SpatialGaussianBlurFilter<N>::Parameters> get()
    {
        constexpr static ssize_t offset = N / 2;
        Matrix<N, float> kernel;
        auto sigma = 1.0f;
        auto s = 2.0f * sigma * sigma;

        for (auto x = -offset; x <= offset; x++) {
            for (auto y = -offset; y <= offset; y++) {
                auto r = sqrtf(x * x + y * y);
                kernel.elements()[x + offset][y + offset] = (expf(-(r * r) / s)) / (float { M_PI } * s);
            }
        }

        normalize(kernel);

        return make<typename Gfx::GenericConvolutionFilter<N>::Parameters>(kernel);
    }
};

template<>
struct FilterParameters<Gfx::SharpenFilter> {
    static OwnPtr<Gfx::GenericConvolutionFilter<3>::Parameters> get()
    {
        return make<Gfx::GenericConvolutionFilter<3>::Parameters>(Matrix<3, float>(0, -1, 0, -1, 5, -1, 0, -1, 0));
    }
};

template<>
struct FilterParameters<Gfx::LaplacianFilter> {
    static OwnPtr<Gfx::GenericConvolutionFilter<3>::Parameters> get(bool diagonal)
    {
        if (diagonal)
            return make<Gfx::GenericConvolutionFilter<3>::Parameters>(Matrix<3, float>(-1, -1, -1, -1, 8, -1, -1, -1, -1));

        return make<Gfx::GenericConvolutionFilter<3>::Parameters>(Matrix<3, float>(0, -1, 0, -1, 4, -1, 0, -1, 0));
    }
};

template<size_t N>
struct FilterParameters<Gfx::GenericConvolutionFilter<N>> {
    static OwnPtr<typename Gfx::GenericConvolutionFilter<N>::Parameters> get(GUI::Window* parent_window)
    {
        auto input = GenericConvolutionFilterInputDialog<N>::construct(parent_window);
        input->exec();
        if (input->result() == GUI::Dialog::ExecResult::OK)
            return make<typename Gfx::GenericConvolutionFilter<N>::Parameters>(input->matrix(), input->should_wrap());

        return {};
    }
};

template<size_t N>
struct FilterParameters<Gfx::BoxBlurFilter<N>> {
    static OwnPtr<typename Gfx::GenericConvolutionFilter<N>::Parameters> get()
    {
        Matrix<N, float> kernel;

        for (size_t i = 0; i < N; ++i) {
            for (size_t j = 0; j < N; ++j) {
                kernel.elements()[i][j] = 1;
            }
        }

        normalize(kernel);

        return make<typename Gfx::GenericConvolutionFilter<N>::Parameters>(kernel);
    }
};

}
