<commit_before>use antidote::RwLock;
use irc::client::prelude::*;
use std::thread::{sleep, spawn};
use std::{fmt, sync::Arc, time::Duration};

use chrono::{self, NaiveDateTime};
use time;

use plugin::*;
pub mod database;
mod parser;
use self::database::Database;
use self::parser::CommandParser;

use self::error::*;
use error::ErrorKind as FrippyErrorKind;
use error::FrippyError;
use failure::ResultExt;

fn get_time() -> NaiveDateTime {
    let tm = time::now().to_timespec();
    NaiveDateTime::from_timestamp(tm.sec, 0u32)
}

fn get_events<T: Database>(db: &RwLock<T>, in_next: chrono::Duration) -> Vec<database::Event> {
    loop {
        let before = get_time() + in_next;
        match db.read().get_events_before(&before) {
            Ok(events) => return events,
            Err(e) => {
                if e.kind() != ErrorKind::NotFound {
                    error!("Failed to get events: {}", e);
                }
            }
        }

        sleep(in_next.to_std().expect("Failed to convert look ahead time"));
    }
}

fn run<T: Database>(client: &IrcClient, db: Arc<RwLock<T>>) {
    let look_ahead = chrono::Duration::minutes(2);

    let mut events = get_events(&db, look_ahead);

    let mut sleep_time = look_ahead
        .to_std()
        .expect("Failed to convert look ahead time");

    loop {
        let now = get_time();
        for event in events {
            if event.time <= now {
                let msg = format!("Reminder from {}: {}", event.author, event.content);
                if let Err(e) = client.send_notice(&event.receiver, &msg) {
                    error!("Failed to send reminder: {}", e);
                } else {
                    debug!("Sent reminder {:?}", event);

                    if let Some(repeat) = event.repeat {
                        let next_time = event.time + chrono::Duration::seconds(repeat as i64);

                        if let Err(e) = db.write().update_event_time(event.id, &next_time) {
                            error!("Failed to update reminder: {}", e);
                        } else {
                            debug!("Updated time on: {:?}", event);
                        }
                    } else if let Err(e) = db.write().delete_event(event.id) {
                        error!("Failed to delete reminder: {}", e);
                    }
                }
            } else {
                let until_event = (event.time - now)
                    .to_std()
                    .expect("Failed to convert until event time");

                if until_event < sleep_time {
                    sleep_time = until_event + Duration::from_secs(1);
                }
            }
        }

        sleep(sleep_time);
        sleep_time = Duration::from_secs(120);

        events = get_events(&db, look_ahead);
    }
}

#[derive(PluginName)]
pub struct Remind<T: 'static + Database> {
    events: Arc<RwLock<T>>,
    has_reminder: RwLock<bool>,
}

impl<T: 'static + Database> Remind<T> {
    pub fn new(db: T) -> Self {
        let events = Arc::new(RwLock::new(db));

        Remind {
            events: events,
            has_reminder: RwLock::new(false),
        }
    }

    fn set(&self, command: PluginCommand) -> Result<&str, RemindError> {
        let parser = CommandParser::try_from_tokens(command.tokens)?;
        debug!("parser: {:?}", parser);

        let mut target = parser.get_target();
        if target == "me" {
            target = &command.source;
        }

        let event = database::NewEvent {
            receiver: target,
            content: &parser.get_message(),
            author: &command.source,
            time: &parser.get_time(Duration::from_secs(120))?,
            repeat: parser
                .get_repeat(Duration::from_secs(300))?
                .map(|d| d.as_secs()),
        };

        debug!("New event: {:?}", event);

        Ok(self.events.write().insert_event(&event).map(|()| "Got it")?)
    }

    fn list(&self, user: &str) -> Result<String, RemindError> {
        let mut events = self.events.read().get_user_events(user)?;

        let mut list = events.remove(0).to_string();
        for ev in events {
            list.push_str("\r\n");
            list.push_str(&ev.to_string());
        }

        Ok(list)
    }

    fn delete(&self, mut command: PluginCommand) -> Result<&str, RemindError> {
        let id = command
            .tokens
            .remove(0)
            .parse::<i64>()
            .context(ErrorKind::Parsing)?;
        let event = self.events.read().get_event(id)?;

        if event.receiver.eq_ignore_ascii_case(&command.source)
            || event.author.eq_ignore_ascii_case(&command.source)
        {
            self.events
                .write()
                .delete_event(id)
                .map(|()| "Successfully deleted")
        } else {
            Ok("Only the author or receiver can delete a reminder")
        }
    }

    fn help(&self) -> &str {
        "usage: remind <subcommand>\r\n\
         subcommands: new, list, delete, help"
    }
}

impl<T: Database> Plugin for Remind<T> {
    fn execute(&self, client: &IrcClient, _: &Message) -> ExecutionStatus {
        let mut has_reminder = self.has_reminder.write();
        if !*has_reminder {
            let events = Arc::clone(&self.events);
            let client = client.clone();

            spawn(move || run(&client, events));

            *has_reminder = true;
        }

        ExecutionStatus::Done
    }

    fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), FrippyError> {
        panic!("Remind should not use frippy's threading")
    }

    fn command(&self, client: &IrcClient, mut command: PluginCommand) -> Result<(), FrippyError> {
        if command.tokens.is_empty() {
            return Ok(client
                .send_notice(&command.source, &ErrorKind::InvalidCommand.to_string())
                .context(FrippyErrorKind::Connection)?);
        }

        let source = command.source.clone();

        let sub_command = command.tokens.remove(0);
        let response = match sub_command.as_ref() {
            "new" => self.set(command).map(|s| s.to_owned()),
            "delete" => self.delete(command).map(|s| s.to_owned()),
            "list" => self.list(&source),
            "help" => Ok(self.help().to_owned()),
            _ => Err(ErrorKind::InvalidCommand.into()),
        };

        let result = match response {
            Ok(msg) => client
                .send_notice(&source, &msg)
                .context(FrippyErrorKind::Connection)?,<commit_msg>Remind: Change syntax and update help<commit_after>use antidote::RwLock;
use irc::client::prelude::*;
use std::thread::{sleep, spawn};
use std::{fmt, sync::Arc, time::Duration};

use chrono::{self, NaiveDateTime};
use time;

use plugin::*;
pub mod database;
mod parser;
use self::database::Database;
use self::parser::CommandParser;

use self::error::*;
use error::ErrorKind as FrippyErrorKind;
use error::FrippyError;
use failure::ResultExt;

fn get_time() -> NaiveDateTime {
    let tm = time::now().to_timespec();
    NaiveDateTime::from_timestamp(tm.sec, 0u32)
}

fn get_events<T: Database>(db: &RwLock<T>, in_next: chrono::Duration) -> Vec<database::Event> {
    loop {
        let before = get_time() + in_next;
        match db.read().get_events_before(&before) {
            Ok(events) => return events,
            Err(e) => {
                if e.kind() != ErrorKind::NotFound {
                    error!("Failed to get events: {}", e);
                }
            }
        }

        sleep(in_next.to_std().expect("Failed to convert look ahead time"));
    }
}

fn run<T: Database>(client: &IrcClient, db: Arc<RwLock<T>>) {
    let look_ahead = chrono::Duration::minutes(2);

    let mut events = get_events(&db, look_ahead);

    let mut sleep_time = look_ahead
        .to_std()
        .expect("Failed to convert look ahead time");

    loop {
        let now = get_time();
        for event in events {
            if event.time <= now {
                let msg = format!("Reminder from {}: {}", event.author, event.content);
                if let Err(e) = client.send_notice(&event.receiver, &msg) {
                    error!("Failed to send reminder: {}", e);
                } else {
                    debug!("Sent reminder {:?}", event);

                    if let Some(repeat) = event.repeat {
                        let next_time = event.time + chrono::Duration::seconds(repeat as i64);

                        if let Err(e) = db.write().update_event_time(event.id, &next_time) {
                            error!("Failed to update reminder: {}", e);
                        } else {
                            debug!("Updated time on: {:?}", event);
                        }
                    } else if let Err(e) = db.write().delete_event(event.id) {
                        error!("Failed to delete reminder: {}", e);
                    }
                }
            } else {
                let until_event = (event.time - now)
                    .to_std()
                    .expect("Failed to convert until event time");

                if until_event < sleep_time {
                    sleep_time = until_event + Duration::from_secs(1);
                }
            }
        }

        sleep(sleep_time);
        sleep_time = Duration::from_secs(120);

        events = get_events(&db, look_ahead);
    }
}

#[derive(PluginName)]
pub struct Remind<T: 'static + Database> {
    events: Arc<RwLock<T>>,
    has_reminder: RwLock<bool>,
}

impl<T: 'static + Database> Remind<T> {
    pub fn new(db: T) -> Self {
        let events = Arc::new(RwLock::new(db));

        Remind {
            events: events,
            has_reminder: RwLock::new(false),
        }
    }

    fn set(&self, command: PluginCommand) -> Result<&str, RemindError> {
        let parser = CommandParser::try_from_tokens(command.tokens)?;
        debug!("parser: {:?}", parser);

        let mut target = parser.get_target();
        if target == "me" {
            target = &command.source;
        }

        let event = database::NewEvent {
            receiver: target,
            content: &parser.get_message(),
            author: &command.source,
            time: &parser.get_time(Duration::from_secs(120))?,
            repeat: parser
                .get_repeat(Duration::from_secs(300))?
                .map(|d| d.as_secs()),
        };

        debug!("New event: {:?}", event);

        Ok(self.events.write().insert_event(&event).map(|()| "Got it")?)
    }

    fn list(&self, user: &str) -> Result<String, RemindError> {
        let mut events = self.events.read().get_user_events(user)?;

        let mut list = events.remove(0).to_string();
        for ev in events {
            list.push_str("\r\n");
            list.push_str(&ev.to_string());
        }

        Ok(list)
    }

    fn delete(&self, mut command: PluginCommand) -> Result<&str, RemindError> {
        let id = command
            .tokens
            .remove(0)
            .parse::<i64>()
            .context(ErrorKind::Parsing)?;
        let event = self.events.read().get_event(id)?;

        if event.receiver.eq_ignore_ascii_case(&command.source)
            || event.author.eq_ignore_ascii_case(&command.source)
        {
            self.events
                .write()
                .delete_event(id)
                .map(|()| "Successfully deleted")
        } else {
            Ok("Only the author or receiver can delete a reminder")
        }
    }

    fn help(&self) -> &str {
        "usage: remind <subcommand>\r\n\
         subcommands: user, list, delete, help\r\n\
         examples\r\n\
         remind user foo to sleep in 1 hour\r\n\
         remind user bar to leave early on 1.1 at 16:00 every week"
    }
}

impl<T: Database> Plugin for Remind<T> {
    fn execute(&self, client: &IrcClient, _: &Message) -> ExecutionStatus {
        let mut has_reminder = self.has_reminder.write();
        if !*has_reminder {
            let events = Arc::clone(&self.events);
            let client = client.clone();

            spawn(move || run(&client, events));

            *has_reminder = true;
        }

        ExecutionStatus::Done
    }

    fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), FrippyError> {
        panic!("Remind should not use frippy's threading")
    }

    fn command(&self, client: &IrcClient, mut command: PluginCommand) -> Result<(), FrippyError> {
        if command.tokens.is_empty() {
            return Ok(client
                .send_notice(&command.source, &ErrorKind::InvalidCommand.to_string())
                .context(FrippyErrorKind::Connection)?);
        }

        let source = command.source.clone();

        let sub_command = command.tokens.remove(0);
        let response = match sub_command.as_ref() {
            "user" => self.set(command).map(|s| s.to_owned()),
            "delete" => self.delete(command).map(|s| s.to_owned()),
            "list" => self.list(&source),
            "help" => Ok(self.help().to_owned()),
            _ => Err(ErrorKind::InvalidCommand.into()),
        };

        let result = match response {
            Ok(msg) => client
                .send_notice(&source, &msg)
                .context(FrippyErrorKind::Connection)?,
{'fraction_non_alphanumeric': 0.14349977507872244, 'fraction_numerical': 0.0036737142000299893, 'mean_word_length': 1.9099040139616057, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 81, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3918437', 'n_tokens_mistral': 4037, 'n_tokens_neox': 3887, 'n_words': 857}