// ################################
// ##    Casova osa triggeru     ##
// ##     na udalosti ve hre     ##
// ################################

#include "time_axis.h"
#include "engine.h"
#include "fortress_skelet.h"

#include <list>

#ifdef _DEBUG
#include <sstream>

#include "log.h"
#endif

#ifdef _DEBUG
#ifndef DEBUG_NEW
#define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
#define new DEBUG_NEW
#endif
#endif

// --------------------------------

void timeAxisThread :: stop()
{
    ending_ = true;
    semaphore_.up();
}

// --------------------------------

void timeAxisThread :: start()
{
    ending_ = false;
}

// --------------------------------

timeAxisThread :: timeAxisThread() : semaphore_(0, MAX_TIME_AXIS_SEMAPHORE_VALUE)
{
    ending_ = false;
    timeAxis_ = NULL;
}

// --------------------------------

void timeAxisThread :: pushEvent(dv_time:: datetime gametime, triggerEventClass* eventToInsert, 
                                 unsigned long long& lastTriggerID)
{
    mapLock_.lock();

    /* nejprve otestujeme, zda nekoncime, pokud koncime, nedelame nic, jen deletneme event */
    if (!ending_)
    {
        /* zkontrolujeme, zda se nenachazi blizsi event, nebo vzdalenejsi */
        if (triggersToDateTime.find(lastTriggerID) != triggersToDateTime.end())
        {
            /* nalezli jsme trigger stejne tridy, zjistime, zda je bliz nebo dal nez soucasny */
            dv_time :: datetime lastTriggerDateTime = triggersToDateTime[lastTriggerID];

            if (lastTriggerDateTime < gametime)
            {
                /* posledni timer je blize, nez soucasny */
#if defined(_DEBUG) && defined(DV_SERVER_VERBOSE) && defined(DV_SERVER_VERBOSE_EVENTS)
                std :: stringstream ss;
                ss << "Event " << eventToInsert->getEventID() << " " << eventToInsert->getEventName() << ": Releasing late event at " 
                    << gt2wt(gametime).toString() << ", last event id is " << lastTriggerID << " at " << gt2wt(lastTriggerDateTime).toString() << " !";
                log(ss.str());
#endif
                delete eventToInsert;
            }
            else
            {
                /* tento timer je blize, nez soucasny, soucasny je nutne smazat */
                triggersToDateTime.erase(lastTriggerID);

                /* smazeme soucasny trigger */
                dateTimePTriggerEventClassMap :: iterator begin = eventTriggers_.lower_bound(lastTriggerDateTime);
                dateTimePTriggerEventClassMap :: iterator end   = eventTriggers_.upper_bound(lastTriggerDateTime);
                for (dateTimePTriggerEventClassMap :: iterator iter = begin; iter != end; iter++)
                {
                    /* porovname, jestli se jedna o nas trigger */
                    if (lastTriggerID == (iter->second)->getEventID())
                    {
                        triggerEventClass* ev = iter->second;
                        eventTriggers_.erase(iter);
                        delete ev;
                        break;
                    }                
                }

#if defined(_DEBUG) && defined(DV_SERVER_VERBOSE) && defined(DV_SERVER_VERBOSE_EVENTS)
                std :: stringstream ss;
                ss << "Event " << eventToInsert->getEventID() << " " << eventToInsert->getEventName() << ": Adding new up-to-date event at " 
                    << gt2wt(gametime).toString() << ", last event id is " << lastTriggerID << " at " << gt2wt(lastTriggerDateTime).toString() << " !";
                log(ss.str());
#endif

                lastTriggerID = eventToInsert->getEventID();
                triggersToDateTime[eventToInsert->getEventID()] = gametime;
                eventTriggers_.insert(dateTimePTriggerEventClassPair(gametime, eventToInsert));
            }
        }
        else
        {
            /* zadny trigger nebyl nalezen, zaradime tento */
#if defined(_DEBUG) && defined(DV_SERVER_VERBOSE) && defined(DV_SERVER_VERBOSE_EVENTS)
            std :: stringstream ss;
            ss << "Event " << eventToInsert->getEventID() << " " << eventToInsert->getEventName() << ": Adding new orphan event at " 
                << gt2wt(gametime).toString() << ", last event id is " << lastTriggerID << " !";
            log(ss.str());
#endif
            lastTriggerID = eventToInsert->getEventID();
            triggersToDateTime[eventToInsert->getEventID()] = gametime;
            eventTriggers_.insert(dateTimePTriggerEventClassPair(gametime, eventToInsert));
        }
    }
    else
    {
        delete eventToInsert;
    }

    mapLock_.unlock();
    semaphore_.up();
}

// --------------------------------

void timeAxisThread :: operator ()(threads::thread *thd)
{
    while (!ending_)
    {
        std :: list<triggerEventClass *> triggersToActivate;

        mapLock_.lock();
        if (eventTriggers_.size() > 0) 
        {
            // zjistime aktualni herni cas
            dv_time:: datetime wallnow = dv_time:: now();
            dv_time:: datetime gamenow = wt2gt(wallnow);

            // aktivujeme vsechny aktualni triggery 
            while (eventTriggers_.size() > 0)
            {
                dateTimePTriggerEventClassMap :: iterator earliest = eventTriggers_.begin();

                if (earliest->first <= gamenow)
                {
                    triggerEventClass* evt = earliest->second;
                    eventTriggers_.erase(earliest);

                    /* odstranime trigger z mapy ID-cas */
                    ULLDateTimeMap :: iterator earliestIdIter = triggersToDateTime.find(evt->getEventID());

                    if (earliestIdIter != triggersToDateTime.end())
                    {
#if defined(_DEBUG) && defined(DV_SERVER_VERBOSE) && defined(DV_SERVER_VERBOSE_EVENTS)
        std :: stringstream ss;
        ss << "Event " << evt->getEventID() << ", " << evt->getEventName() << ": Erased from map!";
        log(ss.str());
#endif
                        triggersToDateTime.erase(earliestIdIter);
                    }
                    else
                    {
#if defined(_DEBUG) && defined(DV_SERVER_VERBOSE) && defined(DV_SERVER_VERBOSE_EVENTS)
        std :: stringstream ss;
        ss << "Event " << evt->getEventID() << ", " << evt->getEventName() << ": Not found in map!";
        log(ss.str());
#endif
                    }

                    triggersToActivate.push_back(evt);
                }
                else break;
            }

            // zjistime, jak dlouho je nutne cekat
            if (eventTriggers_.size() > 0)
            {
                dv_time:: datetime earliestTime = gt2wt(eventTriggers_.begin()->first);
                DWORD timeToWait = (DWORD)dv_time:: millisecondsBetween(wallnow, earliestTime);

                mapLock_.unlock();

                //TADY se insertnou data z triggers to activate do hlavniho threadu, zatim se tady jen smazou
                for (std :: list<triggerEventClass *> :: iterator iter = triggersToActivate.begin(); 
                    iter != triggersToActivate.end(); iter++)
                {
                    timeAxis_->engine_->addEvent(*iter);
                }

                semaphore_.down(timeToWait);
            }
            else
            {
                mapLock_.unlock();

                //tady se insertnou data z triggers to activate do hlavniho threadu
                for (std :: list<triggerEventClass *> :: iterator iter = triggersToActivate.begin(); 
                    iter != triggersToActivate.end(); iter++)
                {
                    timeAxis_->engine_->addEvent(*iter);
                }

                semaphore_.down();
            }
        }
        else
        {
            mapLock_.unlock();
            semaphore_.down();
        }
    }

    mapLock_.lock();

    for (dateTimePTriggerEventClassMap :: iterator iter = eventTriggers_.begin(); iter != eventTriggers_.end(); iter++)
    {
        delete iter->second;
    }
    eventTriggers_.clear();
    triggersToDateTime.clear();

    mapLock_.unlock();
}

// ################################
// ################################

// --------------------------------

void timeAxis :: pushEventTrigger(dv_time::datetime triggerGameTime, triggerEventClass * triggerEvent, 
        unsigned long long& lastTriggerID)
{
    if (active_)
    {
        queue_.pushEvent(triggerGameTime, triggerEvent, lastTriggerID);
    }
    else
        delete triggerEvent;
}

// --------------------------------

void timeAxis :: start()
{
    active_ = true;
    queue_.start();
    timeAxisThd_ = new threads :: thread(&queue_, false);
}

// --------------------------------

timeAxis :: timeAxis()
{
    active_ = false;
    engine_ = NULL;
    queue_.setTimeAxis(this);
}

// --------------------------------

void timeAxis :: setEngine(engine* eng)
{
    engine_ = eng;
}

// --------------------------------

void timeAxis :: stop()
{
    active_ = false;
    queue_.stop();
    timeAxisThd_->join();
    delete timeAxisThd_;
    timeAxisThd_ = NULL;
}

// ================================
/*
void constructionDoneTriggerClass :: checkEvent()
{
#ifdef _DEBUG
        std :: stringstream ss;
        ss << "Fortress [" << fortressID_ << "]: Construction done event!";
        log(ss.str());
#endif

    errExceptionError("Not implemented!");
    // TODO Doimplementovat
}
*/
// ================================
/*
void outOfQuarkGluonPlasmaTriggerClass :: checkEvent()
{
#ifdef _DEBUG
        std :: stringstream ss;
        ss << "Fortress [" << fortressID_ << "]: Out of quark gluon plasma event!";
        log(ss.str());
#endif

    errExceptionError("Not implemented!");
    // TODO Doimplementovat
}
*/
// ================================
/*
void outOfUlreaTriggerClass :: checkEvent()
{
#ifdef _DEBUG
        std :: stringstream ss;
        ss << "Fortress [" << fortressID_ << "]: Out of ulrea event!";
        log(ss.str());
#endif

    errExceptionError("Not implemented!");
    // TODO Doimplementovat
}
*/
// ================================
/*
void outOfHeavyWaterTriggerClass :: checkEvent()
{
#ifdef _DEBUG
        std :: stringstream ss;
        ss << "Fortress [" << fortressID_ << "]: Out of heavy water event!";
        log(ss.str());
#endif

    errExceptionError("Not implemented!");
    // TODO Doimplementovat
}
*/
// ================================
/*
void outOfDeuteriumTriggerClass :: checkEvent()
{
#ifdef _DEBUG
        std :: stringstream ss;
        ss << "Fortress [" << fortressID_ << "]: Out of deuterium event!";
        log(ss.str());
#endif

    errExceptionError("Not implemented!");
    // TODO Doimplementovat
}
*/
// ================================
/*
void outOfZirconiumCrystalsTriggerClass :: checkEvent()
{
#ifdef _DEBUG
        std :: stringstream ss;
        ss << "Fortress [" << fortressID_ << "]: Out of zirconium crystals event!";
        log(ss.str());
#endif

    errExceptionError("Not implemented!");
    // TODO Doimplementovat
}
*/
// ================================
/*
void outOfEnergyTriggerClass :: checkEvent()
{
#ifdef _DEBUG
        std :: stringstream ss;
        ss << "Fortress [" << fortressID_ << "]: Out of energy event!";
        log(ss.str());
#endif

    errExceptionError("Not implemented!");
    // TODO Doimplementovat
}
*/
// ================================
/*
void outOfIlmeniteTriggerClass :: checkEvent()
{
#ifdef _DEBUG
        std :: stringstream ss;
        ss << "Fortress [" << fortressID_ << "]: Out of ilmenite event!";
        log(ss.str());
#endif

    errExceptionError("Not implemented!");
    // TODO Doimplementovat
}
*/
// ================================
/*
void outOfTitaniumTriggerClass :: checkEvent()
{
#ifdef _DEBUG
        std :: stringstream ss;
        ss << "Fortress [" << fortressID_ << "]: Out of titanium event!";
        log(ss.str());
#endif

    errExceptionError("Not implemented!");
    // TODO Doimplementovat
}
*/
// ================================
/*
void checkImpulsJetEngineShutDownTriggerClass :: checkEvent()
{
#ifdef _DEBUG
        std :: stringstream ss;
        ss << "Fortress [" << fortressID_ << "]: Check impulse jet engine shut down event!";
        log(ss.str());
#endif

    errExceptionError("Not implemented!");
    // TODO Doimplementovat
}
*/

// ================================

void scientificAndMedicalPhaseGlobalTriggerClass::checkEvent()
{
	if (0 == AVERAGE_SECONDS_BETWEEN_SCIENTIFIC_UPDATES)
	{
		errExceptionError("Scientific update zero delay.");
		return;
	}
	if (2 > actx->cacheRealmsList.getSize()) // vadi i size = 1, protoze hrac s ID 0 neni platny
	{
		return;
	}
	double realmsTotalCount              = actx->cacheRealmsList.getSize();
	double secondsBetweenGlobalUpdates   = GAME_SECONDS_BETWEEN_GLOBAL_EVENTS;
	double avgSecondsBetweenLocalUpdates = AVERAGE_SECONDS_BETWEEN_SCIENTIFIC_UPDATES;
	double localUpdatesInOneGlobalUpdate = realmsTotalCount * secondsBetweenGlobalUpdates / dHandleZero(avgSecondsBetweenLocalUpdates);
	double wholeFocusedRealms            = 0;
	double partFocusedRealms             = modf(localUpdatesInOneGlobalUpdate, &wholeFocusedRealms);
	unsigned long focusedRealms          = ulRound(wholeFocusedRealms);
	if (rollAWellBalancedDice(partFocusedRealms))
	{
		focusedRealms++;
	}
	for (unsigned long loop1 = 0; loop1 < focusedRealms; loop1++)
	{
		dtIdentifier chosenRealmID = (dtIdentifier)(rand() % ulHandleZero((unsigned long)(actx->cacheRealmsList.getSize() - 1))) + 1; // tohle deleni je zabezpeceno proti nule nahore
		realmClass * chosenRealm   = actx->cacheRealmsList.getItem<realmClass>(chosenRealmID);
        if (chosenRealm)
        {
		    chosenRealm->scientificUpdate();
		    chosenRealm->medicalUpdate();
        }
	}

	scientificAndMedicalPhaseGlobalTriggerClass::registerNextTriggerInstance();
}

// --------------------------------

void scientificAndMedicalPhaseGlobalTriggerClass::registerNextTriggerInstance()
{
	unsigned long long uselessID = 0;
	gctx.eng.registerEvent(wt2gt(dv_time::now()) + dv_time::encodeSeconds(GAME_SECONDS_BETWEEN_GLOBAL_EVENTS), new scientificAndMedicalPhaseGlobalTriggerClass(), uselessID);
}

// ================================

void shipMovementUpdate :: checkEvent()
{
	shipClass* sc = actx->cacheFortressesList.getItem<shipClass>(shipId_);
    if (sc != NULL)
    {
#if defined(_DEBUG) && defined(DV_SERVER_VERBOSE) && defined(DV_SERVER_VERBOSE_MOBILITY_EVENT)
        std :: stringstream ss;
        ss << "Ship [" << shipId_ << "]: Doing mobility event!";
        log(ss.str());
#endif
        sc->DoMobilityEvent();
    }
}

// ================================

void quarkGluonPlasmaUpdateTriggerClass::checkEvent()
{
	fortressClass* f = actx->cacheFortressesList.getItem<fortressClass>(fortressID_);
    if (f != NULL)
    {
#if defined(_DEBUG) && defined(DV_SERVER_VERBOSE) && defined(DV_SERVER_VERBOSE_QGPLASMA_EVENT)
        std :: stringstream ss;
        ss << "Fortress [" << fortressID_ << "]: Quark gluon plasma update event!";
        log(ss.str());
#endif
	    f->quarkGluonPlasmaUpdate(true);
    }
}

// ================================

void energyDistributionPhaseAndWeaponFireTriggerClass::checkEvent()
{
	fortressClass* f = actx->cacheFortressesList.getItem<fortressClass>(fortressID_);
    
    if (f != NULL)
    {
#if defined(_DEBUG) && defined(DV_SERVER_VERBOSE) && defined(DV_SERVER_VERBOSE_ENERGY_DISTRIBUTION_EVENT)
        std :: stringstream ss;
        ss << "Fortress [" << fortressID_ << "]: Energy distribution phase event!";
        log(ss.str());
#endif
        f->energyDistributionPhaseAndWeaponsFire(1);
    }
}

// ================================

void containeredResourcesUpdateTriggerClass::checkEvent()
{
	fortressClass* f = actx->cacheFortressesList.getItem<fortressClass>(fortressID_);
    if (f != NULL)
    {
#if defined(_DEBUG) && defined(DV_SERVER_VERBOSE) && defined(DV_SERVER_VERBOSE_CONTAINERED_RESOURCES_EVENT)
        std :: stringstream ss;
        ss << "Fortress [" << fortressID_ << "]: Containered resource update event!";
        log(ss.str());
#endif
        f->containeredResourcesUpdate();
    }
}

// ================================

void checkConstructionTriggerClass::checkEvent()
{
	fortressClass * constructingFortress = actx->cacheFortressesList.getItem<fortressClass>(fortressID_);

    if (constructingFortress != NULL)
    {   
#if defined(_DEBUG) && defined(DV_SERVER_VERBOSE) && defined(DV_SERVER_VERBOSE_CHECK_CONSTRUCTION_EVENT)
        std :: stringstream ss;
        ss << "Fortress [" << fortressID_ << "]: Check construction event!";
        log(ss.str());
#endif
	    if (FT_BASE == constructingFortress->getFortressType())
	    {
		    baseClass * constructingBase = (baseClass *)constructingFortress;
		    if (constructingBase->isHangarMounted(hangarID_))
		    {
				actx->cacheItemsList.getItem<hangarClass>(hangarID_)->checkConstruction(constructingFortress);
		    }
	    }
    }
}

// ================================

unsigned long long eventIDGenerator = 0;

triggerEventClass :: triggerEventClass()
{
    eventID = ++eventIDGenerator;
}

// --------------------------------

bool triggerEventClass :: operator==(const triggerEventClass& tec) const
{
    return getEventID() == tec.getEventID();
}

// --------------------------------

bool triggerEventClass :: operator!=(const triggerEventClass& tec) const
{
    return !(*this == tec);
}

// --------------------------------

// ################################
// ################################