/*
 * psworld.cpp - 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.
 *
 */
#include <psconfig.h>

#include <iengine/rview.h>
#include <iengine/portal.h>
#include <iengine/portalcontainer.h>

#include <csutil/snprintf.h>
#include <csutil/sysfunc.h>
#include <iengine/region.h>
#include <iutil/vfs.h>
#include <csutil/csstring.h>
#include <cstool/collider.h>
#include <ivaria/collider.h>
#include <igeom/polymesh.h>
#include <imesh/objmodel.h>
#include <iengine/mesh.h>
#include <imesh/object.h>
#include <imesh/thing.h>
#include <csutil/databuf.h>

#include <iutil/objreg.h>
#include <physicallayer/entity.h>
#include <physicallayer/propclas.h>

#include "engine/celbase.h"
#include "psbehave/psworld.h"
#include "util/psconst.h"
#include "util/log.h"

#include <csutil/xmltiny.h>


#define SHARED_REGION_NAME "SharedDataRegion"


psWorld::psWorld()
{
    room = NULL;
    sharedRegion = NULL;
}

psWorld::~psWorld()
{
    if (sharedRegion != NULL)
    {
        engine = CS_QUERY_REGISTRY (object_reg, iEngine);
        engine->GetRegions()->Remove(sharedRegion);
    }

    transarray.Empty();
}

bool psWorld::Initialize( iObjectRegistry* objectReg)
{
    object_reg = objectReg;

    /**
     * In order to delete regions later without deleting our
     * friendly players, we must create a region for them to live
     * in independently from the map regions.  Region loading,
     * unloading and selection logic below always restores the
     * region back to this, so the GetCurrentRegion() will always
     * be spriteworld, unless we are actually in the act of loading
     * a map.
     *
     * (Note that SelectRegion here creates the region also.)
     */
    engine = CS_QUERY_REGISTRY (object_reg, iEngine);

    sharedRegion = engine->CreateRegion(SHARED_REGION_NAME);
        
    return true;
}

void psWorld::MoveToSharedRegion(iRegion * source, iObject * something)
{
    if (source->IsInRegion(something))
    {
        source->Remove(something);
        sharedRegion->Add(something);
    }
}

void psWorld::MoveToSharedRegion(iRegion * source)
{
    engine = CS_QUERY_REGISTRY (object_reg, iEngine);
    csRef<iMaterialList> materials = engine->GetMaterialList();
    csRef<iTextureList> textures = engine->GetTextureList();
    csRef<iMeshFactoryList> factories = engine->GetMeshFactories();
    int i;

    for (i=0; i < materials->GetCount(); i++)
        MoveToSharedRegion(source, materials->Get(i)->QueryObject());

    for (i=0; i < textures->GetCount(); i++)
        MoveToSharedRegion(source, textures->Get(i)->QueryObject());

    for (i=0; i < factories->GetCount(); i++)
        MoveToSharedRegion(source, factories->Get(i)->QueryObject());
}

bool psWorld::CreateMap( const char* name, const char* mapfile, bool load_now, bool loadMeshes)
{    
    if ( !NewRegion(mapfile,load_now, loadMeshes) )    
        return false;
    
    return true;        
}

psRegion* psWorld::NewRegion(const char *mapfile,bool load, bool loadMeshes)
{
    for (unsigned i=0; i < regions.Length(); i++)
    {
        psRegion *rgn = regions[i];
        if (!strcmp(rgn->GetName(),mapfile))
            return rgn;
    }

    psRegion *newregion = new psRegion(object_reg, this, mapfile);
    if (load && !newregion->Load(loadMeshes))
    {
        delete newregion;
        return NULL;
    }
    // This must be rebuilt when the sector list changes
    BuildWarpCache();
    regions.Push(newregion);
    return newregion;
}

void psWorld::GetAllRegionNames(csString& str)
{
    str = "";
    for (unsigned i=0; i < regions.Length(); i++)
    {
        str.Append( regions[i]->GetName() );
        str.Append( "|" );
    }
}


void psWorld::FlagAllRegionsAsNotNeeded()
{
    for (unsigned i=0; i < regions.Length(); i++)
    {
        psRegion *rgn = regions[i];
        rgn->SetNeededFlag(psWorld::NOT_NEEDED);
    }
}

void psWorld::FlagRegionAsNeeded(const char *map)
{
    // if on the list, just retain it with the flag
    for (unsigned i=0; i < regions.Length(); i++)
    {
        psRegion *rgn = regions[i];
        if (rgn->GetName() == map)
        {
            rgn->SetNeededFlag(psWorld::NEEDED);
            return;
        }
    }
    // if not on the list, make a new entry
    psRegion *rgn = NewRegion(map,psWorld::DEFER_LOAD);
    rgn->SetNeededFlag(psWorld::NEEDED);
}

void ConnectPortalToSector(iEngine * engine, const char * portalName, const char * sectorName)
{
    iMeshList * meshes = engine->GetMeshes();
    iMeshWrapper * wrap = meshes->FindByName(portalName);
    if (wrap)
    {
        iMeshObject *obj=wrap->GetMeshObject();
        if (obj)
        {
            csRef<iPortalContainer> portalc = SCF_QUERY_INTERFACE(obj, iPortalContainer);
            if (portalc==NULL)
                return;
            for (int pn=0; pn < portalc->GetPortalCount(); pn++)
            {
                iPortal * portal = portalc->GetPortal(pn);
                iSector * sector = engine->FindSector(sectorName);
                if (sector)
                {
                    Error3("sector %s connected to portal %s", sectorName, portalName);
                    portal->SetSector(sector);
                }
            }
        }
    }
}

bool psWorld::ExecuteFlaggedRegions(bool transitional, bool one)
{
    bool oneDeleted = false;

    // Transitional means that we should allow whatever
    // levels are already loaded to stay loaded, but still
    // ensure that listed levels are loaded also.
    if (!transitional)
    {
        // Delete and unload regions not flagged to be saved
        for (unsigned i=0; i < regions.Length(); i++)
        {
            psRegion *rgn = regions[i];
            if (!rgn->IsNeeded() )
            {
                if (one && oneDeleted)
                    return false;
                Debug2(LOG_LOAD,0,"Region %s is being deleted.\n",rgn->GetName() );

                regions.DeleteIndex(i);
                i--;
                oneDeleted = true;
            }
        }
    }

    bool oneLoaded = false;

    // Now load any remaining regions on the list which are not already loaded
    for (unsigned i=0; i < regions.Length(); i++)
    {
        psRegion *rgn = regions[i];
        if (!rgn->IsLoaded() )
        {
            if (one && oneLoaded)
                return false;
            if(!rgn->Load())
            {
                Error2("Loading region %s failed!", rgn->GetName());
                abort();
            }
            oneLoaded = true;
        }
    }

    return true;
}

bool psWorld::NeedsLoading(bool transitional)
{

    // Transitional means that we should allow whatever
    // levels are already loaded to stay loaded, but still
    // ensure that listed levels are loaded also.
    if (!transitional)
        for (unsigned i=0; i < regions.Length(); i++)
            if (!regions[i]->IsNeeded() )
                return true;

    for (unsigned i=0; i < regions.Length(); i++)
        if (!regions[i]->IsLoaded() )
                return true;
    return false;
}

bool psWorld::IsAllLoaded()
{
    for (unsigned i=0; i < regions.Length(); i++)
    {
        psRegion *rgn = regions[i];
        if (!rgn->IsLoaded() )
        {
            return false;            
        }
    }
    
    return true;
}

void psWorld::GetNotNeededRegions(csArray<iRegion*> & regs)
{
    for (unsigned i=0; i < regions.Length(); i++)
        if (!regions[i]->IsNeeded() )
            regs.Push(regions[i]->GetRegion());
}

void psWorld::BuildWarpCache()
{
    int sectorCount = engine->GetSectors()->GetCount();
    
    /// Clear existing entries
    transarray.Empty();

    transarray.SetSize(sectorCount);

    csReversibleTransform identity;

    for(int i=0; i<sectorCount; i++)
    {
        const csSet<csPtrKey<iMeshWrapper> >& portals = engine->GetSectors()->Get(i)->GetPortalMeshes();
        csSet<csPtrKey<iMeshWrapper> >::GlobalIterator it = portals.GetIterator ();
        while (it.HasNext ())
        {
            iMeshWrapper* portal_mesh = it.Next ();
            iPortalContainer* pc = portal_mesh->GetPortalContainer ();
            int i;
            for (i = 0 ; i < pc->GetPortalCount () ; i++)
            {
                iPortal* portal = pc->GetPortal (i);
                if (portal->CompleteSector(0))
                {
                    if (engine->GetSectors()->Find(portal->GetSector()) != -1)
                        transarray[i].Set(portal->GetSector(), portal->GetWarp());
                }
            }
        }
    }
}

//--------------------------------------------------------------------------

psRegion::psRegion(iObjectRegistry *obj_reg, psWorld * world, const char *file)
{
    object_reg = obj_reg;
    this->world = world;

    worlddir.Format("/planeshift/world/%s", file);
    worldfile  = "world";
    regionname = file;
    loaded     = false;
}

psRegion::~psRegion()
{
    if (loaded)
        Unload();
}


bool psRegion::Load(bool loadMeshes)
{
    if (loaded)
        return true;

    bool using3D;

    csRef<iEngine> engine = CS_QUERY_REGISTRY (object_reg, iEngine);

    // Find out if we are ever going to render 3D
    csRef<iGraphics3D> g3d = CS_QUERY_REGISTRY(object_reg, iGraphics3D);
    csRef<iFactory> factory = 
      SCF_QUERY_INTERFACE(g3d, iFactory);

    const char* g3Dname = factory->QueryClassID();
    using3D = (strcmp("crystalspace.graphics3d.null", g3Dname)? true: false);

    csString target;
    target.Format("%s/world", worlddir.GetData());
    csRef<iVFS> vfs = CS_QUERY_REGISTRY( object_reg, iVFS );

    csRef<iDocumentSystem> xml (
        CS_QUERY_REGISTRY (object_reg, iDocumentSystem));

    csRef<iDocument> doc = xml->CreateDocument();

    csRef<iDataBuffer> buf (vfs->ReadFile (target.GetData()));
    if (!buf || !buf->GetSize ())
    {
        Error2("Error loading world file. %s\n", target.GetData());
        return false;
    }

    const char* error = doc->Parse( buf );

    if( error )
    {
        Error3("Error %s loading file to be cleaned. %s\n",error, target.GetData());
        return false;
    }

    csRef<iDocumentNode> worldNode = doc->GetRoot()->GetNode("world");

    if(!loadMeshes)
    {
        // Clean the world file to remove all textures/meshes/models
        printf("Cleaning map file.\n");
        worldNode = Clean(worldNode);
    }
    

    // Save the old region to restore it if this load doesn't work

    // Create a new region with the given name, or select it if already there
    iRegion* cur_region = engine->CreateRegion (regionname);

    // Clear it out if it already existed
    cur_region->DeleteAll ();

    // Now load the map into the selected region
    csRef<iLoader> loader (CS_QUERY_REGISTRY (object_reg, iLoader));
    CS_ASSERT (loader != NULL);
    csRef<iVFS> VFS (CS_QUERY_REGISTRY (object_reg, iVFS));
    CS_ASSERT (VFS != NULL);
    VFS->ChDir (worlddir);
    engine->SetCacheManager(NULL);

    csTicks start = csGetTicks();
    Debug2(LOG_LOAD, 0,"Loading map file %s", worlddir.GetData());

    if (!loader->LoadMap(worldNode, CS_LOADER_KEEP_WORLD,cur_region, CS_LOADER_ACROSS_REGIONS, true)) //original
//    if (!loader->LoadMapFile(worldfile, CS_LOADER_KEEP_WORLD,cur_region, CS_LOADER_ACROSS_REGIONS))
//    if (!loader->LoadMapFile(worldfile, false,0, false))
    {
        Error3("loader->LoadMapFile(%s,%s) failed.",worlddir.GetData(),worldfile.GetData() );
        Error2("Region name was: %s", regionname.GetData() );
        return false;
    }
    Debug2(LOG_LOAD, 0,"After LoadMapFile, %dms elapsed", csGetTicks()-start);

    // Successfully loaded.  Now get textures ready, etc. and return.
    if (using3D)
    {
        cur_region->Prepare ();
        Debug2(LOG_LOAD, 0,"After Prepare, %dms elapsed", csGetTicks()-start);
    }

    if (using3D)
    {
        engine->PrecacheDraw (cur_region);
        Debug2(LOG_LOAD, 0,"After Precache, %dms elapsed", csGetTicks()-start);
    }

    world->MoveToSharedRegion(cur_region);

    if (loadMeshes)
    {
        SetupWorldColliders(engine,cur_region);
        Debug2(LOG_LOAD, 0,"After SetupWorldColliders, %dms elapsed\n", csGetTicks()-start);
    }
    
    loaded = true;
    printf("Region %s loaded successfully in %dms\n", (const char *)regionname, csGetTicks()-start);

    VFS->ChDir("/planeshift");
    engine->SetCacheManager(NULL);
    engine->GetCacheManager();

    return true;
}

csRef<iDocumentNode> psRegion::Clean(csRef<iDocumentNode> world)
{
    csRef<iDocumentSystem> xml (
    CS_QUERY_REGISTRY (object_reg, iDocumentSystem));

    csRef<iDocument> doc = xml->CreateDocument();
    csRef<iDocumentNode> node = doc->CreateRoot();

    // Copy the world node
    csRef<iDocumentNode> cleanedWorld = node->CreateNodeBefore(CS_NODE_ELEMENT);

    cleanedWorld->SetValue("world");

    // Copy the sector node
    csRef<iDocumentNodeIterator> sectors = world->GetNodes("sector");
    while ( sectors->HasNext() )
    {
        csRef<iDocumentNode> sector = sectors->Next();
        
        csRef<iDocumentNode> cleanedSector = cleanedWorld->CreateNodeBefore(CS_NODE_ELEMENT);
        cleanedSector->SetValue(sector->GetValue());

        // Copy the sector attributes
        csRef<iDocumentAttributeIterator> attrs = sector->GetAttributes();
        while(attrs->HasNext())
        {
            csRef<iDocumentAttribute> attr = attrs->Next();
            cleanedSector->SetAttribute(attr->GetName(), attr->GetValue());
        }

        // Copy the portals
        csRef<iDocumentNodeIterator> nodes = sector->GetNodes("portal");

        while(nodes->HasNext())
        {
            csRef<iDocumentNode> portal = nodes->Next();
            csRef<iDocumentNode> cleanedportal = cleanedSector->CreateNodeBefore(CS_NODE_ELEMENT);
            CloneNode(portal, cleanedportal);
        }

    }

    // Copy the start node
    csRef<iDocumentNodeIterator> startLocations = world->GetNodes("start");
    while (startLocations->HasNext())
    {
        csRef<iDocumentNode> start = startLocations->Next();
        csRef<iDocumentNode> cleanedStart = cleanedWorld->CreateNodeBefore(CS_NODE_ELEMENT);
        CloneNode(start, cleanedStart);
    }

    return cleanedWorld;
}

void psRegion::CloneNode (iDocumentNode* from, iDocumentNode* to)
{
  to->SetValue (from->GetValue ());
  csRef<iDocumentNodeIterator> it = from->GetNodes ();
  while (it->HasNext ())
  {
    csRef<iDocumentNode> child = it->Next ();
    csRef<iDocumentNode> child_clone = to->CreateNodeBefore (
    	child->GetType (), 0);
    CloneNode (child, child_clone);
  }
  csRef<iDocumentAttributeIterator> atit = from->GetAttributes ();
  while (atit->HasNext ())
  {
    csRef<iDocumentAttribute> attr = atit->Next ();
    to->SetAttribute (attr->GetName (), attr->GetValue ());
  }
}

bool psRegion::Unload()
{
    if (!loaded)
        return true;
    loaded = false;

    // The engine needs to not be in the region when it is unloaded!

    csRef<iEngine> engine = CS_QUERY_REGISTRY (object_reg, iEngine);

    iRegion* cur_region = engine->CreateRegion (regionname);

    // Delete everything in the region
    cur_region->DeleteAll ();

    return true;
}

void psRegion::SetupWorldColliders(iEngine *engine,iRegion* cur_region)
{
    csRef<iCollideSystem> cdsys =
        CS_QUERY_REGISTRY (object_reg, iCollideSystem);

    csRef<iObjectIterator> iter = cur_region->QueryObject()->GetIterator();
//return;
    csRef<iPolygonMesh> mesh;
    iObject *curr;
    while ( iter->HasNext() )
    {
        curr = iter->Next();
        // regions hold many objects, but only meshes are collide-able
        csRef<iMeshWrapper> sp = SCF_QUERY_INTERFACE(curr,iMeshWrapper);
        if (sp && sp->GetMeshObject() )
        {
            mesh = sp->GetMeshObject()->GetObjectModel()->GetPolygonMeshColldet();
        
            if ( mesh )
            {
                csRef<csColliderWrapper> cw = csPtr<csColliderWrapper> (
                    new csColliderWrapper( sp->QueryObject(), cdsys, mesh));
                    cw->SetName(sp->QueryObject()->GetName());
            }
    
            csRef<iThingState> thing = SCF_QUERY_INTERFACE(sp->GetMeshObject(),iThingState);
            if (thing)
                thing->Prepare ();
        }
    }
}

iRegion * psRegion::GetRegion()
{
    csRef<iEngine> engine = CS_QUERY_REGISTRY (object_reg, iEngine);
    return engine->GetRegions()->FindByName(regionname);
}
