/*
 * weather.cpp Author: Christian Svensson
 *
 * Copyright (C) 2005 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.
 *
 * Holder of all weather types
 */
#include <psconfig.h>

#include <csutil/objreg.h>
#include <csutil/sysfunc.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 <iengine/camera.h>
#include <imesh/object.h>
#include <imesh/partsys.h>
#include <imesh/snow.h>
#include <imesh/rain.h>
#include <csutil/cscolor.h>
#include <csutil/flags.h>
#include <cstool/fogmath.h>
#include <iutil/vfs.h>

#include "iclient/isoundmngr.h"
#include "pscelclient.h"
#include "globals.h"
#include "weather.h"
#include "pscamera.h"
#include "modehandler.h"



void WeatherInfo::Fade(WeatherParams* wp, int delta)
{
    if (wp->fade_time)
    {
        // Only interpolate to the point where fade stops
        if (delta > wp->fade_time)
        {
            delta = wp->fade_time;
        }

        float pct = (float)delta/(float)wp->fade_time;
        
        wp->value += (int)((wp->fade_value - wp->value)*pct);
        
        // Substract the delta time from the fade.
        wp->fade_time -= delta;
    }
}



/* Keeper of the objects */
WeatherObject::WeatherObject(WeatherInfo* parent)
{
    this->parent = parent;
    weather_sound = NULL;
}
WeatherObject::~WeatherObject()
{
    Destroy();
}

void WeatherObject::Destroy()
{
    if(mesh)
    {
        // Remove
        psengine->GetEngine()->RemoveObject(mesh);
        CHECK_FINAL_DECREF_CONFIRM(mesh,"WeatherObject iMeshWrapper");
        mesh.Invalidate();
    }
}

bool WeatherObject::Valid()
{
    return mesh.IsValid();
}

iSector* WeatherObject::GetSector()
{
    if(Valid())
        return mesh->GetMovable()->GetSectors()->Get(0);

    return NULL;
}

void WeatherObject::RefreshSector()
{
    if(!Valid())
        return;

    // Refresh the flags
    iSector* sector = mesh->GetMovable()->GetSectors ()->Get(0);
    mesh->GetMovable()->ClearSectors();
    mesh->GetMovable()->SetSector(sector);
    mesh->GetMovable()->UpdateMove();
}

void WeatherObject::MoveTo( WeatherInfo* new_parent ,iSector* sector)
{
    if(!Valid())
        return;

    mesh->GetMovable()->ClearSectors();
    mesh->GetMovable()->SetSector(sector);
    mesh->GetMovable()->UpdateMove();

    SetParent(new_parent);
    SetupMesh(bbox); // Setup the mesh again for the new parent info
}

void WeatherObject::MoveTo(csVector3 pos)
{
    mesh->GetMovable()->SetPosition(pos);
    mesh->GetMovable()->UpdateMove();
}

void WeatherObject::StartFollow()
{
    if(Valid())
    {
        mesh->GetFlags().Set(CS_ENTITY_CAMERA);  // Makes weather mesh follow player around
        RefreshSector();
    }
}

void WeatherObject::StopFollow()
{
    if(Valid())
    {
        mesh->GetFlags().Reset(CS_ENTITY_CAMERA);
        RefreshSector();
    }
}


/***************************************************** Rain object ****/

RainWeatherObject::RainWeatherObject(WeatherInfo* parent) : WeatherObject(parent)
{
}
RainWeatherObject::~RainWeatherObject()
{
}

WeatherSound RainWeatherObject::GetWeatherSoundForced()
{
    if(parent->downfall_params.value > 2000)
        return WEATHER_SOUND_RAIN_HEAVY;
    else
        return WEATHER_SOUND_RAIN_LIGHT;
}

WeatherSound RainWeatherObject::GetWeatherSound()
{
    if(Valid())
        return GetWeatherSoundForced();
    else
        return WEATHER_SOUND_CLEAR;
}

float RainWeatherObject::GetDensity(int drops)
{
    return (float)drops / (float)WEATHER_MAX_RAIN_DROPS;
}

csBox3 RainWeatherObject::CreateDefaultBBox()
{
    csBox3 bbox;
    bbox.Set(csVector3(-5,-10,-5),csVector3(5,10,5));

    return bbox;
}

void RainWeatherObject::Destroy()
{
    WeatherObject::Destroy();
    psengine->GetModeHandler()->RemoveDownfallObject(this);
}


void RainWeatherObject::MoveTo(WeatherInfo* wi,iSector* sect)
{
    WeatherObject::MoveTo(wi,sect);
    psengine->GetModeHandler()->AddDownfallObject(this);

    // refresh rainstate
    SetupMesh(bbox);

}

bool RainWeatherObject::CreateMesh()
{
    // Create new rain
    char *matname = "raindrop";

    // First check if the material exists, and load it.
    iMaterialWrapper* mat = psengine->GetEngine()->GetMaterialList()->FindByName(matname);
    if (!mat)
    {
        Bug2("Can't find material '%s' in memory for rain!", matname);
        return false;
    }

    if(!mfw)
    {
        mfw = psengine->GetEngine ()->CreateMeshFactory ("crystalspace.mesh.object.rain", "rain");
        if (!mfw)
        {
            Bug1("Could not create rain factory.");
            return false;
        }
    }

    // Get the sector
    iSector* sector = psengine->GetEngine()->FindSector(parent->sector);
        
    // Create the mesh
    mesh = psengine->GetEngine ()->CreateMeshWrapper(mfw, "rain", sector,
                                                    csVector3 (0, 0, 0));

    mesh->GetMeshObject()->SetMaterialWrapper(mat);
    psengine->GetModeHandler()->AddDownfallObject(this);

    return true;
}

void RainWeatherObject::SetupMesh(csBox3 bbox)
{
    float speed = 5;

    this->bbox = bbox; // for reuse

    // Set render priority
    iEngine* e = psengine->GetEngine ();
    int c = e->GetAlphaRenderPriority ();
    mesh->SetRenderPriority (c);
    mesh->SetZBufMode(CS_ZBUF_TEST);

    csRef<iRainState> rainstate(SCF_QUERY_INTERFACE (mesh->GetMeshObject (), iRainState));

    csColor color = csColor (.25,.25,.25);
    mesh->GetMeshObject()->SetMixMode (CS_FX_ADD);
    mesh->GetMeshObject()->SetColor (color);

    SetDrops(parent->downfall_params.value);

    rainstate->SetDropSize (.2F/50.0F, .08F);
    rainstate->SetLighting (false);
    rainstate->SetBox (bbox.Min (), bbox.Max ());
    rainstate->SetFallSpeed (csVector3 (0.0f, -speed, 0));

}

void RainWeatherObject::SetDrops(int drops)
{
    csRef<iRainState> rainstate(SCF_QUERY_INTERFACE (mesh->GetMeshObject (), iRainState));

    if(drops > WEATHER_MAX_RAIN_DROPS)
    {
        drops = WEATHER_MAX_RAIN_DROPS;
    }
    if (drops != rainstate->GetParticleCount())
    {
        rainstate->SetParticleCount (drops);
    }
}


void RainWeatherObject::Update(csTicks delta)
{
    //    Notify2(LOG_WEATHER,"Updating rain object, Setting drops: %d",parent->downfall_params.value);
    
    SetDrops(parent->downfall_params.value);
}


/***************************************************** Snow object ****/

SnowWeatherObject::SnowWeatherObject(WeatherInfo* parent)  : WeatherObject(parent)
{}
SnowWeatherObject::~SnowWeatherObject()
{}

WeatherSound SnowWeatherObject::GetWeatherSoundForced()
{
    if(parent->downfall_params.value > 2000)
        return WEATHER_SOUND_SNOW_HEAVY;
    else
        return WEATHER_SOUND_SNOW_LIGHT;
}

WeatherSound SnowWeatherObject::GetWeatherSound()
{
    if(Valid())
        return GetWeatherSoundForced();
    else
        return WEATHER_SOUND_CLEAR;
}

float SnowWeatherObject::GetDensity(int drops)
{
    return (float)drops / (float)WEATHER_MAX_SNOW_FALKES;
}

csBox3 SnowWeatherObject::CreateDefaultBBox()
{
    csBox3 bbox;
    bbox.Set(csVector3(-10,-20,-10),csVector3(10,20,10));

    return bbox;
}

void SnowWeatherObject::Destroy()
{
    WeatherObject::Destroy();
    psengine->GetModeHandler()->RemoveDownfallObject(this);
}

void SnowWeatherObject::MoveTo(WeatherInfo* wi,iSector* sect)
{
    WeatherObject::MoveTo(wi,sect);
    psengine->GetModeHandler()->AddDownfallObject(this);

    SetupMesh(bbox);
}

bool SnowWeatherObject::CreateMesh()
{
    // Create new rain
    char *matname = "snowflake";

    // First check if the material exists, and load it.
    iMaterialWrapper* mat = psengine->GetEngine()->GetMaterialList()->FindByName(matname);
    if (!mat)
    {
        Bug2("Can't find material '%s' in memory for snow!", matname);
        return false;
    }

    if(!mfw)
    {
        mfw = psengine->GetEngine ()->CreateMeshFactory ("crystalspace.mesh.object.snow", "snow");
        if (!mfw)
        {
            Bug1("Could not create rain factory.");
            return false;
        }
    }

    // Get the sector
    iSector* sector = psengine->GetEngine()->FindSector(parent->sector);
        
    // Create the mesh
    mesh = psengine->GetEngine ()->CreateMeshWrapper(mfw, "snow", sector,
                                                    csVector3 (0, 0, 0));

    mesh->GetMeshObject()->SetMaterialWrapper(mat);
    psengine->GetModeHandler()->AddDownfallObject(this);

    return true;
}

void SnowWeatherObject::SetupMesh(csBox3 bbox)
{
    float speed = 1.5f;

    this->bbox = bbox; // for reuse

    // Set render priority
    iEngine* e = psengine->GetEngine ();
    int c = e->GetAlphaRenderPriority ();
    mesh->SetRenderPriority (c);
    mesh->SetZBufMode(CS_ZBUF_TEST);

    // Snow needs a particle system, weather already has it's own
    csRef<iParticleState> partstate ( SCF_QUERY_INTERFACE (mesh->GetMeshObject (), iParticleState));
    partstate->SetMixMode (CS_FX_ADD);
    mesh->GetMeshObject()->SetColor (csColor (.25,.25,.25));

    csRef<iSnowState> snowstate(SCF_QUERY_INTERFACE (mesh->GetMeshObject (), iSnowState));

    // Setup the particel manager
    SetDrops(parent->downfall_params.value);

    snowstate->SetDropSize (0.07f, 0.07f);
    snowstate->SetLighting (false);
    snowstate->SetBox (bbox.Min (), bbox.Max ());
    snowstate->SetFallSpeed (csVector3 (0, -speed, 0));
    snowstate->SetSwirl (0.7f);
}

void SnowWeatherObject::SetDrops(int drops)
{
    csRef<iSnowState> snowstate(SCF_QUERY_INTERFACE (mesh->GetMeshObject (), iSnowState));

    if(drops > WEATHER_MAX_SNOW_FALKES)
    {
        drops = WEATHER_MAX_SNOW_FALKES;
    }
    if (drops != snowstate->GetParticleCount())
    {
        snowstate->SetParticleCount (drops);
    }
}



void SnowWeatherObject::Update(csTicks delta)
{
    //    Notify2(LOG_WEATHER,"Updating snow object, Setting drops: %d",parent->downfall_params.value);
    
    SetDrops(parent->downfall_params.value);
}


/***************************************************** Fog object ****/

FogWeatherObject::FogWeatherObject(WeatherInfo* parent) : WeatherObject(parent)
{
    sector = psengine->GetEngine()->FindSector(parent->sector);
    SetColor(parent->r / 255.0f, parent->g / 255.0f, parent->b / 255.0f);
    applied = false;
}

FogWeatherObject::~FogWeatherObject()
{
}

void FogWeatherObject::Destroy()
{   
    //It is necessary to get the new sector to which everything is referring to.
    iSector* sector = psengine->GetEngine()->FindSector(parent->sector); 
    if (sector)
    {
        sector->DisableFog();
    }

    applied = false;
}

bool FogWeatherObject::Valid()
{
    return applied;
}

void FogWeatherObject::SetColor(float r,float g,float b)
{
    color = csColor(r,g,b);
    if(applied)
    {
        CreateMesh(); // Refresh
    }
}

bool FogWeatherObject::CreateMesh()
{
    iSector* sector = psengine->GetEngine()->FindSector(parent->sector);
    if(!sector)
        return false;

    float density = GetDensity(parent->fog_params.value);
    sector->SetFog(density,color);

    // Use this as a clipping plane if it's not adaptive
    psCamera* cam = psengine->GetPSCamera();
    if( cam && cam->GetICamera()->GetSector() == sector && !cam->GetDistanceCfg().adaptive )
    {
        float distance = csFogMath::DistanceForOpacity(density, 255.0f/256.0f);
        cam->UseFixedDistanceClipping(distance); // It's only the last applied fog in this sector that matters
    }

    applied = true;
    return true;
}

void FogWeatherObject::Update(csTicks delta)
{
    //    Notify2(LOG_WEATHER,"Updating fog object, Setting fog: %d",parent->fog_params.value);
    SetColor(parent->r / 255.0f, parent->g / 255.0f, parent->b / 255.0f);
    CreateMesh();
}


float FogWeatherObject::GetDensity(int density)
{
    return (float)density / 100000.0f;
}

iSector* FogWeatherObject::GetSector()
{
    return sector;
}

WeatherSound FogWeatherObject::GetWeatherSoundForced()
{
    return WEATHER_SOUND_FOG;
}

WeatherSound FogWeatherObject::GetWeatherSound()
{
    if(Valid())
        return GetWeatherSoundForced();
    else
        return WEATHER_SOUND_CLEAR;
}

void FogWeatherObject::MoveTo(WeatherInfo* new_parent,iSector* to)
{
    Destroy(); // Reset old fog

    parent = new_parent;
    sector = to;

    CreateMesh();
}
