/*
 *  Copyright (c) 2000-2022 Inria
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 *  * Neither the name of the ALICE Project-Team nor the names of its
 *  contributors may be used to endorse or promote products derived from this
 *  software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *
 *  Contact: Bruno Levy
 *
 *     https://www.inria.fr/fr/bruno-levy
 *
 *     Inria,
 *     Domaine de Voluceau,
 *     78150 Le Chesnay - Rocquencourt
 *     FRANCE
 *
 */

#include <geogram_gfx/gui/simple_application.h>
#include <geogram_gfx/basic/GLSL.h>
#include <geogram/basic/command_line.h>
#include <geogram/basic/file_system.h>
#include <geogram/basic/stopwatch.h>
#include <algorithm>
#include <map>

extern void register_embedded_glsl_files(GEO::FileSystem::MemoryNode* n);

namespace {

    using namespace GEO;

    /**
     * \brief A super-simple shader authoring application.
     */
    class GeoShadeApplication : public SimpleApplication {
    public:

        /**
         * \brief GeoShadeApplication constructor.
         */
        GeoShadeApplication() : SimpleApplication("GeoShade") {
            builtin_files_ = new FileSystem::MemoryNode();
            set_default_filename("hello.glsl");
            use_text_editor_ = true;
            set_region_of_interest(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
            register_embedded_glsl_files(builtin_files_);
            glsl_program_ = 0;
            three_D_ = false;
            glsl_frame_ = 0;
            glsl_start_time_ = Stopwatch::now();
            object_properties_visible_ = false;
            new_file();
        }

        /**
         * \brief GeoShadeApplication destructor.
         */
        ~GeoShadeApplication() override {
            if(glsl_program_ != 0) {
                glDeleteProgram(glsl_program_);
                glsl_program_ = 0;
            }
        }


        /**
         * \copydoc SimpleApplication::geogram_initialize()
         */
        void geogram_initialize(int argc, char** argv) override {
            SimpleApplication::geogram_initialize(argc, argv);
            if(phone_screen_) {
                viewer_properties_visible_ = false;
            }
        }


        /**
         * \brief Displays and handles the GUI for object properties.
         * \details Overloads Application::draw_object_properties().
         */
        void draw_object_properties() override {
            // TODO
        }

        /**
         * \brief Draws the scene according to currently set primitive and
         *  drawing modes.
         */
        void draw_scene() override {

            if(glsl_program_ != 0) {
                if(animate()) {

                    glUseProgram(glsl_program_);

                    // If shader has an iTime uniform (e.g. a ShaderToy shader),
                    // then update it, and indicate that graphics should be
                    // updated again and again (call update() at each frame).
                    GLint iTime_loc = glGetUniformLocation(
                        glsl_program_, "iTime"
                    );
                    if(iTime_loc != -1) {
                        glUniform1f(
                            iTime_loc,
                            float(Stopwatch::now() - glsl_start_time_)
                        );
                    }

                    GLint iFrame_loc = glGetUniformLocation(
                        glsl_program_, "iFrame"
                    );
                    if(iFrame_loc != -1) {
                        glUniform1f(
                            iFrame_loc, float(glsl_frame_)
                        );
                    }

                    GLint iDate_loc = glGetUniformLocation(
                        glsl_program_, "iDate"
                    );

                    if(iDate_loc != -1) {
                        time_t t = time(nullptr);
                        struct tm* tm = localtime(&t);
                        float datex = float(tm->tm_year + 1900);
                        float datey = float(tm->tm_mon);
                        float datez = float(tm->tm_mday);
                        float datew = float(tm->tm_sec) +
                            60.0f * float(tm->tm_min) +
                            3600.0f * float(tm->tm_hour);
                        glUniform4f(iDate_loc, datex, datey, datez, datew);
                    }

                    glUseProgram(0);

                    ++glsl_frame_;

                    // Of course, there is a much faster way of drawing a
                    // quad, but here we do not care about this (tiny) loss
                    // of performance.

                    glupDisable(GLUP_VERTEX_COLORS);
                    glupEnable(GLUP_TEXTURING);
                    glupUseProgram(glsl_program_);
                    glupBegin(GLUP_TRIANGLES);

                    glupTexCoord2d(0.0, 0.0);
                    glupVertex2d(  0.0, 0.0);

                    glupTexCoord2d(1.0, 1.0);
                    glupVertex2d(  1.0, 1.0);

                    glupTexCoord2d(0.0, 1.0);
                    glupVertex2d(  0.0, 1.0);


                    glupTexCoord2d(1.0, 1.0);
                    glupVertex2d(  1.0, 1.0);

                    glupTexCoord2d(0.0, 0.0);
                    glupVertex2d(  0.0, 0.0);

                    glupTexCoord2d(1.0, 0.0);
                    glupVertex2d(  1.0, 0.0);

                    glupEnd();
                    glupUseProgram(0);
                    glupDisable(GLUP_TEXTURING);
                }
            }
        }

        void new_file() {
            text_editor_.clear();
            text_editor_.load_data(
                "void mainImage(\n"
                "   out vec4 col,in vec2 coord\n"
                ") {\n"
                "   col.r = coord.x /\n"
                "               iResolution.x;\n"
                "   col.g = coord.y /\n"
                "               iResolution.y;\n"
                "   col.b = 0.5*(col.x+col.y);\n"
                "}\n"
            );
            text_editor_visible_ = true;
        }

        virtual void draw_fileops_menu() override {
            if(ImGui::MenuItem(
                   (icon_UTF8("play-circle") + " Run program").c_str(),
                   phone_screen_ ? nullptr : "[F5]"
               )) {
                run_program();
            }
            if(ImGui::MenuItem(
                   (icon_UTF8("stop-circle") + "Stop program").c_str()
               )) {
                stop_program();
            }
            ImGui::Separator();
            if(ImGui::MenuItem(icon_UTF8("file")+" New...")) {
                new_file();
                current_file_ = "";
            }
            if(phone_screen_) {
                draw_load_menu();
                draw_save_menu();
            }
            ImGui::Separator();
            if(ImGui::MenuItem(icon_UTF8("folder-open")+"Load example...")) {
                ImGui::OpenFileDialog(
                    "##load_dlg",
                    supported_read_file_extensions().c_str(),
                    filename_,
                    ImGuiExtFileDialogFlags_Load,
                    builtin_files_
                );
            }
        }

        void run_program() {
            if(glsl_program_ != 0) {
                glDeleteProgram(glsl_program_);
                glsl_program_ = 0;
            }
            glsl_frame_ = 0;
            glsl_start_time_ = Stopwatch::now();
            if(text_editor_.text().find("<GLUP/ShaderToy.h>") != std::string::npos) {
                glsl_program_ = glupCompileProgram(text_editor_.text().c_str());
            } else {
                std::string source = (
                    "//stage GL_FRAGMENT_SHADER\n"
                    "//import <GLUP/ShaderToy.h>\n"
                    "#line 1\n"
                ) + text_editor_.text();
                glsl_program_ = glupCompileProgram(source.c_str());
            }
            text_editor_visible_ = (glsl_program_ == 0);
            start_animation();
        }

        void stop_program() {
            stop_animation();
            text_editor_visible_ = true;
        }

        /**
         * \copydoc SimpleApplication::draw_application_icons()
         */
        void draw_application_icons() override {
            if(phone_screen_) {
                if(ImGui::SimpleButton(icon_UTF8("home"))) {
                    home();
                }
                if(ImGui::SimpleButton(icon_UTF8("play-circle"))) {
                    run_program();
                }
                if(ImGui::SimpleButton(icon_UTF8("stop-circle"))) {
                    stop_program();
                }
            }
        }


        /**
         * \copydoc Application::load()
         */
        bool load(const std::string& filename) override {
            if(FileSystem::is_file(filename)) {
                text_editor_.load(filename);
                current_file_ = filename;
                text_editor_visible_ = true;
                return true;
            } else if(builtin_files_->is_file(filename)) {
                const char* data = builtin_files_->get_file_contents(filename);
                text_editor_.load_data(data);
                current_file_ = "";
                text_editor_visible_ = true;
                return true;
            }
            return false;
        }

        /**
         * \copydoc Application::save()
         */
        bool save(const std::string& filename) override {
            text_editor_.save(filename);
            current_file_ = filename;
            return true;
        }

        /**
         * \copydoc Application::supported_read_file_extensions()
         */
        std::string supported_read_file_extensions() override {
            return "glsl";
        }

        /**
         * \copydoc Application::supported_write_file_extensions()
         */
        std::string supported_write_file_extensions() override {
            return "glsl";
        }

    protected:
        void draw_viewer_properties() override {
            if(ImGui::Button(
                   (icon_UTF8("play-circle")+" run").c_str(),
                   ImVec2(-ImGui::GetContentRegionAvail().x/1.8f,0.0f))
              ) {
                run_program();
            }
            ImGui::SameLine();
            if(ImGui::Button(
                   (icon_UTF8("stop-circle")+" stop").c_str(),
                   ImVec2(-1.0f, 0.0f))
              ) {
                stop_program();
            }
            ImGui::Separator();
            if(ImGui::Button(
                   (icon_UTF8("home") + " Home").c_str(), ImVec2(-1.0, 0.0))
              ) {
                home();
            }
        }

    private:
        GLuint glsl_program_;
        int glsl_frame_;
        double glsl_start_time_;
        SmartPointer<FileSystem::MemoryNode> builtin_files_;
    };

}

int main(int argc, char** argv) {
    GeoShadeApplication app;
    app.start(argc, argv);
    return 0;
}
