use std::{thread, time};
use cron::Schedule;
use chrono::Utc;
use std::str::FromStr;

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

trait Task {
    fn run(&self);
}

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

struct TaskWebsiteUp {
    url: String,
}

impl TaskWebsiteUp {
    fn new(url: String) -> TaskWebsiteUp {
        let task = TaskWebsiteUp {
            url: url,
        };
        return task;
    }
}

impl Task for TaskWebsiteUp {
    fn run(&self) {
        let client = reqwest::blocking::Client::new();
        let resp = client.get(self.url).send().expect("Got an error calling for the website");
        let status = resp.status().as_u16();
        println!("{}", status);
    }
}

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

struct Job {
    schedule: cron::Schedule,
    next: chrono::DateTime<chrono::Utc>,
    name: String,
    function: Box<dyn Fn()>,
}

impl Job {
    fn new(expression: &str, name: String, function: Box<dyn Fn()>) -> Job {
        let job = Job{
            schedule: Schedule::from_str(expression).unwrap(),
            next: chrono::MIN_DATETIME,
            name: name,
            function: function,
        };
        return job;
    }

    fn set_next_time(&mut self) {
        self.next = self.schedule.upcoming(Utc).take(1).next().unwrap();
    }

    fn run(&self) {
        (self.function)();
    }
}

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

fn main() {
    // Store an array of jobs
    let mut jobs = Vec::new();

    // Define a job to run
    let test_task = TaskWebsiteUp::new("xxnetwork.wiki".to_string());
    let test_job = Job::new("0 * * * * * *", "Hello, World.".to_string(), Box::new(move || test_task.run()));
    jobs.push(test_job);

    loop {
        // Get a second from now for sleep
        let one_sec = time::Duration::from_secs(1);

        // Get the next timestamp for each job
        // We need to check this before the sleep, because if checked in the same second it should run, 
        // it returns the next time instead
        for job in jobs.iter_mut() {
            job.set_next_time();
        }

        // Sleep for a second
        // TODO: just sleep to the next job to run
        thread::sleep(one_sec);

        // Check through all the jobs in the queue
        for job in jobs.iter() {
            // Check if its time for the job to run
            if chrono::offset::Utc::now().timestamp() >= job.next.timestamp() {
                println!("{}", job.name);
                job.run();
            }
        }
    }
}