﻿/*
    Simple Graphics Framework (SGF) - implementation of the features offered
    by the framework.

    Copyright 2023 Arnold Beiland

    Simple Graphics Framework 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.

    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 (look for a file named COPYING in the top directory). If not, see
    <https://www.gnu.org/licenses/>.
*/

#include <cmath>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
#include <exception>
#include <cstring>
#include <FL/Enumerations.H>
#include <FL/Fl.H>
#include <FL/Fl_Group.H>
#include <FL/Fl_Input_.H>
#include <FL/Fl_Widget.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Light_Button.H>
#include <FL/Fl_Input.H>
#include <FL/fl_draw.H>
#include <FL/fl_types.h>
#include "sgf.h"

std::ofstream logfile("logfile.txt");

namespace sgf
{
    class DrawDestination
    {
        public:
            struct TextInfo {
                std::string text;
                int row;
                int col;
                Color color;
                int size;
            };

            unsigned char *data;
            int width, height;
            std::vector<TextInfo> texts;

            DrawDestination(unsigned char *data, int width, int height);

            void draw_pixel(int row, int col, Color color);
            void draw_text(int row, int col, Color color, int size, const char *text);
    };

    class Drawing_Area : public Fl_Box
    {
        public:
            Drawing_Area(int x, int y, int w, int h);
            void draw() override;
            int handle(int event) override;
    };
    class App
    {
        private:
            Drawing_Area *drawing_area;

        public:
            App();
            ~App();
            int run(int argc, char **argv);
    };

    std::unique_ptr<DrawDestination> current_draw_destination;

    DrawDestination::DrawDestination(unsigned char *data, int width, int height):
        data(data), width(width), height(height)
    {
        if (height <= 0 || width <= 0)
            throw new std::invalid_argument("width and height have to be positive");

        for (int j = 0; j < width; ++j) {
            *(data + j*3) = '\xFF';
            *(data + j*3+1) = '\xFF';
            *(data + j*3+2) = '\xFF';
        }

        for (int i = 1; i < height; ++i)
            std::memcpy(data + 3*width*i, data, width*3);

    }

    void DrawDestination::draw_pixel(int row, int col, Color color)
    {
        if (row < 0 || col < 0 || col >= width || row >= height) {
            logfile << "incorrect draw_pixel call: "
                    << "row=" << row
                    << ", col=" << col
                    << ", height=" << height
                    << ", width=" << width << "\n"
                    << std::flush;
            return;
        }

        unsigned char *target = data + 3*width*row + 3*col;
        *(target) = color.r;
        *(target+1) = color.g;
        *(target+2) = color.b;
    }

    void DrawDestination::draw_text(int row, int col, Color color, int size, const char *text)
    {
        if (row < 0 || col < 0 || col >= width || row >= height) {
            logfile << "incorrect draw_text call: "
                    << "row=" << row
                    << ", col=" << col
                    << ", height=" << height
                    << ", width=" << width << "\n"
                    << std::flush;
            return;
        }

        DrawDestination::TextInfo info;
        info.text = text;
        info.row = row;
        info.col = col;
        info.color = color;
        info.size = size;

        texts.push_back(info);
    }

    Drawing_Area::Drawing_Area(int x, int y, int w, int h): Fl_Box(x, y, w, h)
    {
    }

    void Drawing_Area::draw()
    {
        Fl_Box::draw();

        int width = w();
        int height = h();
        int buffer_size = width * height * 3;
        auto buff = new unsigned char[buffer_size];

        current_draw_destination = std::unique_ptr<DrawDestination>(new DrawDestination(
            buff,
            width,
            height));

        render(width, height);

        fl_draw_image(buff, x(), y(), width, height, 3);


        for (const auto &t : current_draw_destination->texts) {
            fl_color(fl_rgb_color(t.color.r, t.color.g, t.color.b));
            fl_font(fl_font(), t.size);
            fl_draw(t.text.c_str(), x() + t.col, y() + t.row);
        }

        delete[] buff;
    }

    int Drawing_Area::handle(int event)
    {
        int result = 1;

        if (event == FL_DRAG || event == FL_MOVE || event == FL_ENTER) {
            if (on_move(Fl::event_y() - y(), Fl::event_x() - x()))
                redraw();
        }
        else if (event == FL_PUSH && Fl::event_button() == FL_LEFT_MOUSE) {
            if (on_mouse_down())
                redraw();
        }
        else if (event == FL_RELEASE && Fl::event_button() == FL_LEFT_MOUSE) {
            if (on_mouse_up())
                redraw();
        }
        else if (event == FL_MOUSEWHEEL) {
            if (on_scroll(Fl::event_dy()))
                redraw();
        }
        else if (event == FL_FOCUS) {
            // Let the function return 1.
            // Reacting to the FL_FOCUS event ensures that we will
            // get the FL_KEYDOWN events.
        }
        else if (event == FL_KEYDOWN) {
            if (on_key_down(Fl::event_key()))
                redraw();
        }
        else if (event == FL_KEYUP) {
            if (on_key_up(Fl::event_key()))
                redraw();
        }
        else {
            result = 0;
        }

        return result;
    }

    App::App()
    {
    }

    App::~App()
    {
    }

    int App::run(int argc, char **argv)
    {
        const int initial_width = 1000;
        const int initial_height = 700;

        auto window = new Fl_Double_Window(initial_width, initial_height, argv[0]);

        drawing_area = new Drawing_Area(
            0,
            0,
            initial_width,
            initial_height);

        window->color(fl_rgb_color(200, 200, 200));
        window->resizable(drawing_area);
        window->end();

        Fl::visual(FL_RGB);
        window->show(argc, argv);
        return Fl::run();
    }
}

// implementations for API functions defined in sgf.h

void draw_pixel(int row, int col, Color color)
{
    if (sgf::current_draw_destination)
        sgf::current_draw_destination->draw_pixel(row, col, color);
    else
        throw std::runtime_error("Nothing can be drawn now.");
}

void draw_text(int row, int col, Color color, int size, const char *text)
{
    if (sgf::current_draw_destination)
        sgf::current_draw_destination->draw_text(row, col, color, size, text);
    else
        throw std::runtime_error("Nothing can be drawn now.");
}

// entry point

int main(int argc, char **argv)
{
    initialize();
    sgf::App app;
    return app.run(argc, argv);
}
