//  SuperTuxKart - a fun racing game with go-kart
//  Copyright (C) 2010-2015 Marianne Gagnon
//
//  This program 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; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


#ifndef HEADER_SCREEN_HPP
#define HEADER_SCREEN_HPP

#include <map>
#include <string>
#include <typeinfo>
#include "utils/cpp2011.hpp"

#include <irrString.h>
#include <IXMLReader.h>

namespace irr
{
    namespace gui { class IGUIElement; }
}
using namespace irr;

#include "config/stk_config.hpp"
#include "guiengine/abstract_top_level_container.hpp"
#include "guiengine/engine.hpp"
#include "guiengine/event_handler.hpp"
#include "guiengine/widget.hpp"
#include "input/input.hpp"
#include "utils/leak_check.hpp"
#include "utils/ptr_vector.hpp"

#include <functional>

/**
 * \ingroup guiengine
 */
namespace GUIEngine
{
    /**
     * \brief Declares a class to be a singleton.
     * Normally, all screens will be singletons.
     * \ingroup guiengine
     */
    template<typename SCREEN>
    class ScreenSingleton
    {
    protected:
        static SCREEN* singleton;
    public:

        ~ScreenSingleton()
        {
            singleton = NULL;
        }

        static SCREEN* getInstance()
        {
            if (singleton == NULL)
            {
                singleton = new SCREEN();
                std::function<SCREEN*()> new_screen_function = []()
                    { return ScreenSingleton::getInstance(); };
                singleton->setScreenPointerFunction(new_screen_function);
                GUIEngine::addScreenToList(singleton);
            }

            return singleton;
        }
    };
    template <typename SCREEN> SCREEN*
        ScreenSingleton<SCREEN>::singleton = nullptr;

    /**
     * \brief Represents a single GUI screen.
     * Mainly responsible of its children widgets; Screen lays them
     * out, asks them to add themselves, asks them to remove themselves, etc.
     *
     * Also initiates the read of GUI files, even though most of that work is
     * done in "screen_loader.cpp"
     *
     * \ingroup guiengine
     */
    class Screen : public AbstractTopLevelContainer
    {
protected:
        /** True if this screen is resizable
         */
        bool m_resizable;
    private:
        /** True if the race (if it is running) should be paused when this
         *  screen is shown. The RaceResultGUI uses this to leave the race
         *  running while it is being shown. */
        bool m_pause_race;

        bool m_loaded;

        /** Will be called to determine if the 3D scene must be rendered when
         *  at this screen.
         */
        bool m_render_3d;

        /** When set to true it updates the screen even if modal dialog is 
         *  opened
         */
        bool m_update_in_background;

        /** to catch errors as early as possible, for debugging purposes only */
        unsigned int m_magic_number;

        unsigned m_width, m_height;

        friend class Skin;

        std::string m_filename;
        /** For runtime screen reloading without template */
        std::function<Screen*()> m_screen_func;
    public:

        LEAK_CHECK()

        /**
         * \ingroup guiengine
         * \brief Loads a GUI screen from its XML file.
         *
         * Builds a hierarchy of Widget objects whose contents are a direct
         * transcription of the XML file, with little analysis or layout
         * performed on them.
         */
        static void parseScreenFileDiv(irr::io::IXMLReader* xml,
                                       PtrVector<Widget>& append_to,
                                       irr::gui::IGUIElement* parent = NULL);

        /** Save the function before GUIEngine::clearScreenCache, call it after
         * to get the new screen instance pointer
         */
        std::function<Screen*()> getNewScreenPointer() const { return m_screen_func; }

        void setScreenPointerFunction(const std::function<Screen*()>& f) { m_screen_func = f; }

        Screen(bool pause_race=true);

        Screen(const char* filename, bool pause_race=true);

        virtual ~Screen();

        bool operator ==(const char* filename) const { return m_filename == filename; }

        void loadFromFile();

        /** \return whether this screen is currently loaded */
        bool isLoaded() const { return m_loaded; }

        void addWidgets();

        void calculateLayout();

        void manualAddWidget(Widget* w);

        void manualRemoveWidget(Widget* w);
        
        /** When set to true it updates the screen even if modal dialog is 
         *  opened
         */
        void setUpdateInBackground(bool value) {m_update_in_background = value;}
        bool getUpdateInBackground() {return m_update_in_background;}

        /** \return the name of this menu (which is the name of the file) */
        const std::string& getName() const { return m_filename; }

        virtual void unload();

        /** Will be called to determine if the 3D scene must be rendered when
         *  at this screen
         */
        bool needs3D() { return m_render_3d; }

        /** \brief Invoke this method for screens that use a 3D scene as
         *         background.
         *
         *  (if this method is not invoked with 'true' as parameter, the menu
         *  background will be rendered instead).
         *
         *  \note To create the 3D background, use the facilities provided by
         *        the irrLicht scene manager, this class will not set up any
         *        3D scene.
         */
        void setNeeds3D(bool needs3D) { m_render_3d = needs3D; }

        /**
         * \brief Callback invoked when loading this menu.
         *
         * \pre   Children widgets of this menu have been created by the time
         *        this callback is invoked.
         * \note  This method is not called everytime the screen is shown.
         *        Screen::init is.
         *        Use this method for persistent setup code (namely, that
         *        deals with setting up children widget objects and needs not
         *        be done everytime we visit the screen).
         * \note  A Screen object instance may be unloaded then loaded back.
         *        This method might thus be called more than once in the
         *        lifetime of a Screen object, however there will always
         *        be an 'unload' event in-between calls to this method.
         */
        virtual void loadedFromFile() = 0;

        /**
          * \brief Callback invoked when this screen is being unloaded.
          *        Override this method in children classes if you need to be
          *        notified of this.
          * \note  A Screen object instance may be unloaded then loaded back
          *        at will.
          * \note  An unloaded Screen object does not have its children widgets
          *        anymore, it only retains its members (most importantly the
          *        path to its GUI file) so that it can be loaded back later.
          */
        virtual void unloaded() {}

        /**
          * \brief Optional callback invoked very early, before widgets have
          *        been added (contrast with init(), which is invoked afer
          *        widgets were added)
          */
        virtual void beforeAddingWidget() {}

        /**
          * \brief Callback invoked when entering this menu (after the
          *        widgets have been added).
          *
          * \note  The same instance of your object may be entered/left more
          *        than once, so make sure that one instance of your object
          *        can be used several times if the same screen is visited
          *        several times.
          */
        virtual void init();

        /** Displays this screen bu pushing it onto the stack of screen 
         *  in the state manager. */
        void push();

        /**
          * \brief Callback invoked before leaving this menu.
          *
          * \note  The same instance of your object may be entered/left more
          *        than once, so make sure that one instance of your object can
          *        be used several times if the same screen is visited several
          *        times.
          */
        virtual void tearDown();

        /**
          * \brief  Called when escape is pressed.
          * \return true if the screen should be closed,
          *         false if you handled the press another way
          */
        virtual bool onEscapePressed() { return true; }

        /**
         * \brief will be called everytime something happens.
         *
         * Events are generally a widget state change. In this case, a pointer
         * to the said widget is passed along its name, so you get its new
         * state and/or act. There are two special events, passed with a NULL
         * widget, and which bear the names "init" and "tearDown", called
         * respectively when a screen is being made visible and when it's being
         * left, allowing for setup/clean-up.
         */
        virtual void eventCallback(Widget* widget, const std::string& name, const int playerID) = 0;

        /**
         * \brief optional callback you can override to be notified at every frame.
         */
        virtual void onUpdate(float dt) { };

        /**
         * \brief optional callback you can override to be notified at every frame.
         */
        virtual void onDraw(float dt) { };

        /**
         * \return which music to play at this screen
         */
        virtual MusicInformation* getMusic() const { return stk_config->m_title_music; }

        /**
         * \return which music to play at this screen, if accessed in "in-game-menu" mode
         */
        virtual MusicInformation* getInGameMenuMusic() const { return NULL; }

        virtual int getWidth() { return m_width; }

        virtual int getHeight() { return m_height; }

        virtual bool isResizable() const { return m_resizable; }
        /**
         * \brief Override this if you need to be notified of player actions
         *        in subclasses.
         */
        virtual EventPropagation filterActions(PlayerAction action,
                                               int deviceID,
                                               const unsigned int value,
                                               Input::InputType type,
                                               int playerId)
            { return EVENT_LET; }

        /** Callback you can use if you want to know when the user pressed
         *  on a disabled ribbon item.
         *  (the main use I see for this is to give feedback)
         */
        virtual void onDisabledItemClicked(const std::string& item) {}

        /**
         * \brief Override this if you need to be notified of raw input in
         *        subclasses.
         */
        virtual void filterInput(Input::InputType type,
                                 int deviceID,
                                 int btnID,
                                 int axisDir,
                                 int value) {}

        /** Callback that gets called when a dialog is closed.
         *  Can be used to set focus for instance.
         */
        virtual void onDialogClose() {}

        /** Callback called when focus changes */
        virtual void onFocusChanged(Widget* previous, Widget* focus, int playerID) {}
    };

    class CutsceneScreen : public Screen
    {
    public:
        CutsceneScreen(const char* name) : Screen(name, false)
        {
            setNeeds3D(true);
        }

        virtual void onCutsceneEnd() = 0;
    };
}

#endif
