/*
* eventmanager.cpp
*
* 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 <csutil/sysfunc.h>
#include <csutil/scopedmutexlock.h>

#include "gameevent.h"
#include "sleep.h"
#include "psconst.h"
#include "util/log.h"
#include "util/consoleout.h"

#include "eventmanager.h"

// Number of recent events to use when calculating moving average
#define EVENT_AVERAGETIME_COUNT 50

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

EventManager::EventManager()
{
    mutex = csMutex::Create(CS_MUTEX_RECURSIVE);
    runmutex = csMutex::Create();

    // Seting up the static pointer in psGameEvent. Used so
    // that a event can be fired without needing to look up 
    // the event manager first.
    psGameEvent::eventmanager = this;
}

EventManager::~EventManager()
{
    // Clean up the event queue
    while (eventqueue.Length())
    {
        delete eventqueue.DeleteMin();
    }
}

void EventManager::Push(psGameEvent *event)
{
    csScopedMutexLock lock(mutex);

    // This inserts the event into the priority queue, sorted by timestamp
    eventqueue.Insert(event);
}

void EventManager::ProcessEventQueue()
{
    csTicks now = csGetTicks();

    static int lastticks;
    static int lastid;

    while (true)
    {
        int count = 0;

        psGameEvent *event;

        {
            csScopedMutexLock lock(mutex);

            event = eventqueue.FindMin();

            if (!event || event->triggerticks > now) // empty event queue
                break;
            eventqueue.DeleteMin();
        }

        char lastType[70];
        strcpy(lastType, event->GetType());
        //        CPrintf(CON_SPAM, "Event #%d @ %u ticks.\n",event->id,event->triggerticks);
        csTicks start = csGetTicks();

        if (event->CheckTrigger())
            event->Trigger();

        csTicks timeTaken = csGetTicks() - start;

        if(timeTaken > 500)
        {
            csString status;
            status.Format("Event type %s has taken %u time to process\n", event->GetType(), timeTaken);
            CPrintf(CON_WARNING, "%s\n", status.GetData());
            if(LogCSV::GetSingletonPtr())
                LogCSV::GetSingleton().Write(CSV_STATUS, status);
        }

        if (lastid == event->id)
        {
            CPrintf(CON_DEBUG, "Event %d is being processed more than once at time %d!\n",event->id,event->triggerticks);
        }
        lastid    = event->id;
        lastticks = event->triggerticks;

        delete event;

        count++;
        if (count == 100)
        {
            CPrintf(CON_DEBUG, "Went through event loop 100 times in one timeslice.  This means we either have duplicate events, "
                "bugs in event generation or bugs in deleting events from the event tree.\n");
        }
    }
}

// Process events at least every 250 tick
#define PROCESS_EVENT   250


// This is the MAIN GAME thread. Every message and event are handled from
// this thread. This eliminate need for synchronization of access to
// game data.
void EventManager::Run ()
{
    csString status;
    csTicks eventtimes[EVENT_AVERAGETIME_COUNT];
    short index = 0;
    csTicks eventtimesTotal = 0;

    // Have we filled in all the entries in the array yet?
    bool filled = false;

    csRef<MsgEntry> msg = 0;

    csTicks nextEvent = csGetTicks() + PROCESS_EVENT;

    stop_network = false;

    while( !stop_network)
    {
        csTicks now = csGetTicks();
        int timeout = nextEvent - now;

        if (timeout > 0)
            msg = queue->GetWait(timeout);

        if (msg)
        {
            csTicks start = csGetTicks();

            //            CPrintf(CON_SPAM, "Handle msg: %d , %08X\n",now,msg->data->type);
            Publish(msg);

            csTicks timeTaken = csGetTicks() - start;

            // Ignore messages that take no time to process.
            if(timeTaken)
            {
                if(!filled)
                    eventtimesTotal += timeTaken;
                else
                    eventtimesTotal = timeTaken + eventtimesTotal - eventtimes[index];

                eventtimes[index] = timeTaken;

                // Done this way to prevent a division operator
                if(filled && timeTaken > 500 && (timeTaken * EVENT_AVERAGETIME_COUNT > 2 * eventtimesTotal || eventtimesTotal > EVENT_AVERAGETIME_COUNT * 1000))
                {
                    status.Format("Message type %u has taken %u time to process, average time of events is %u", msg->GetType(), timeTaken, eventtimesTotal / EVENT_AVERAGETIME_COUNT);
                    CPrintf(CON_WARNING, "%s\n", status.GetData());
                    if(LogCSV::GetSingletonPtr())
                        LogCSV::GetSingleton().Write(CSV_STATUS, status);
                }

                index++;
                // Rollover
                if(index == EVENT_AVERAGETIME_COUNT)
                {
                    index = 0;
                    filled = true;
                }
            }
            // don't forget to release the packet
            msg = NULL;
        }
        else if (now >= nextEvent)
        {
            //            CPrintf(CON_SPAM, "Handle event: %d\n",now);
            nextEvent = csGetTicks() + PROCESS_EVENT;
            ProcessEventQueue();
        }
    }
    csScopedMutexLock lock(runmutex);
    if(filled)
    {
        status.Format("Event manager shutdown, average time of events is %u", eventtimesTotal / EVENT_AVERAGETIME_COUNT);
        CPrintf(CON_CMDOUTPUT, "%s\n", status.GetData());
        if(LogCSV::GetSingletonPtr())
            LogCSV::GetSingleton().Write(CSV_STATUS, status);
    }
    printf("Event thread stopped!\n");
}




