/*
 * psworld.h - author Matze Braun <MatzeBraun@gmx.de> and
 *              Keith Fulton <keith@paqrat.com>
 *
 * Copyright (C) 2001 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) 
 *
 *
 * 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 (version 2 of the License)
 * 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 __PSWORLD_H__
#define __PSWORLD_H__

#include <csutil/ref.h>
#include <csutil/weakref.h>
#include <csutil/hash.h>
#include <iengine/sector.h>

#include "csutil/parray.h"
#include "csutil/csstring.h"

class CelBase;
struct iSector;
struct iCelEntity;
struct iObjectRegistry;
struct iEngine;
struct iRegion;
class psWorld;

/**
 * Replacement class for iPcRegion from CEL.
 * This is more versatile for dynamic loading
 * and unloading of multiple maps than iPcRegion.
 */
class psRegion
{
protected:
    iObjectRegistry* object_reg;
    csString worlddir;
    csString worldfile;
    csString regionname;
    bool loaded;
    bool isNeeded;
    psWorld * world;

    static void CloneNode(iDocumentNode* from, iDocumentNode* to);

public:
    /**
     * Creates an entry representing a single region
     * but does not load it.
     */
    psRegion(iObjectRegistry *obj_reg, psWorld * world, const char *file);

    /**
     * Dtor unloads region if loaded
     */
    ~psRegion();

    /**
     * Loads the file "world" from /planeshift/art/world/<name>.zip
     * into the region called <name>.
     */
    bool Load(bool loadMeshes = true);

    /**
     * Unloads the region from the CS engine, deleting all objects
     * in that region.
     */
    bool Unload();

    bool IsLoaded()
    { return loaded; }

    iRegion * GetRegion();

    /**
     * Sets up the collision detection blockers for all meshes in
     * the region.
     */
    void SetupWorldColliders(iEngine *engine,iRegion *cur_region);

    /**
     * The retain flag is used by psWorld owner to manage the list
     * of loaded and unloaded regions efficiently.  See psWorld class
     * def for details on how this is done.
     */
    void SetNeededFlag(bool flag)
    { isNeeded = flag; }
    bool IsNeeded()
    { return isNeeded; }

    const char *GetName()
    { return regionname; }

    /// Cleans the given file and removes all meshes, lights, etc. not needed on the server.
    csRef<iDocumentNode> Clean(csRef<iDocumentNode> worldNode);

    
};

struct iObject;

/**
 * 
 * It is in charge of managing all regions (zone map files)
 * and loading/unloading them as needed.  The main users
 * of this class are EntityManager and on the client, ZoneHandler.
 */
class psWorld 
{
protected:
    iSector* room;
    csPDelArray<psRegion> regions;
    iObjectRegistry *object_reg;
    iRegion * sharedRegion;
    csWeakRef<iEngine> engine;

    class sectorTransformation
    {
        // Transformation
        csHash<csReversibleTransform*, csPtrKey<iSector> > trans;
        
    public:
        void Set(iSector* sector, csReversibleTransform transform)
        {
            csReversibleTransform* transf = trans.Get(csPtrKey<iSector> (sector), NULL);
            
            if(!transf)
            {
                transf = new csReversibleTransform();
                trans.Put(csPtrKey<iSector> (sector), transf);
            }

            *transf = transform;
        }

        csReversibleTransform* Get(iSector* sector)
        {
            csReversibleTransform* got = trans.Get(csPtrKey<iSector> (sector), NULL);

            return got;
        }

        ~sectorTransformation()
        {
            csHash<csReversibleTransform*, csPtrKey<iSector> >::GlobalIterator iter = trans.GetIterator();
            while(iter.HasNext())
                delete iter.Next();
            
            trans.Empty();
        }

    };

    csArray<sectorTransformation> transarray;

    void BuildWarpCache();
public:
    psWorld();
    ~psWorld();
 
    /// Initialize psWorld
    bool Initialize( iObjectRegistry* object_reg);
    bool CreateMap( const char* name, const char* mapFile, bool loadNow, bool loadMeshes = true ); 
    enum
    {
        DEFER_LOAD = 0,
        LOAD_NOW   = 1
    };

    /// Create an psworld entity
    
    
    /// Objects (with meshes) need a sector to be positioned.
    iSector *GetRoom(void) { return room; };


    /// Create a new psRegion entry and load it if specified
    psRegion *NewRegion(const char *mapfile,bool load_now, bool loadMeshes = true);

    /// This makes a string out of all region names, separated by | chars.
    void GetAllRegionNames(csString& str);

    /**
     * Mark all regions to be unloaded initially.  The ZoneHandler
     * clears the list whenever a sector is crossed and verifies
     * that all the right zones are loaded and unloaded.  The algorithm
     * is:
     *     1) Flag everything as unload
     *     2) Flag the things that should be loaded or retained based
     *        on the zone information ZoneHandler has.  If the region
     *        exists, it is just flagged to be retained.  If it doesn't
     *        exist yet, it is added to the list but not loaded yet.
     *     3) When this loop is through, regions that were not touched
     *        are still flagged to be unloaded per step 1.
     *     4) ExecuteFlaggedRegions first unloads any entries flagged
     *        as unloaded, deleting their entries, then loads any entries
     *        flagged to be loaded which are not already in memory.
     */
    void FlagAllRegionsAsNotNeeded();
    enum 
    {
        NOT_NEEDED = 0,
        NEEDED = 1
    };
    /**
     * Flag a region to be kept, or add a new entry to the list if not found.
     * The new entry will not be loaded immediately, but only by ExecuteFlaggedRegions().
     */
    void FlagRegionAsNeeded(const char *map);

    void GetNotNeededRegions(csArray<iRegion*> & regions);

    /// Unload all regions to be unloaded, and load any regions to be kept but
    /// which are not loaded already.
    bool ExecuteFlaggedRegions(bool transitional, bool one);

    bool NeedsLoading(bool transitional);

    bool IsAllLoaded();

    /// Immediately after a map is loaded, all materials, textures and mesh factories defined
    /// in this map file are moved to special region that serves as container of map data
    /// that are shared between maps. This is done by MoveToMaterialsRegion().
    void MoveToSharedRegion(iRegion * source);
    /// Moves one object there
    void MoveToSharedRegion(iRegion * source, iObject * something);

    /// Changes pos according to the warp portal between adjacent sectors from and to.
    void WarpSpace(iSector* from, iSector* to, csVector3& pos)
    {
        if(from == to)
            return;

        int i = engine->GetSectors()->Find(from);
        csReversibleTransform* transform = transarray[i].Get(to);
        if(transform)
            pos = *transform * pos;
    }
};


#endif

