// os-tools work generator written by Christian Beer
// http://blog.os-tools.net
// Copyright (C) 2008 Christian Beer
//
// This is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation;
// either version 2.1 of the License, or (at your option) any later version.
//
// This software 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 Lesser General Public License for more details.
//
// To view the GNU Lesser General Public License visit
// http://www.gnu.org/copyleft/lesser.html
// or write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

// ostools_work_generator.C: a BOINC work generator.
// This work generator has the following properties:
//
// - Runs as a daemon, and creates an unbounded supply of work.
//   It attempts to maintain a "cushion" of 5 unsent job instances (tasks)
// - Creates work for the application "ostools_app".
// - Moves the input file for each job from input to download directory;
//   the job names contain a timestamp and a sequence number, so that they're unique.
// - Search for TODO within this file to see more configuration options

#include <unistd.h>
#include <cstdlib>
#include <string>
#include <cstring>
#include <stdio.h>

#include "filesys.h"
#include "boinc_db.h"
#include "error_numbers.h"
#include "backend_lib.h"
#include "parse.h"
#include "util.h"

#include "sched_config.h"
#include "sched_util.h"
#include "sched_msgs.h"

#define CUSHION 5
  // maintain at least this many unsent tasks (results)
#define REPLICATION_FACTOR  1
  // generate as much tasks for every job (workunit) created
#define INPUT_FOLDER "../input_files"
  // The directory where the input files are stored
  // NO trailing slash!
// Search for TODO within this file to see more configuration options

// globals
char* wu_template;
DB_APP app;
int start_time;
int seqno;

// create one new job
//
int make_job(const char* filename) {
    DB_WORKUNIT wu;
    char wu_name[256], dst_path[256], src_path[256];
    const char* infiles[1];
    int retval;

    // make a unique name and move input file into download dir
    sprintf(wu_name, "ostools_app_%d_%d", start_time, seqno++); // TODO: change this if you want to generate the workunitname using the input file name
    log_messages.printf(MSG_DEBUG,
            "found input file %s and creating job %s\n", filename, wu_name
    );
    retval = config.download_path(filename, dst_path);
    if (retval) return retval;
    sprintf(src_path, "%s/%s", INPUT_FOLDER, filename);
    log_messages.printf(MSG_DEBUG,
            "move file from %s to %s\n", src_path, dst_path
    );
    retval = rename(src_path, dst_path);
    if (retval) {
        log_messages.printf(MSG_CRITICAL,
            "rename: %d, errno is %d\n", retval, errno
        );
        return retval;
    }

    // Fill in the job parameters
    //
    wu.clear();
    wu.appid = app.id;
    strcpy(wu.name, wu_name);
    wu.rsc_fpops_est = 1e12;
    wu.rsc_fpops_bound = 1e12;
    wu.rsc_memory_bound = 1e8;
    wu.rsc_disk_bound = 1e8;
    wu.delay_bound = 86400;
    wu.min_quorum = REPLICATION_FACTOR;
    wu.target_nresults = REPLICATION_FACTOR;
    wu.max_error_results = REPLICATION_FACTOR*2;
    wu.max_total_results = REPLICATION_FACTOR*4;
    wu.max_success_results = REPLICATION_FACTOR*2;
    infiles[0] = filename;

    // Register the job with BOINC
    //
    // TODO: change result template here if needed
    return create_work(
        wu,
        wu_template,
        "templates/ostools_result",
        "../templates/ostools_result",
        infiles,
        1,
        config
    );
}

void main_loop() {
    std::string file;
    int retval;

    while (1) {
        check_stop_daemons();
        int n;
        retval = count_unsent_results(n, 0);
        if (n > CUSHION) {
            sleep(60);
        } else {
            DirScanner dirscan(INPUT_FOLDER);
            // check if there are files in INPUT_FOLDER
            if (dirscan.scan(file)) {
                // files found, now create work for them
                int njobs = (CUSHION-n)/REPLICATION_FACTOR;
                log_messages.printf(MSG_DEBUG,
                    "Making %d jobs\n", njobs
                );
                for (int i=0; i<njobs; i++) {
                    retval = make_job(file.c_str());
                    if (retval) {
                        log_messages.printf(MSG_CRITICAL,
                            "can't make job: %d\n", retval
                        );
                        exit(retval);
                    }
                    if (!dirscan.scan(file)) break; // no more files but CUSHION not yet reached
                }
            } else {
                // no more files found, wait some time
                log_messages.printf(MSG_DEBUG,
                        "no more input files in: %s\n", INPUT_FOLDER
                );
                sleep(60); //increase this time if you can't supply a steady stream of input files
            }
            // Now sleep for a few seconds to let the transitioner
            // create instances for the jobs we just created.
            // Otherwise we could end up creating an excess of jobs.
            sleep(5);
        }
    }
}

int main(int argc, char** argv) {
    int i, retval;

    for (i=1; i<argc; i++) {
        if (!strcmp(argv[i], "-d")) {
            log_messages.set_debug_level(atoi(argv[++i]));
        } else {
            log_messages.printf(MSG_CRITICAL,
                "bad cmdline arg: %s", argv[i]
            );
        }
    }

    if (config.parse_file("..")) {
        log_messages.printf(MSG_CRITICAL,
            "can't read config file\n"
        );
        exit(1);
    }

    retval = boinc_db.open(
        config.db_name, config.db_host, config.db_user, config.db_passwd
    );
    if (retval) {
        log_messages.printf(MSG_CRITICAL, "can't open db\n");
        exit(1);
    }
    // TODO: change appname for which the work is generated
    if (app.lookup("where name='ostools_app'")) {
        log_messages.printf(MSG_CRITICAL, "can't find app\n");
        exit(1);
    }
    // TODO: change workunit template if needed
    if (read_file_malloc("../templates/ostools_wu", wu_template)) {
        log_messages.printf(MSG_CRITICAL, "can't read WU template\n");
        exit(1);
    }

    start_time = time(0);
    seqno = 0;

    log_messages.printf(MSG_NORMAL, "Starting\n");

    main_loop();
}

