/* bzflag
 * Copyright (c) 1993-2025 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include "bzfsAPI.h"
#include "plugin_utils.h"
#include "CronJob.h"
#include <iostream>


// debug util func
static std::string vector_dump(std::vector<int> &iv)
{
    std::string tmp = "<";
    for (std::vector<int>::iterator itr = iv.begin(); itr != iv.end(); ++itr)
        tmp += format(" %d", *itr);
    tmp += " >";
    return tmp;
}

CronJob::CronJob()
{
    // do nothing
}

CronJob::CronJob(std::string job)
{
    setJob(job);
}

CronJob::~CronJob()
{
    // do nothing
}

void CronJob::setJob(std::string job)
{
    if (job.size() == 0)
        return;
    if (no_whitespace(job).size() == 0)
        return;

    // parse the string we're given into five vectors of n ints and a command
    // note: this is rather expensive
    inputJob = job;

    // first bust it up into tokens based on whitespace.
    // the first five are the timing values and the 'sixth through nth' is the command.
    std::vector<std::string> toks = tokenize(job, " \t", 6, false);

    // hokey dokey.  now we have six strings and we need five arrays of ints and one string out of them.
    minutes = parseTimeList(toks[0], 0, 59);
    hours = parseTimeList(toks[1], 0, 23);
    days = parseTimeList(toks[2], 1, 31);
    months = parseTimeList(toks[3], 1, 12);
    weekdays = parseTimeList(toks[4], 0, 7);
    command = toks[5];

    // sunday is both 7 and 0, make sure we have both or neither
    if (isInVector(weekdays, 0) && !isInVector(weekdays, 7))
        weekdays.push_back(7);
    else if (isInVector(weekdays, 7) && !isInVector(weekdays, 0))
        weekdays.push_back(0);

    // dump the list if we're debuggering
    if (bz_getDebugLevel() >= 4)
    {
        std::cout << "bzfscron: read job: " << inputJob << std::endl;
        std::cout << "bzfscron: job minutes: " << vector_dump(minutes) << std::endl;
        std::cout << "bzfscron: job hours: " << vector_dump(hours) << std::endl;
        std::cout << "bzfscron: job days: " << vector_dump(days) << std::endl;
        std::cout << "bzfscron: job months: " << vector_dump(months) << std::endl;
        std::cout << "bzfscron: job weekdays: " << vector_dump(weekdays) << std::endl;
        std::cout << "bzfscron: job command: " << command << std::endl;
    }
}

std::vector<int> CronJob::parseTimeList(const std::string in, const int min, const int max)
{
    std::vector<int> vi;
    std::string list = in;

    // First things first.  Find out if there's a periodicity and trim it off.
    size_t pos = in.find("/");
    int period = 1;
    if (pos != std::string::npos)
    {
        period = atoi(in.substr(pos + 1).c_str());
        list = in.substr(0, pos);
    }

    // Now tokenize on ","
    std::vector<std::string> stage1 = tokenize(list, ",", 0, false);
    // No tokens?  That's cool too.
    if (stage1.empty())
        stage1.push_back(list);

    // And for each token, blow up any "-" ranges and "*" ranges.
    for (std::vector<std::string>::iterator itr = stage1.begin(); itr != stage1.end(); ++itr)
    {
        if ((*itr).find("*") != std::string::npos)
        {
            bz_debugMessage(4, "bzfscron: exploding * range");
            for (int i = min; i <= max; ++i)
                vi.push_back(i);
        }
        else if ((pos = (int)(*itr).find("-")) != std::string::npos)
        {
            bz_debugMessage(4, "bzfscron: exploding x-y range");
            int rmin = 0, rmax = 0;
            rmin = atoi((*itr).substr(0, pos).c_str());
            rmax = atoi((*itr).substr(pos + 1).c_str());
            if (rmin < min)
                rmin = min;
            if (rmax > max)
                rmax = max;
            for (int i = rmin; i <= rmax; ++i)
                vi.push_back(i);
        }
        else
        {
            bz_debugMessage(4, "bzfscron: using single int");
            vi.push_back(atoi((*itr).c_str()));
        }
    }

    // Remember that periodicity we got rid of earlier?  Now we need it.
    // Eliminate any elements which disagree with the periodicity.
    if (period > 1)
    {
        std::vector<int> vp;
        for (std::vector<int>::iterator itr2 = vi.begin(); itr2 != vi.end(); ++itr2)
        {
            if (((*itr2) == 0) || ((*itr2) % period == 0))
                vp.push_back(*itr2);
        }
        return vp;
    }
    else
        return vi;
}

bool CronJob::matches(int n, int h, int d, int m, int w) const
{
    // if we are supposed to execute now, return true, otherwise return false
    return (isInVector(minutes, n) &&
            isInVector(hours, h) &&
            isInVector(days, d) &&
            isInVector(months, m) &&
            isInVector(weekdays, w));
}

bool CronJob::isInVector(const std::vector<int> &iv, const int x)
{
    for (std::vector<int>::const_iterator itr = iv.begin(); itr != iv.end(); ++itr)
    {
        if (*itr == x)
            return true;
    }
    return false;
}

// Local Variables: ***
// mode: C++ ***
// tab-width: 4 ***
// c-basic-offset: 4 ***
// indent-tabs-mode: nil ***
// End: ***
// ex: shiftwidth=4 tabstop=4
