/*
 * zonehandler.cpp    Keith Fulton <keith@paqrat.com>
 *
 * Copyright (C) 2003 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 <csutil/objreg.h>
#include <iutil/object.h>
#include <iengine/engine.h>
#include <iengine/sector.h>
#include <iengine/light.h>
#include <iengine/material.h>
#include <iengine/mesh.h>
#include <iengine/movable.h>
#include <iengine/sector.h>
#include <imesh/partsys.h>
#include <imesh/rain.h>
#include <csutil/cscolor.h>
#include <iutil/vfs.h>
#include <csutil/sysfunc.h>
#include <ivaria/engseq.h>

#include <physicallayer/pl.h>
#include <physicallayer/entity.h>
#include <propclass/mesh.h>
#include "physicallayer/propclas.h"

#include "globals.h"
#include "psbehave/psworld.h"
#include "zonehandler.h"
#include "pscamera.h"
#include "globals.h"

#include "iclient/isoundmngr.h"
#include "net/messages.h"
#include "net/msghandler.h"
#include "pscelclient.h"
#include "modehandler.h"
#include "util/psscf.h"
#include "util/log.h"

#include "paws/pawsmanager.h"
#include "paws/pawsprogressbar.h"
#include "gui/pawsloading.h"
#include "pscharcontrol.h"

ZoneHandler::ZoneHandler(MsgHandler* mh,iObjectRegistry* obj_reg, psCelClient *cc)
{
    msghandler     = mh;
    object_reg     = obj_reg;
    world          = NULL;
    celclient      = cc;
    haveNewPos     = false;

    msghandler->Subscribe(this,MSGTYPE_NEWSECTOR);

    valid = LoadZoneInfo();
    needsToLoadMaps = false;
    
    loadWindow = NULL;
    loadProgressBar = NULL;
}

ZoneHandler::~ZoneHandler()
{
    if (msghandler)
    {
        msghandler->Unsubscribe(this,MSGTYPE_NEWSECTOR);
    }
}

bool ZoneHandler::FindLoadWindow()
{
    if (loadProgressBar != NULL)
        return true;  // Already found

    loadWindow = static_cast <pawsLoadWindow*> (PawsManager::GetSingleton().FindWidget("LoadWindow"));
    if (loadWindow == NULL)
        return false;

    loadProgressBar = static_cast <pawsProgressBar*> (loadWindow->FindWidget("Progress"));
    if (loadProgressBar == NULL)
        return false;
        
    return true;
}

ZoneLoadInfo * ZoneHandler::FindZone(const char* sector)
{
    ZoneLoadInfo key(sector, NULL);
    ZoneLoadInfo* zone = zonelist.Find(&key);

    if (zone == NULL)
        Error2("Fatal error: Could not find zone info for sector %s!\n",sector);

    return zone;
}

bool ZoneHandler::LoadZoneInfo()
{
    iVFS* vfs = psengine->GetVFS ();
    if (!vfs)
        return false;
    iDocumentSystem* xml = psengine->GetXMLParser ();
    csRef<iDocument> doc = xml->CreateDocument();

    csRef<iFile> file = vfs->Open("/planeshift/data/zoneinfo.xml", VFS_FILE_READ);
    if (!file)
        return false;

    const char* error = doc->Parse(file);
    if (error)
    {
        Error2("Error Loading Zone Data: %s\n", error);
        return false;
    }

    csRef<iDocumentNodeIterator> zoneIter = doc->GetRoot()->GetNode("zonelist")->GetNodes("zone");

    while (zoneIter->HasNext())
    {
        csRef<iDocumentNode> zoneNode = zoneIter->Next();

        csRef<iDocumentNodeIterator> regionIter = zoneNode->GetNodes("region");
        while (regionIter->HasNext())
        {
            // Parse attributes from tag
            csRef<iDocumentNode> regionNode = regionIter->Next();
            csString mapname = regionNode->GetAttributeValue("map");
            
            bool found = false;
            for(size_t i =0;i<alllist.Length();i++)
            {
                if(mapname == alllist[i])
                {
                    found = true;
                    break;
                }
            }

            if(!found)
                alllist.Push(mapname);
        }

        ZoneLoadInfo *zone = new ZoneLoadInfo(zoneNode);
        zonelist.Insert(zone, true);
    }

    return true;
}


void ZoneHandler::HandleMessage(MsgEntry* me)
{
    psNewSectorMessage msg(me);

    Notify3(LOG_LOAD, "Crossed from sector %s to sector %s.", msg.oldSector.GetData(), msg.newSector.GetData() );

    if (needsToLoadMaps) 
    {
        Warning2(LOG_LOAD, "Still loading maps, ignoring crossing to sector %s.", msg.newSector.GetData());
        return;
    }

    // We don't load the maps here: we just remember that we need to do it and we show LoadingWindow
    // If we began to load the maps immediately at this place, the LoadingWindow would not have a chance to be drawn

    ZoneLoadInfo * zone = FindZone(msg.newSector);
    if (zone == NULL)
    {
        psengine->FatalError("The sector you have entered couldn't be loaded.\nPlease check your logs for details.");
        return;
    }
    
    FlagRegions(zone);

    if (world->NeedsLoading(zone->transitional))
    {
        needsToLoadMaps = true;
        sectorToLoad = msg.newSector;
        haveNewPos = true;
        newPos = msg.pos;

        psengine->GetCharControl()->GetMovementManager()->StopAllMovement();
        
        if (FindLoadWindow())
        {
            loadWindow->SetAlwaysOnTop(true);
            loadWindow->Clear();
            // If the area has its own loading image, use it
            if (zone->loadImage) {
                Debug2(LOG_LOAD, 0, "Setting background %s", zone->loadImage.GetData());
                loadWindow->SetBackground(zone->loadImage.GetData());
            }
            loadWindow->AddText("Loading region");
            loadWindow->Show();
            psengine->ForceRefresh();
        }
    }
    else // If we don't need to load map, set player's position immediately
        MovePlayerTo(msg.pos, msg.newSector);

    if(!needsToLoadMaps)
        psengine->GetModeHandler()->DoneLoading(msg.newSector);
}

void ZoneHandler::MovePlayerTo(const csVector3 & newPos, const csString & newSector)
{
    csVector3 pos;
    float yrot;
    iSector* sector;
    csRef<iPcLinearMovement> pcmove;

    pcmove = celclient->GetMainPlayer()->linmove;
    pcmove->GetLastPosition (pos, yrot, sector);            // retrieve last yrot
    sector = psengine->GetEngine()->FindSector(newSector);
    if (sector != NULL)
    {
        Notify5(LOG_LOAD, "Setting position of player %f %f %f in sector '%s'", newPos.x, newPos.y, newPos.z, newSector.GetData());
        pcmove->SetPosition(newPos, yrot, sector);          // set new position
    }
    else
    {
        Error2("Couldn't find sector '%s'", newSector.GetData());
    }

    if (FindLoadWindow())
        loadProgressBar->Completed();
}

void ZoneHandler::OnDrawingFinished()
{
    if (needsToLoadMaps)
    {
        //Error1("Going to load and unload maps");
        if (ExecuteFlaggedRegions(sectorToLoad))
        {
            needsToLoadMaps = false;

            psengine->SetLoadedMap(true);
            psengine->GetModeHandler()->ClearLightFadeSettings();  // make sure new map gets relit for time of day

            if (haveNewPos) 
            {
                MovePlayerTo(newPos, sectorToLoad);
                psengine->GetPSCamera()->ResetCameraPositioning();
            }
        } 

        if (FindLoadWindow())
        {
            loadProgressBar->SetCurrentValue(loadProgressBar->GetCurrentValue() + 1);
            psengine->ForceRefresh();
        }
    }
}

void ZoneHandler::FlagRegions(ZoneLoadInfo* zone)
{
    if (!zone)
        return;

    if(!keepMapsLoaded)
        world->FlagAllRegionsAsNotNeeded();

    csArray<csString> loading;
    for (size_t i=0; i < zone->regions.Length(); i++)
    {
        csString *map = zone->regions[i];
        world->FlagRegionAsNeeded(*map);
        loading.Push(*map);
    }

    if(loadAllMaps)
    {
        Debug1(LOG_LOAD,0, "Flagging all maps to be loaded");
        for (size_t i=0; i<alllist.Length(); i++)
        {
            bool found = false;
            for (size_t j=0; j<loading.Length(); j++)
            {
                if(loading[j] == alllist[i])
                {
                    found = true;
                    break;
                }
            }
            if(found)
                continue;

            // Flag
            world->FlagRegionAsNeeded(alllist[i]);
        }
    }

    if (FindLoadWindow())
    {
        if (loadAllMaps)
        {
            loadProgressBar->SetTotalValue(alllist.Length());
        }
        else
        {
            csArray<iRegion*> deletedRegions;
            world->GetNotNeededRegions(deletedRegions);
            loadProgressBar->SetTotalValue(zone->regions.Length() + deletedRegions.Length());
        }

        loadProgressBar->SetCurrentValue(0);
        psengine->ForceRefresh();
    }
}

void ZoneHandler::LoadZone(const char* sector)
{
    sectorToLoad = sector;
    needsToLoadMaps = true;
    FlagRegions( FindZone(sectorToLoad) );
}

inline bool ZoneHandler::ExecuteFlaggedRegions(const csString & sector)
{
    ZoneLoadInfo* found = FindZone(sector);

    if (found)
    {
        // If the sector has a loading screen, display it
        if (found->loadImage && FindLoadWindow())
        {
            Debug2(LOG_LOAD, 0, "Setting background %s", found->loadImage.GetData());
            loadWindow->SetBackground(found->loadImage.GetData());
            psengine->ForceRefresh();
        }

        if(!found->transitional)
        {
            csArray<iRegion*> deletedRegions;
            world->GetNotNeededRegions(deletedRegions);
            celclient->OnRegionsDeleted(deletedRegions);
        }

        if (!world->ExecuteFlaggedRegions(found->transitional, true))
            return false;

        celclient->OnMapsLoaded();

        // If this is the first time, don't hide the loading (we still got NPCs to load)
        if (psengine->IsGameLoaded())
        {
            if (FindLoadWindow())
                loadWindow->Hide();
        }
        else
        {
            // Else we need to call this since it's not a "new" sector
            psengine->GetModeHandler()->DoneLoading(sector);
        }
    }
    
    return true;
}

ZoneLoadInfo::ZoneLoadInfo(const char *sector, const char *image)
{
    inSector = sector;
    loadImage = image;
}

ZoneLoadInfo::ZoneLoadInfo(iDocumentNode *node)
{
    csString trans;

    inSector = node->GetAttributeValue("sector");
    loadImage = node->GetAttributeValue("loadimage");
    trans = node->GetAttributeValue("transitional");
    
    transitional = (trans=="yes");

    csRef<iDocumentNodeIterator> regionIter = node->GetNodes("region");

    while (regionIter->HasNext())
    {
        // Parse attributes from tag
        csRef<iDocumentNode> regionNode = regionIter->Next();
        csString *mapname = new csString(regionNode->GetAttributeValue("map"));
        regions.Push(mapname);
    }    
}

