/*
-----------------------------------------------------------------------------
This source file is part of OGRE
(Object-oriented Graphics Rendering Engine)
For the latest info, see http://www.ogre3d.org/

Copyright (c) 2000-2014 Torus Knot Software Ltd

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-----------------------------------------------------------------------------
*/

#ifndef __Ogre_PagedWorldSection_H__
#define __Ogre_PagedWorldSection_H__

#include "OgrePagingPrerequisites.h"
#include "OgreAxisAlignedBox.h"

namespace Ogre
{
    /** \addtogroup Optional
    *  @{
    */
    /** \addtogroup Paging
    *  Some details on paging component
    *  @{
    */

    /** Represents a section of the PagedWorld which uses a given PageStrategy, and
        which is made up of a generally localised set of Page instances.

        The reason for PagedWorldSection is that you may wish to cater for multiple
        sections of your world which use a different approach to paging (ie a
        different PageStrategy), or which are significantly far apart or separate 
        that the parameters you want to pass to the PageStrategy are different.
    @par
        PagedWorldSection instances are fully contained within the PagedWorld and
        their definitions are loaded in their entirety when the PagedWorld is
        loaded. However, no Page instances are initially loaded - those are the
        responsibility of the PageStrategy.
    @par
        PagedWorldSection can be subclassed and derived types provided by a
        PagedWorldSectionFactory. These subclasses might come preconfigured
        with a strategy for example, or with additional metadata used only for
        that particular type of section.
    @par
        A PagedWorldSection targets a specific SceneManager. When you create one
        in code via PagedWorld::createSection, you pass that SceneManager in manually.
        When loading from a saved world file however, the SceneManager type and
        instance name are saved and that SceneManager is looked up on loading, or
        created if it didn't exist. 
    */
    class _OgrePagingExport PagedWorldSection : public PageAlloc
    {
    public:
        typedef std::map<PageID, Page*> PageMap;
    protected:
        String mName;
        AxisAlignedBox mAABB;
        PagedWorld* mParent;
        PageStrategy* mStrategy;
        PageStrategyData* mStrategyData;
        PageMap mPages;
        PageProvider* mPageProvider;
        SceneManager* mSceneMgr;
    private:
        /// Load data specific to a subtype of this class (if any)
        virtual void loadSubtypeData(StreamSerialiser& ser) {}
        virtual void saveSubtypeData(StreamSerialiser& ser) {}


    public:
        static const uint32 CHUNK_ID;
        static const uint16 CHUNK_VERSION;

        /** Construct a new instance, specifying the parent and scene manager. */
        PagedWorldSection(const String& name, PagedWorld* parent, SceneManager* sm);
        virtual ~PagedWorldSection();

        PageManager* getManager() const;

        /// Get the name of this section
        virtual const String& getName() const { return mName; }
        /// Get the page strategy which this section is using
        virtual PageStrategy* getStrategy() const { return mStrategy; }
        /** Change the page strategy.

            Doing this will invalidate any pages attached to this world section, and
            require the PageStrategyData to be repopulated.
        */
        virtual void setStrategy(PageStrategy* strat);
        /** Change the page strategy.

            Doing this will invalidate any pages attached to this world section, and
            require the PageStrategyData to be repopulated.
        */
        virtual void setStrategy(const String& stratName);

        /** Change the SceneManager.

        Doing this will invalidate any pages attached to this world section, and
        require the pages to be reloaded.
        */
        virtual void setSceneManager(SceneManager* sm);
        
        /** Change the SceneManager.

        Doing this will invalidate any pages attached to this world section, and
        require the pages to be reloaded.
        @param smName The instance name of the SceneManager
        */
        virtual void setSceneManager(const String& smName);
        /// Get the current SceneManager
        virtual SceneManager* getSceneManager() const { return mSceneMgr; }

        /// Get the parent world
        virtual PagedWorld* getWorld() const { return mParent; }
        /// Get the data required by the PageStrategy which is specific to this world section
        virtual PageStrategyData* getStrategyData() const { return mStrategyData; }
        /// Set the bounds of this section
        virtual void setBoundingBox(const AxisAlignedBox& box);
        /// Get the bounds of this section
        virtual const AxisAlignedBox& getBoundingBox() const;


        /// Load this section from a stream (returns true if successful)
        virtual bool load(StreamSerialiser& stream);
        /// Save this section to a stream
        virtual void save(StreamSerialiser& stream);


        /// Called when the frame starts
        virtual void frameStart(Real timeSinceLastFrame);
        /// Called when the frame ends
        virtual void frameEnd(Real timeElapsed);
        /// Notify a section of the current camera
        virtual void notifyCamera(Camera* cam);

        /** Load or create a page against this section covering the given world 
            space position. 

            This method is designed mainly for editors - it will try to load
            an existing page if there is one, otherwise it will create a new one
            synchronously.
        */
        virtual Page* loadOrCreatePage(const Vector3& worldPos);

        /** Get the page ID for a given world position. */
        virtual PageID getPageID(const Vector3& worldPos);


        /** Ask for a page to be loaded with the given (section-relative) PageID

            You would not normally call this manually, the PageStrategy is in 
            charge of it usually.
            If this page is already loaded, this request will not load it again.
            If the page needs loading, then it may be an asynchronous process depending
            on whether threading is enabled.
        @param pageID The page ID to load
        @param forceSynchronous If true, the page will always be loaded synchronously
        */
        virtual void loadPage(PageID pageID, bool forceSynchronous = false);

        /** Ask for a page to be unloaded with the given (section-relative) PageID

            You would not normally call this manually, the PageStrategy is in 
            charge of it usually.
        @param pageID The page ID to unload
        @param forceSynchronous If true, the page will always be unloaded synchronously
        */
        virtual void unloadPage(PageID pageID, bool forceSynchronous = false);
        /** Ask for a page to be unloaded with the given (section-relative) PageID

        You would not normally call this manually, the PageStrategy is in 
        charge of it usually.
        @param p The Page to unload
        @param forceSynchronous If true, the page will always be unloaded synchronously
        */
        virtual void unloadPage(Page* p, bool forceSynchronous = false);
        /** Give a section the opportunity to prepare page content procedurally. 

        You should not call this method directly. This call may well happen in 
        a separate thread so it should not access GPU resources, use _loadProceduralPage
        for that
        @return true if the page was populated, false otherwise
        */
        virtual bool _prepareProceduralPage(Page* page);
        /** Give a section the opportunity to prepare page content procedurally. 

        You should not call this method directly. This call will happen in 
        the main render thread so it can access GPU resources. Use _prepareProceduralPage
        for background preparation.
        @return true if the page was populated, false otherwise
        */
        virtual bool _loadProceduralPage(Page* page);
        /** Give a section  the opportunity to unload page content procedurally. 

        You should not call this method directly. This call will happen in 
        the main render thread so it can access GPU resources. Use _unprepareProceduralPage
        for background preparation.
        @return true if the page was populated, false otherwise
        */
        virtual bool _unloadProceduralPage(Page* page);
        /** Give a section  the opportunity to unprepare page content procedurally. 

        You should not call this method directly. This call may well happen in 
        a separate thread so it should not access GPU resources, use _unloadProceduralPage
        for that
        @return true if the page was unpopulated, false otherwise
        */
        virtual bool _unprepareProceduralPage(Page* page);

        /** Ask for a page to be kept in memory if it's loaded.

            This method indicates that a page should be retained if it's already
            in memory, but if it's not then it won't trigger a load. This is useful
            for retaining pages that have just gone out of range, but which you
            don't want to unload just yet because it's quite possible they may come
            back into the active set again very quickly / easily. But at the same
            time, if they've already been purged you don't want to force them to load. 
            This is the 'maybe' region of pages. 
        @par
            Any Page that is neither requested nor held in a frame will be
            deemed a candidate for unloading.
        */
        virtual void holdPage(PageID pageID);

        /** Retrieves a Page.

            This method will only return Page instances that are already loaded. It
            will return null if a page is not loaded. 
        */
        virtual Page* getPage(PageID pageID);

        /** Remove all pages immediately. 

            Effectively 'resets' this section by deleting all pages. 
        */
        virtual void removeAllPages();

        /** Set the PageProvider which can provide streams Pages in this section. 

            This is the top-level way that you can direct how Page data is loaded. 
            When data for a Page is requested for a PagedWorldSection, the following
            sequence of classes will be checked to see if they have a provider willing
            to supply the stream: PagedWorldSection, PagedWorld, PageManager.
            If none of these do, then the default behaviour is to look for a file
            called worldname_sectionname_pageID.page. 
        @note
            The caller remains responsible for the destruction of the provider.
        */
        virtual void setPageProvider(PageProvider* provider) { mPageProvider = provider; }
        
        /** Get the PageProvider which can provide streams for Pages in this section. */
        virtual PageProvider* getPageProvider() const { return mPageProvider; }

        /** Get a serialiser set up to read Page data for the given PageID. 
        @param pageID The ID of the page being requested

        The StreamSerialiser returned is the responsibility of the caller to
        delete. 
        */
        virtual StreamSerialiser* _readPageStream(PageID pageID);

        /** Get a serialiser set up to write Page data for the given PageID. 
        @param pageID The ID of the page being requested

        The StreamSerialiser returned is the responsibility of the caller to
        delete. 
        */
        virtual StreamSerialiser* _writePageStream(PageID pageID);

        /** Function for writing to a stream.
        */
        _OgrePagingExport friend std::ostream& operator <<( std::ostream& o, const PagedWorldSection& p );

        /** Get the type name of this section. */
        virtual const String& getType();

    };


    /** A factory class for creating types of world section.
    */
    class _OgrePagingExport PagedWorldSectionFactory : public PageAlloc
    {
    public:
        virtual ~PagedWorldSectionFactory() {}
        virtual const String& getName() const = 0;
        virtual PagedWorldSection* createInstance(const String& name, PagedWorld* parent, SceneManager* sm) = 0;
        virtual void destroyInstance(PagedWorldSection*) = 0;


    };

    /** @} */
    /** @} */
}

#endif
