/*
 * weathermanager.cpp
 *
 * 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 <csgeom/vector3.h>

#include "weathermanager.h"
#include "psserver.h"
#include "cachemanager.h"
#include "globals.h"
#include "netmanager.h"
#include "client.h"
#include "gem.h"
#include "util/eventmanager.h"

//#define WEATHER_DEBUG

WeatherManager::WeatherManager()
{
    current_daynight = 0;

    randomgen = psserver->rng;

}

WeatherManager::~WeatherManager()
{
}

void WeatherManager::Initialize()
{
    // Start rain/snow for all sectors
    csHash<psSectorInfo *>::GlobalIterator iter = CacheManager::GetSingleton().GetSectorIterator();
    while (iter.HasNext() )
    {
        psSectorInfo *si = iter.Next();

        StartWeather(si);

    }

    // Start the time of day clock
    QueueNextEvent(GAME_HOUR,psWeatherMessage::DAYNIGHT,12,0,0,"",NULL);
}

void WeatherManager::StartWeather(psSectorInfo *si)
{
    // Is rain/snow enabled for this sector
    if (si->rain_enabled)
    {
        // Queue event to start rain/snow
        QueueNextEvent(si->GetRandomRainGap(),
                       psWeatherMessage::RAIN,
                       si->GetRandomRainDrops(),
                       0, // Duration is calculated when stop event is created
                       0, // Fade is calculated when sending weather event
                       si->name,
                       si);
    }
}

void WeatherManager::UpdateClient(uint32_t cnum)
{
    psWeatherMessage::NetWeatherInfo info;

    // Update client with weather for all sectors
    csHash<psSectorInfo *>::GlobalIterator iter = CacheManager::GetSingleton().GetSectorIterator();
    while (iter.HasNext() )
    {
        psSectorInfo *sector = iter.Next();
        info.has_downfall  = false;
        info.has_lightning = false;
        info.has_fog       = false;
        info.sector = sector->name;
        
        if(sector->is_raining)
        {
            info.has_downfall = true;
            info.downfall_is_snow = false;
            
            info.downfall_drops = sector->current_rain_drops;
            info.downfall_fade = 0; // Don't fade in
        }
        else if (sector->is_snowing ) //If it is snowing
        {
            info.has_downfall = true;
            info.downfall_is_snow = true;
            
            info.downfall_drops = sector->current_rain_drops;
            info.downfall_fade = 0; // Don't fade in
        }
        
        if( sector->fog_density > 0 || sector->current_rain_fog_density > 0 )
        {
            info.has_fog = true;
            info.fog_density = 
                MAX( sector->fog_density, sector->current_rain_fog_density );
            info.fog_fade = 0; // Don't fade in
            info.r = sector->r;info.g = sector->g;info.b = sector->b;

        }
        if (info.has_downfall || info.has_fog)
        {
            psWeatherMessage weather(cnum,info);
            weather.SendMessage();
        }
    }

    // Update time
    psWeatherMessage time(cnum,current_daynight);
    time.SendMessage();

    // Send
#ifdef WEATHER_DEBUG
    printf("Sending weather and time updates to client %u\n",cnum);
#endif

}

void WeatherManager::QueueNextEvent(int delayticks,
                                    int eventtype,
                                    int eventvalue,
                                    int duration,
                                    int fade,
                                    const char *sector,
                                    psSectorInfo *si,
                                    uint clientnum,
                                    int r,int g,int b)
{
    psWeatherGameEvent *event;

    // There are no notify for 7 pars so we create a string first.
    csString note;
    note.Format("QueueNextEvent Delay: %d Type: %d Value: %d "
                "Duration: %d Fade: %d Sector: %s ...",
                delayticks,eventtype,eventvalue,duration,
                fade,sector);
    Notify1(LOG_WEATHER,note.GetDataSafe());
    
    event = new psWeatherGameEvent(this,
                                   delayticks,
                                   eventtype,
                                   eventvalue,
                                   duration,
                                   fade,
                                   sector,
                                   si,
                                   clientnum,
                                   r,g,b);

    events.Push(event);
    psserver->GetEventManager()->Push(event);
}

void WeatherManager::SendClientCurrentTime(int cnum)
{
    psWeatherMessage time(cnum,current_daynight);
    time.SendMessage();    
}

void WeatherManager::HandleWeatherEvent(psWeatherGameEvent *event)
{
    events.Delete(event); // Delete this from our "db"

    // See if we want to ignore this event
    for(size_t i = 0; i < ignored.Length();i++)
    {
        if( event == ignored[i] )
        {
            ignored.DeleteIndex(i);
            return;
        }
    }

    switch (event->type)
    {
    case psWeatherMessage::SNOW:
    case psWeatherMessage::RAIN:
        {
            event->si->current_rain_drops = event->value;

            Notify3(LOG_WEATHER,"New rain in sector '%s': %d",event->si->name.GetData(),event->value);

            psWeatherMessage::NetWeatherInfo info;

            info.has_downfall     = true;
            info.downfall_is_snow = (event->type == psWeatherMessage::SNOW);
            info.has_fog          = true;
            info.has_lightning    = false;

            info.downfall_drops = event->value;

            // We set the fog depending from the drops!
            // Any rain will override the current fog settings
            if (info.downfall_drops < 2001)
            {
                event->si->current_rain_fog_density = 0; //Too few drops for having any fog!
            }
            else if (info.downfall_drops > 1000 && info.downfall_drops < 8000)
            {
                // Calculate fog to be linear in range 0 to 200
                event->si->current_rain_fog_density = 
                    (int)(200.0f*(info.downfall_drops-1000.0f)/8000.0f);
            }
            else 
            {
                event->si->current_rain_fog_density = 200;
            }

            info.fog_density =
                MAX( event->si->fog_density, event->si->current_rain_fog_density );

            info.r = info.g = info.b = 255;

            info.sector = event->sector;

            if (event->fade)
            {
                info.downfall_fade = event->fade;
                info.fog_fade = event->fade;
            }
            else
            {
                if (event->value)
                {
                    info.downfall_fade = event->si->GetRandomRainFadeIn();
                    info.fog_fade = info.downfall_fade;
                }
                else
                {
                    info.downfall_fade = event->si->GetRandomRainFadeOut();
                    info.fog_fade = info.downfall_fade;
                }
            }
            
            Notify4(LOG_WEATHER,"Drops: %d Density: %d Sector: %s\n",
                    info.downfall_drops,info.fog_density,info.sector.GetDataSafe());

            psWeatherMessage rain(0,info);
            if (rain.valid)
                psserver->GetEventManager()->Broadcast(rain.msg,NetBase::BC_EVERYONE);
            else
            {
                Bug1("Could not create valid psWeatherMessage (rain) for broadcast.\n");
            }

            // Make sure we don't have any other events in this sector that will disturb
            // Simple case is when event types are equal. In addition we have to test
            // for the mutal exclusive case where we are changing from snow to rain or
            // rain to snow.
            for(size_t i = 0; i < events.Length();i++)
            {
                psWeatherGameEvent* evt = events[i];
                if(evt->sector == event->sector && 
                   (evt->type == event->type || ((evt->type==psWeatherMessage::RAIN||
                                                  evt->type==psWeatherMessage::SNOW)&&
                                                 (event->type==psWeatherMessage::RAIN||
                                                  event->type==psWeatherMessage::SNOW))))
                {
                    ignored.Push(evt); // Ignore when the eventmanager handles the event
                    events.DeleteIndex(i);
                    i--;

                    Notify4(LOG_WEATHER,"Removed disturbing event for sector '%s' (%d,%d)",
                            evt->sector.GetData(),evt->value,evt->duration);

                }
            }

            if (event->value)  // if this has drops, then queue event to turn off rain
            {
                if(event->type != psWeatherMessage::SNOW)
                    event->si->is_raining = true;
                else
                    event->si->is_snowing = true;

                if (event->si->lightning_min_gap && event->value > 2000 && 
                    event->si->is_raining && event->si->rain_enabled)
                {
                    // Queue lightning during rain storm here first
                    QueueNextEvent(event->si->GetRandomLightningGap(),
                                   psWeatherMessage::LIGHTNING,
                                   0,
                                   0,
                                   0,
                                   event->si->name,
                                   event->si,
                                   event->clientnum);
                }

                // Queue event to stop rain/snow
                int duration;
                if (event->duration)
                {
                    duration = event->duration;
                }
                else
                {
                    duration = event->si->GetRandomRainDuration();
                }
                
                QueueNextEvent(duration,
                               event->type,
                               0,
                               0,
                               event->fade,
                               event->si->name,
                               event->si);

            }
            // if this is event to turn off rain/rain
            else
            {
                if(event->type== psWeatherMessage::SNOW)
                    event->si->is_snowing = false;
                else
                    event->si->is_raining = false;

                // Queue event to turn on again later if enabled
                StartWeather(event->si);
            }

            break;
        }
    case psWeatherMessage::FOG:
        {
            // Update sector weather info
            event->si->fog_density = event->value;
            event->si->r = event->cr;
            event->si->g = event->cg;
            event->si->b = event->cb;

            // Update the clients
            psWeatherMessage::NetWeatherInfo info;
            info.has_downfall     = false;
            info.downfall_is_snow = false;
            info.has_fog          = true;
            info.has_lightning    = false;
            info.sector           = event->si->name;
            info.fog_density      = 
                MAX( event->si->fog_density, event->si->current_rain_fog_density );
            info.fog_fade         = event->fade;
            info.r                = event->cr;
            info.g                = event->cg;
            info.b                = event->cb;
            info.downfall_drops   = 0;
            info.downfall_fade    = 0;


            psWeatherMessage fog(0,info);
            if (fog.valid)
                psserver->GetEventManager()->Broadcast(fog.msg,NetBase::BC_EVERYONE);
            else
            {
                Bug1("Could not create valid psWeatherMessage (fog) for broadcast.\n");
            }

            break;
        }
    case psWeatherMessage::LIGHTNING:
        {
            if (event->si->is_raining)
            {
                Notify2(LOG_WEATHER,"Lightning in sector '%s'",event->sector.GetData());

                psWeatherMessage::NetWeatherInfo info;
                info.has_downfall  = false;
                info.has_fog       = false;
                info.has_lightning = true;
                info.sector = event->sector;
                info.fog_fade = info.downfall_fade = 0;
                info.r = info.g = info.b = 0;
                info.downfall_drops = info.fog_density = 0;
               
                psWeatherMessage lightning(0,info, event->clientnum);

                if (lightning.valid)
                {
                    psserver->GetEventManager()->Broadcast(lightning.msg);
                }
                else
                {
                    Bug1("Could not create valid psWeatherMessage (lightning) for broadcast.\n");
                }

                if (event->si->rain_enabled &&
                    event->si->lightning_max_gap != 0)
                {
                    QueueNextEvent(event->si->GetRandomLightningGap(),
                                   psWeatherMessage::LIGHTNING,
                                   0,
                                   0,
                                   0,
                                   event->si->name,
                                   event->si, 
                                   event->clientnum);
                }
            }
            break;
        }
    case psWeatherMessage::DAYNIGHT:
        {
            psWeatherMessage time(0,event->value);
            if (time.valid)
            {
                psserver->GetEventManager()->Broadcast(time.msg);
            }
            else
            {
                Bug1("Could not create valid psWeatherMessage (daynight) for broadcast.\n");
            }


            current_daynight = event->value;

            QueueNextEvent(GAME_HOUR,
                           psWeatherMessage::DAYNIGHT,
                           ++(event->value) % 24,  // hourly cycle
                           0,
                           0,
                           NULL,
                           NULL);
            break;
        }
    default:
        {
            break;
        }
    }
}





/*---------------------------------------------------------------------*/

psWeatherGameEvent::psWeatherGameEvent(WeatherManager *mgr,
                                       int delayticks,
                                       int eventtype,
                                       int eventvalue,
                                       int length,
                                       int fadevalue,
                                       const char *where,
                                       psSectorInfo *sinfo,
                                       uint client,
                                       int r,int g,int b)
  : psGameEvent(0,delayticks,"psWeatherGameEvent")
{
    weathermanager = mgr;
    type           = eventtype;
    value          = eventvalue;
    sector         = where;
    si             = sinfo;
    duration       = length;
    fade           = fadevalue;
    clientnum      = client;

    cr = r;
    cg = g;
    cb = b;
}


void psWeatherGameEvent::Trigger()
{
    weathermanager->HandleWeatherEvent(this);
}

const char *psWeatherGameEvent::GetType()
{
    switch (type)
    {
        case psWeatherMessage::SNOW:
            return "Snow";

        case psWeatherMessage::LIGHTNING:
            return "Lightning";

        case psWeatherMessage::FOG:
            return "Fog";

        case psWeatherMessage::RAIN:
            return "Rain";

        case psWeatherMessage::DAYNIGHT:  
            return "Day/Night";

        default:        
            return "Unknown";
    }
}

