/*
  * psserverdr.cpp by Matze Braun <MatzeBraun@gmx.de>
  *
  * Copyright (C) 2002 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 <iutil/databuff.h>
#include <iengine/movable.h>
#include <iengine/mesh.h>
#include <iutil/object.h>
#include "client.h"
#include "clients.h"
#include "net/message.h"
#include "net/msghandler.h"
#include "engine/celbase.h"
#include "engine/drmessage.h"
#include "engine/netpersist.h"
#include <propclass/linmove.h>
#include <propclass/colldet.h>

#include "psserverdr.h"
#include "tutorialmanager.h"
#include "events.h"

#include "psserver.h"
#include "cachemanager.h"
#include "globals.h"
#include "playergroup.h"
#include "gem.h"
#include "entitymanager.h"

#include "paladinjr.h"

#include "util/serverconsole.h"
#include "util/log.h"
#include "util/psconst.h"
#include "util/mathscript.h"
#include "psproxlist.h"
#include "util/eventmanager.h"


psServerDR::psServerDR()
{
    entitymanager = NULL;
    clients = NULL;
    paladin = NULL;
}

psServerDR::~psServerDR()
{
    if (psserver->GetEventManager())
        psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_DEAD_RECKONING);
    delete paladin;
}

bool psServerDR::Initialize(EntityManager* entitymanager,
    ClientConnectionSet* clients)
{
    psServerDR::entitymanager  = entitymanager;
    psServerDR::clients        = clients;

    if (!psserver->GetEventManager()->Subscribe(this,MSGTYPE_DEAD_RECKONING,REQUIRE_READY_CLIENT))
        return false;

    calc_damage   = psserver->GetMathScriptEngine()->FindScript("Calculate Fall Damage");

    // Output var bindings here
    var_fall_height = calc_damage->GetVar("FallHeight");
    var_fall_dmg    = calc_damage->GetVar("Damage");

    paladin = new PaladinJr;
    paladin->Initialize(entitymanager);
    
    return true;
}

void psServerDR::SendPersist()
{
    // no server side actions yet

    return;
}

void psServerDR::HandleFallDamage(gemActor *actor,int clientnum, float height)
{
    float fallHeight = actor->FallEnded(height);

    var_fall_height->SetValue(fallHeight);
    calc_damage->Execute();
    // CPrintf(CON_DEBUG, "Fall damage=%1.2f\n",var_fall_dmg->GetValue() );

    if (var_fall_dmg->GetValue() > 0)
    {
        bool died = (var_fall_dmg->GetValue() > actor->GetCharacterData()->GetHP());

        actor->DoDamage(NULL, var_fall_dmg->GetValue() );
        if (died)
        {
            //Client died
            psserver->SendSystemInfo(clientnum, "You fell down and died.");
        }
    }
}


void psServerDR::HandleMessage (MsgEntry* me,Client *client)
{
    psDRMessage drmsg(me,CacheManager::GetSingleton().GetMsgStrings(),psserver->GetEntityManager()->GetEngine() );
    if (!drmsg.valid)
    {
        Debug2(LOG_NET,me->clientnum,"Received unparsable psDRMessage from client %u.\n",me->clientnum);
        return;
    }

    gemActor *actor = client->GetActor();
    if (actor == NULL)
    {
        Error1("Recieved DR data for NULL actor.");
        return;
    }

    if ( !actor->IsAllowedToMove() || client->IsFrozen() )  // Is this movement allowed?
    {
        actor->MoveToLastPos();
        return;  // Ignore this DR data, and force the actor to use last.
    }

    if (drmsg.sector == NULL)
    {
        Error2("Client sent the server DR message with unknown sector \"%s\" !", drmsg.sectorName.GetData());
        psserver->SendSystemInfo(me->clientnum,
                                 "Received unknown sector \"%s\" - moving you to a valid position.",
                                 drmsg.sectorName.GetData() );
        actor->MoveToValidPos();
        return;
    }

    // These values must be sane or the proxlist will die.
    // The != test tests for NaN because if it is, the proxlist will mysteriously die (found out the hard way)
    if(drmsg.pos.x != drmsg.pos.x || drmsg.pos.y != drmsg.pos.y || drmsg.pos.z != drmsg.pos.z ||
        fabs(drmsg.pos.x) > 100000 || fabs(drmsg.pos.y) > 100000 || fabs(drmsg.pos.z) > 100000)
    {
        psserver->SendSystemInfo(me->clientnum, "Received out of bounds positional data, resetting your position.");
        actor->MoveToValidPos();
        return;
    }
    else if(drmsg.vel.x != drmsg.vel.x || drmsg.vel.y != drmsg.vel.y || drmsg.vel.z != drmsg.vel.z ||
        fabs(drmsg.vel.x) > 1000 || fabs(drmsg.vel.y) > 1000 || fabs(drmsg.vel.z) > 1000)
    {
        psserver->SendSystemInfo(me->clientnum, "Received out of bounds velocity data, resetting your position.");
        actor->MoveToValidPos();
        return;
    }
    else if(drmsg.worldVel.x != drmsg.worldVel.x || drmsg.worldVel.y != drmsg.worldVel.y || drmsg.worldVel.z != drmsg.worldVel.z ||
        fabs(drmsg.worldVel.x) > 1000 || fabs(drmsg.worldVel.y) > 1000 || fabs(drmsg.worldVel.z) > 1000)
    {
        psserver->SendSystemInfo(me->clientnum, "Received out of bounds velocity data, resetting your position.");
        actor->MoveToValidPos();
        return;
    }

    paladin->PredictClient(client, drmsg);

    // Go ahead and update the server version
    if (!actor->SetDRData(drmsg)) // out of date message if returns false
        return;

    // Check for Movement Tutorial Required.
    // Usually we don't want to check but DR msgs are so frequent,
    // perf hit is unacceptable otherwise.
    if (actor->GetCharacterData()->NeedsHelpEvent(TutorialManager::MOVEMENT))
    {
        if (!drmsg.vel.IsZero() || drmsg.ang_vel)
        {
            psMovementEvent evt(client->GetClientNum() );
            evt.FireEvent();
        }
    }

    // Update falling status
    if (actor->pcmove->IsOnGround())
    {
        if (actor->IsFalling())
        {
            // GM flag
            if(!actor->safefall)
                HandleFallDamage(actor,me->clientnum, drmsg.pos.y);
            else
                actor->FallEnded(drmsg.pos.y);
        }
    }
    else if (!actor->IsFalling())
    {
        //actor->pcmove->ClearPortalDisplacement();
        // Perform the extrapolation here to find warp portals
        //if(drmsg.vel.y > -50 && drmsg.vel.y < 50)                               // Sanity check
        //    actor->pcmove->UpdateDRDelta(1100);

        iSector* sector = actor->GetMeshWrapper()->GetMovable()->GetSectors()->Get(0);
        actor->FallBegan(drmsg.pos.y, 0.0f, sector);
        
        // Reset to correct position
        //actor->SetDRData(drmsg);
    }

    //Check if client fell off the end of the world or has left it
    if (drmsg.pos.y < -1000 && drmsg.worldVel.y < 0)
    {
        actor->Kill(NULL);
        psserver->SendSystemInfo(me->clientnum, "You fell off the end of the world and died.");
    }
    else if (drmsg.pos.y > 1000 && drmsg.vel.y > 0)
    {
        actor->Kill(NULL);
        psserver->SendSystemInfo(me->clientnum, "You flew off the end of the world and died.");
    }

    // Now multicast to other clients
    csTicks time = csGetTicks();
    actor->UpdateProxList();
    if (csGetTicks() - time > 500)
    {   
        csString status;
        status.Format("Warning: Spent %u time updating proxlist for %s!", csGetTicks() - time, actor->GetName());
        psString out;
        actor->GetProxList()->DebugDumpContents(out);
        out.ReplaceAllSubString("\n", " ");

        status.Append(out);
        psserver->GetLogCSV()->Write(CSV_STATUS, status);
    }


    psserver->GetEventManager()->Multicast(me,
                          actor->GetMulticastClients(),
                          me->clientnum,PROX_LIST_ANY_RANGE);

    paladin->CheckClient(client);
    
    if (strcmp(drmsg.sector->QueryObject()->GetName(), "DRexit") == 0)
    {
        actor->pcmove->SetOnGround(false);
        actor->MoveToSpawnPos();
    }
}
