/*
 *  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
 *
 */

#ifndef H_GEOGRAM_GFX_GUI_SIMPLE_APPLICATION_H
#define H_GEOGRAM_GFX_GUI_SIMPLE_APPLICATION_H

#include <geogram_gfx/basic/common.h>
#include <geogram_gfx/gui/application.h>
#include <geogram_gfx/gui/status_bar.h>
#include <geogram_gfx/gui/console.h>
#include <geogram_gfx/gui/text_editor.h>
#include <geogram_gfx/gui/command.h>
#include <geogram_gfx/gui/arc_ball.h>
#include <geogram_gfx/full_screen_effects/full_screen_effect.h>
#include <geogram_gfx/imgui_ext/imgui_ext.h>
#include <geogram_gfx/imgui_ext/icon_font.h>

#include <map>
#include <functional>

struct lua_State;

namespace GEO {

    /**
     * \brief SimpleApplication is an Application with pre-created
     *  and customizable menus, commands, graphic attributes,
     *  load and save dialogs, and text editor.
     */
    class GEOGRAM_GFX_API SimpleApplication : public Application {
    public:

        /**
         * \brief SimpleApplication constructor.
         * \param[in] name the name of the application.
         */
        SimpleApplication(const std::string& name);

        /**
         * \brief SimpleApplication destructor.
         */
        ~SimpleApplication() override;

        /**
         * \copydoc GEO::Application::draw_gui()
         */
        void draw_gui() override;

        /**
         * \copydoc GEO::Application::draw_graphics()
         */
        void draw_graphics() override;

        /**
         * \brief Saves the current content to a file.
         * \details Baseclass implementation does nothing. Derived classes
         *  may overload this function.
         * \retval true if the file could be sucessfully saved.
         * \retval false otherwise
         */
        virtual bool save(const std::string& filename);

        /**
         * \brief Loads a file.
         * \details Baseclass implementation does nothing. Derived classes
         *  may overload this function.
         * \retval true if the file could be sucessfully loaded
         * \retval false otherwise
         */
        virtual bool load(const std::string& filename);

        /**
         * \brief Gets the text editor.
         * \return a reference to the text editor.
         */
        TextEditor& text_editor() {
            return text_editor_;
        }

        /**
         * \brief Shows the text editor.
         */
        void show_text_editor() {
            text_editor_visible_ = true;
        }

        /**
         * \brief Hides the text editor.
         */
        void hide_text_editor() {
            text_editor_visible_ = false;
        }

        /**
         * \brief Shows the console.
         */
        void show_console() {
            console_visible_ = true;
        }

        /**
         * \brief Hides the console.
         */
        void hide_console() {
            console_visible_ = false;
        }

        /**
         * \brief Restores default viewing parameters.
         */
        void home();

        /**
         * \copydoc GEO::Application::set_style()
         */
        void set_style(const std::string& style) override;

        /**
         * \brief Sets the region of interest
         * \details This defines the default target of the camera
         * \param[in] xmin , ymin , zmin , xmax , ymax , zmax the
         *   bounds of the region of interest.
         */
        void set_region_of_interest(
            double xmin, double ymin, double zmin,
            double xmax, double ymax, double zmax
        );

        /**
         * \brief Gets the region of interest
         * \see set_region_of_interest()
         * \param[out] xmin , ymin , zmin , xmax , ymax , zmax the
         *   bounds of the region of interest.
         */
        void get_region_of_interest(
            double& xmin, double& ymin, double& zmin,
            double& xmax, double& ymax, double& zmax
        ) const;

        void zoom_in() {
            zoom_ *= 1.1;
        }

        void zoom_out() {
            zoom_ /= 1.1;
        }

        void set_clipping(bool x) {
            clipping_ = x;
        }

        void set_lighting(bool x) {
            lighting_ = x;
        }

        void set_background_color(const vec4f& color) {
            background_color_ = color;
        }

        virtual bool exec_command(const char* command);

        static SimpleApplication* instance() {
            return dynamic_cast<SimpleApplication*>(
                GEO::Application::instance()
            );
        }

        /**
         * \brief Projects a point from model space to window coordinates.
         * \param[in] p the point in model space coordinates.
         * \return the point in window coordinates, that is
         *    [0,width-1] x [0,height-1]
         */
        vec3 project(const vec3& p);

        /**
         * \brief Unprojects a 3d point from window coordinates to model space.
         * \param[in] p the 3d point in window coordinates, that is
         *   [0,width-1] x [0,height-1]
         * \return the point in model space coordinates.
         */
        vec3 unproject(const vec3& p);

        /**
         * \brief Unprojects a 2d point from window coordinates to model space.
         * \param[in] p the 2d point in screen space coordinates.
         * \return the 2d point in model space coordinates.
         */
        vec2 unproject_2d(const vec2& p);


        /**
         * \copydoc Application::drop_callback()
         */
        void drop_callback(int nb, const char** f) override;

	/**
	 * \brief Runs the key func associated with a given key
	 * \param[in] key the name of the key ("F5" for the F5 key)
	 * \details Does nothing if no function is associated with the key
	 */
	void run_key_func(const std::string& key) {
	    auto it = key_funcs_.find(key);
	    if(it != key_funcs_.end()) {
		it->second();
	    }
	}

    protected:
        /**
         * \brief Declares a function to be triggered when a key is pressed.
         * \param[in] key the key ("a" for a, "F1" for F1)
         * \param[in] cb the function to be called
         * \param[in] help an optional help string
         */
        void add_key_func(
            const std::string& key, std::function<void()> cb,
            const char* help = nullptr
        );

        /**
         * \brief Declares a boolean to be toggled when a key is pressed.
         * \param[in] key the key ("a" for a, "F1" for F1)
         * \param[in] p_val a pointer to the boolean
         * \param[in] help an optional help string
         */
        void add_key_toggle(
            const std::string& key, bool* p_val,
            const char* help = nullptr
        );

        /**
         * \copydoc GEO::Application::char_callback()
         */
        void char_callback(unsigned int c) override;

        /**
         * \copydoc GEO::Application::key_callback()
         */
        void key_callback(int key, int scancode, int action, int mods) override;

        /**
         * \copydoc GEO::Application::mouse_button_callback()
         */
        void mouse_button_callback(
            int button, int action, int mods, int source
        ) override;

        /**
         * \copydoc GEO::Application::cursor_pos_callback()
         */
        void cursor_pos_callback(double x, double y, int source) override;

        /**
         * \copydoc GEO::Application::scroll_callback()
         */
        void scroll_callback(double xoffset, double yoffset) override;

        /**
         * \brief Setups OpenGL for scene drawing.
         */
        virtual void draw_scene_begin();

        /**
         * \brief Draws the scene.
         */
        virtual void draw_scene();

        /**
         * \brief Cleanups OpenGL after scene drawing.
         */
        virtual void draw_scene_end();

        /**
         * \brief Draws the viewer properties window frame and contents.
         */
        virtual void draw_viewer_properties_window();

        /**
         * \brief Draws the contents of viewer properties window.
         */
        virtual void draw_viewer_properties();

        /**
         * \brief Draw the object properties window frame and contents.
         */
        virtual void draw_object_properties_window();

        /**
         * \brief Draws the contents of the object properties window.
         */
        virtual void draw_object_properties();


        /**
         * \brief Draws the active command window if any.
         */
        virtual void draw_command_window();

        /**
         * \brief Draws the console.
         */
        virtual void draw_console();


        /**
         * \brief Draws the menu bar.
         */
        virtual void draw_menu_bar();


        /**
         * \brief Draws the load menu and browser.
         */
        virtual void draw_load_menu();

        /**
         * \brief Draws the save menu.
         */
        virtual void draw_save_menu();

        /**
         * \brief Draws other file operation menu.
         * \details Default implementation does nothing.
         *  It can be overloaded to add other menu
         *  items in the file menu.
         */
        virtual void draw_fileops_menu();

        /**
         * \brief Draws the about box in the file menu.
         */
        virtual void draw_about();

        /**
         * \brief Draws help info (accelarators)
         */
        virtual void draw_help();

        /**
         * \brief Draws the windows menu.
         */
        virtual void draw_windows_menu();

        /**
         * \brief Draws the application menus.
         * \details Meant to be overloaded by derived classes.
         */
        virtual void draw_application_menus();

        /**
         * \brief Draws the application icons on the menubar.
         * \details Meant to be overloaded by derived classes.
         */
        virtual void draw_application_icons();


	/**
	 * \brief Draws a window to edit all command line arguments
	 */
	virtual void draw_command_line_editor();


        /**
         * \copydoc Application::post_draw()
         */
        void post_draw() override;

        /**
         * \brief Tests whether a file can be loaded.
         * \details This function can be used to filter the files displayed
         *  in the "Load..." menu. Baseclass implementation always return true.
         *  Derived classes may overload it and return false for files with
         *  unknown extensions.
         */
        virtual bool can_load(const std::string& filename);

        /**
         * \brief Gets the list of supported file extensions for reading.
         * \details This function may be olverloaded by derived class. Base
         *  class implementation returns "". If this function returns "", then
         *  no "Load..." option is displayed in the "File" menu.
         * \return The semi-colon separated list of supported file extensions,
         *  or "*" if all file extensions are supported.
         */
        virtual std::string supported_read_file_extensions();

        /**
         * \brief Gets the list of supported file extensions for writing.
         * \details This function may be olverloaded by derived class. Base
         *  class implementation returns "". If this function returns "", then
         *  no "Save..." option is displayed in the "File" menu.
         *   If it returns a colon-separated list of extensions, then the
         *  "Save..." option displays a list of possible file names for each
         *  supported extension.
         * \return The semi-colon separated list of supported file extensions,
         *  or "*" if all file extensions are supported.
         */
        virtual std::string supported_write_file_extensions();

        /**
         * \copydoc GEO::Application::GL_initialize()
         */
        void GL_initialize() override;

        /**
         * \copydoc GEO::Application::GL_terminate()
         */
        void GL_terminate() override;

        /**
         * \brief Recursively browses a directory and generates
         *  menu items.
         * \param[in] path the path to be browsed
         * \param[in] subdirs if true, browse subdirectories as well
         */
        void browse(const std::string& path, bool subdirs=false);

        /**
         * \copydoc GEO::Application::geogram_initialize()
         */
        void geogram_initialize(int argc, char** argv) override;

        /**
         * \brief Sets the default filename used to save
         *  the current file.
         * \param[in] filename the default filename.
         */
        void set_default_filename(const std::string& filename) {
            strcpy(filename_, filename.c_str());
        }

        /**
         * \brief Initializes a new colormap from name and xpm data.
         * \details This function can be called only once the OpenGL
         *  context is ready, for instance in the init_graphics() function.
         * \param[in] name the name of the colormap
         * \param[in] xpm_data the image data of the colormap.
         */
        void init_colormap(const std::string& name, const char** xpm_data);

        /**
         * \brief Initializes all the default colormaps.
         * \details This function can be called only once the OpenGL
         *  context is ready, for instance in the init_graphics() function.
         */
        void init_colormaps();

        /**
         * \copydoc Application::ImGui_initialize()
         */
        void ImGui_initialize() override;

        void set_2d() {
            three_D_ = false;
        }

        void set_3d() {
            three_D_ = true;
        }

        void set_default_layout();

        void resize(index_t w, index_t h, index_t fb_w, index_t fb_h) override;

        virtual const char* default_layout() const;
        virtual const char* default_layout_android_vertical() const;
        virtual const char* default_layout_android_horizontal() const;

	/**
	 * \brief This function is changed each time the user changed the
	 *  value of a command line argument in the command line editor.
	 * \param[in] argname the name of the argument that changed
	 */
	virtual void command_line_arg_changed(const std::string& argname);

	/**
	 * \brief Executes once again the latest issued command
	 * \details SimpleApplication has a mechanism to handle user commands
	 *   implemented in menus. The latest command is memorized, and not
	 *   directly executed when the user invokes a menu, this is to avoid
	 *   launching a command while in ImGui rendering/event handler. In
	 *   addition, one can use this mechanism to replay the latest
	 *   memorized command (by default, this is bound to the F5 key).
	 */
        static void replay_latest_command();

	/**
	 * \brief Copies a rectangular zone of the framebuffer to an image
	 * \param[in,out] image pointer to an image. If uninitialized, then
	 *   it will be initialized to an RGB image of the specified size, else
	 *   the present allocated image and colore ecoding are used.
	 * \param[in] x0 , y0 origin
	 * \param[in] width , height size. If not set, then window size is used.
	 * \param[in] redraw if set (default), then scene is redrawn before
	 *  taking the snapshot, without the GUI superimposed, else the current
	 *  content of the framebuffer is used (may have the GUI superimposed).
	 */
	virtual void snapshot(
	    Image* image,
	    index_t x0=0, index_t y0=0, index_t width=0, index_t height=0,
	    bool redraw = true
	);

	/**
	 * \brief Saves the framebuffer to an image
	 * \details The scene is redrawn without the gui.
	 * \param[in] filename if left unspecified, then the command line
	 *   argument gui:snapshot_filename is used.
	 * \param[in] verbose if set, display a message with the snapshot
	 *   filename
	 */
	virtual void snapshot(std::string filename = "", bool verbose=false);

	/**
	 * \copydoc Application::declare_args()
	 */
	void declare_args() override;

    protected:
        bool locked_; // avoid starting command when command is running
        bool lighting_;
        bool edit_light_;
        bool clipping_;
        GLUPclipMode clip_mode_;
        bool edit_clip_;
        bool fixed_clip_;
        GLenum effect_;
        vec4f background_color_;

        bool viewer_properties_visible_;
        bool object_properties_visible_;
        bool console_visible_;
        bool text_editor_visible_;
        bool use_text_editor_;
	bool command_line_editor_visible_;

        Box roi_;
        double roi_radius_;
        vec3    object_translation_;
        ArcBall object_rotation_;
        ArcBall light_rotation_;
        ArcBall clip_rotation_;
        vec3    clip_translation_;
        bool    three_D_;
        double  zoom_;
        double  zoom_down_; /**< Zoom when mouse down. */

        bool props_pinned_;

        enum MouseOp {
            MOUSE_NOOP, MOUSE_ROTATE, MOUSE_TRANSLATE, MOUSE_ZOOM
        } mouse_op_;

        enum MouseTarget {
            MOUSE_NOTARGET, MOUSE_OBJECT, MOUSE_LIGHT, MOUSE_CLIP
        } mouse_target_;

        vec2 mouse_down_xy_; // in [-1,1] x [-1,1]
        vec2 mouse_xy_;      // in [-1,1] x [-1,1]

        // Current transform, for picking
        GLint viewport_[4];
        mat4 modelview_transpose_;
        mat4 project_transpose_;

        std::string path_;
        std::string current_file_;
        char filename_[geo_imgui_string_length]; // Buffer for file dialog.
        GLuint geogram_logo_texture_;

        Console_var console_;
        StatusBar_var status_bar_;
        TextEditor text_editor_;

        std::map< std::string, std::function<void()> > key_funcs_;
        std::map< std::string, std::string > key_funcs_help_;

        struct ColormapInfo {
            ColormapInfo() : texture(0) {
            }
            GLuint texture;
            std::string name;
        };

        vector<ColormapInfo> colormaps_;
        FullScreenEffectImpl_var full_screen_effect_;

        lua_State* lua_state_;
        bool lua_error_occured_;
    };

}

#endif
