#include "fcfs.hpp"
#include <iostream>

#include "../pempek_assert.hpp"

FCFS::FCFS(Workload *workload,
    SchedulingDecision *decision, Queue *queue, ResourceSelector *selector,
    double rjms_delay, rapidjson::Document *variant_options) :
    ISchedulingAlgorithm(workload, decision, queue, selector, rjms_delay,
        variant_options)
{}

FCFS::~FCFS()
{}

void FCFS::on_simulation_start(double date,
    const rapidjson::Value &batsim_config)
{
    (void) date;
    (void) batsim_config;

    _available_machines.insert(IntervalSet::ClosedInterval(0, _nb_machines - 1));
    _nb_available_machines = _nb_machines;
    PPK_ASSERT_ERROR(_available_machines.size() == (unsigned int) _nb_machines);
}

void FCFS::on_simulation_end(double date)
{
    (void) date;
}

void FCFS::make_decisions(double date,
    SortableJobOrder::UpdateInformation *update_info,
    SortableJobOrder::CompareInformation *compare_info)
{
    (void) update_info;
    (void) compare_info;

    // This algorithm is a version of FCFS without backfilling.
    // It is meant to be fast in the usual case, not to handle corner cases.
    // It is not meant to be easily readable or hackable ;).

    // This fast FCFS variant in a few words:
    // - only handles the FCFS queue order
    // - only handles finite jobs (no switchoff)
    // - only handles time as floating-point (-> precision errors).

    bool job_ended = false;

    // Handle newly finished jobs
    for (const std::string & ended_job_id : _jobs_ended_recently)
    {
        job_ended = true;

        Job * finished_job = (*_workload)[ended_job_id];

        // Update data structures
        _available_machines.insert(_current_allocations[ended_job_id]);
        _nb_available_machines += finished_job->nb_requested_resources;
        _current_allocations.erase(ended_job_id);
    }

    // If jobs have finished, execute jobs as long as they fit
    if (job_ended)
    {
        for (auto job_it = _pending_jobs.begin();
             job_it != _pending_jobs.end(); )
        {
            Job * pending_job = *job_it;
            IntervalSet machines;

            if (_selector->fit(pending_job, _available_machines, machines))
           {
                _decision->add_execute_job(pending_job->id,
                    machines, date);

                // Update data structures
                _available_machines -= machines;
                _nb_available_machines -= pending_job->nb_requested_resources;
                _current_allocations[pending_job->id] = machines;
                job_it = _pending_jobs.erase(job_it);

            }
            else
            {
                // The job becomes priority!
                // As there is no backfilling, we can simply leave this loop.
                break;
            }
        }
    }

    // Handle newly released jobs
    for (const std::string & new_job_id : _jobs_released_recently)
    {
        Job * new_job = (*_workload)[new_job_id];

        // Is this job valid?
        if (new_job->nb_requested_resources > _nb_machines)
        {
            // Invalid!
            _decision->add_reject_job(new_job_id, date);
            continue;
        }

        // Is there a waiting job?
        if (!_pending_jobs.empty())
        {
            // Yes. The new job is queued up.
            _pending_jobs.push_back(new_job);
        }
        else
        {
            // No, the queue is empty.
            // Can the new job be executed now?
            if (new_job->nb_requested_resources <= _nb_available_machines)
            {
                // Yes, the job can be executed right away!
                IntervalSet machines = _available_machines.left(
                    new_job->nb_requested_resources);
                _decision->add_execute_job(new_job_id, machines, date);

                // Update data structures
                _available_machines -= machines;
                _nb_available_machines -= new_job->nb_requested_resources;
                _current_allocations[new_job_id] = machines;
            }
            else
            {
                // No. The job is queued up.
                _pending_jobs.push_back(new_job);
            }
        }
    }
}