/** \file
 *
 *  Contains the CommandExec class implementation.
 *
 *  Copyright (c) 2007,2008,2009 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 *
 */

#include "Scheduler.h"

#include "ScheduledItem.h"
#include "Supervisor.h"


Scheduler::Scheduler()
    : ScheduledItems_( true ),
      ScheduleIndex_( 0 ),
      SchedulePauseSeverity_( Syslog::NONE ),
      SchedulePauseTime_( Timestamp::NOT_SET_TIME )
{}

void Scheduler::Schedule( ScheduledItem* newItem, Logger& logger )
{
    MutexLocker locker( ScheduledItemsMutex_ );

    newItem->setIndex( ++ScheduleIndex_ );
    int idCount = newItem->getIdCount();
    int idTot = ( idCount * ( idCount + 1 ) ) / 2;
    int idIndex = newItem->getIdIndex();
    Str& id = newItem->getId();

    if( !newItem->isReady() )
    {
        if( idCount <= 0 )
        {
            logger.syslog( "Failed to schedule command with id=" + id + " and idCount=" + Str( idCount ) + ". idCount must be > 0.", Syslog::FAULT );
            return;
        }
        if( idIndex <= 0 || idIndex > idCount )
        {
            logger.syslog( "Failed to schedule command with id=" + id + ". idIndex must be > 0 and <= idCount.", Syslog::FAULT );
            return;
        }

        // Now we must ensure that scheduled items with a shared ID are executed in the correct order:
        // create a temporary array to hold scheduled items with shared ID so we can sort them
        ScheduledItem** sorted = new ScheduledItem*[idCount];
        sorted[idIndex - 1] = newItem;

        int idSum = idIndex;
        for( unsigned int i = 0; i < ScheduledItems_.size(); ++i )
        {
            ScheduledItem* item = ScheduledItems_.get( i );

            if( item->getId() == id )
            {
                if( item->getExecTime() != newItem->getExecTime() )
                {
                    logger.syslog( "Failed to schedule command with id=" + id + ". All time-stamps must match.", Syslog::FAULT );
                    delete[] sorted;
                    return;
                }
                if( item->getIdCount() != idCount )
                {
                    logger.syslog( "Failed to schedule command with id=" + id + ". All idCounts must match.", Syslog::FAULT );
                    delete[] sorted;
                    return;
                }
                if( item->getIdIndex() == idIndex )
                {
                    logger.syslog( "Failed to schedule command with id=" + id + ". Duplicate idIndex.", Syslog::FAULT );
                    delete[] sorted;
                    return;
                }
                // populate the scheduled item in it's correct postion
                sorted[item->getIdIndex() - 1] = item;
                idSum += item->getIdIndex();
            }
        }
        if( idSum == idTot )
        {
            // we have all of the items belonging to the ID
            newItem->markReady();
            // now re-insert the sorted parts in the schedule queue
            unsigned int sortedIndex = 0;
            for( unsigned int i = 0; i < ScheduledItems_.size(); ++i )
            {
                ScheduledItem* anItem = ScheduledItems_.get( i );
                if( anItem->getId() == id )
                {
                    anItem->markReady();
                    ScheduledItems_.set( i, sorted[sortedIndex++] );
                }
            }
            newItem = sorted[sortedIndex];
        }
        delete[] sorted;
    }
    // now insert the last part
    ScheduledItems_.push( newItem );
    // notify operators that their command is scheduled
    logger.syslog( "Scheduled #" + newItem->report(), Syslog::IMPORTANT );
    // and let them know if the scheduler is paused
    if( newItem->getType() != ScheduledItem::ASAP )
        ShowPaused( logger );
}

bool Scheduler::Remove( int index, Logger& logger, bool quiet )
{
    MutexLocker locker( ScheduledItemsMutex_ );

    bool removed = false;
    for( unsigned int i = 0; !removed && i < ScheduledItems_.size(); ++i )
    {
        ScheduledItem* scheduledItem = ScheduledItems_[i];
        if( NULL != scheduledItem )
        {
            if( scheduledItem->getIndex() == index )
            {
                if( !quiet )
                {
                    logger.syslog( "Removed scheduled #" + Str( index, 10 ) + ": \"" + scheduledItem->getCmd() + "\" " + scheduledItem->typeToStr(), Syslog::IMPORTANT );
                }
                delete ScheduledItems_.pop( i );
                removed = true;
            }
        }
    }

    if( !removed )
    {
        logger.syslog( "No items at scheduled index #" + Str( index, 10 ), Syslog::FAULT );
    }
    return removed;
}

void Scheduler::Clear( const Str& id, Logger& logger )
{
    MutexLocker locker( ScheduledItemsMutex_ );
    int size = ( int )ScheduledItems_.size();
    int removed = 0;

    if( id == Str::EMPTY_STR )
    {
        // remove all scheduled items
        ScheduledItems_.clear();
        removed = size;
    }
    else
    {
        // remove scheduled items matching specified ID
        for( int i = ( size - 1 ); i >= 0; --i )
        {
            ScheduledItem* item = ScheduledItems_.get( i );
            if( NULL != item && id == item->getId() )
            {
                delete ScheduledItems_.pop( i );
                ++removed;
            }
        }
    }

    logger.syslog( "Cleared " + Str( removed, 0 ) + " scheduled commands.", Syslog::IMPORTANT );
}

void Scheduler::DoScheduled( Logger& logger )
{
    MutexLocker locker( ScheduledItemsMutex_ );

    if( ScheduledItems_.size() <= 0 )
        // No scheduled commands at this time.
        return;

    unsigned int i;
    // Deal with ASAP scheduled commands
    for( i = 0; i < ScheduledItems_.size(); ++i )
    {
        ScheduledItem* item = ScheduledItems_.get( i );

        if( item->isReady() && item->getType() == ScheduledItem::ASAP )
        {
            CommandExec::DoCommand( item->getCmd().cStr() );
            item->markExecuted();

            if( item->isExecuted() )
            {
                delete ScheduledItems_.pop( i );
                return;
            }
        }
    }

    // No ASAP commands. Deal with TIMED scheduled commands
    for( i = 0; i < ScheduledItems_.size(); ++i )
    {
        ScheduledItem* item = ScheduledItems_.get( i );

        if( item->isReady() && item->getType() == ScheduledItem::TIMED )
        {
            if( item->getExecTime() <= Timestamp::Now() )
            {
                if( !schedulePaused() )
                {
                    CommandExec::DoCommand( item->getCmd().cStr() );
                }
                else
                {
                    logger.syslog( "Schedule is paused. Ignoring time-scheduled command: " + item->getCmd(), Syslog::FAULT );
                }
                item->markExecuted();
            }

            if( item->isExecuted() )
            {
                delete ScheduledItems_.pop( i );
                return;
            }
        }
    }

    // No ASAP or TIMED commands. Deal with NEXT scheduled commands (after mission)
    for( i = 0; i < ScheduledItems_.size(); ++i )
    {
        ScheduledItem* item = ScheduledItems_.get( i );

        if( item->isReady() && item->getType() == ScheduledItem::NEXT )
        {
            if( !Supervisor::IsMissionRunning() && !schedulePaused() )
            {
                CommandExec::DoCommand( item->getCmd().cStr() );
                item->markExecuted();
            }

            if( item->isExecuted() )
            {
                delete ScheduledItems_.pop( i );
                return;
            }
        }
    }
}

void Scheduler::List( Logger& logger )
{
    for( unsigned int i = 0; i < ScheduledItems_.size(); ++i )
    {
        ScheduledItem* item = ScheduledItems_[i];
        if( NULL != item )
        {
            logger.syslog( "Scheduled #" + item->report(), Syslog::IMPORTANT );
        }
    }

    ShowPaused( logger );
}

void Scheduler::Pause( Syslog::Severity schedulePauseSeverity, Timestamp pauseTime, Logger& logger )
{
    MutexLocker locker( SchedulePauseMutex_ );

    SchedulePauseTime_ = pauseTime;
    if( SchedulePauseSeverity_ == schedulePauseSeverity )
    {
        return;
    }
    SchedulePauseSeverity_ = schedulePauseSeverity;
    int paused = ScheduledItems_.size();
    if( paused == 0 )
    {
        logger.syslog( "Scheduling is paused", schedulePauseSeverity );
    }
    else
    {
        logger.syslog( "Scheduling is paused for " + Str( paused, 10 ) + " commands", schedulePauseSeverity );
    }
}

void Scheduler::Resume( Logger& logger )
{
    MutexLocker locker( SchedulePauseMutex_ );

    if( SchedulePauseSeverity_ == Syslog::NONE )
    {
        return;
    }
    SchedulePauseTime_ = Timestamp::NOT_SET_TIME;
    SchedulePauseSeverity_ = Syslog::NONE;
    logger.syslog( "Scheduling is resumed", Syslog::IMPORTANT );
}

void Scheduler::ShowPaused( Logger& logger )
{
    if( SchedulePauseSeverity_ > Syslog::IMPORTANT )
    {
        logger.syslog( "Scheduling was paused by an error at " + SchedulePauseTime_.toString( 0 ), Syslog::FAULT );
    }
    else if( SchedulePauseSeverity_ > Syslog::NONE )
    {
        logger.syslog( "Scheduling was paused by a command at " + SchedulePauseTime_.toString( 0 ), Syslog::FAULT );
    }
}

bool Scheduler::schedulePaused()
{
    return SchedulePauseSeverity_ != Syslog::NONE;
}
