timeleft 1.0.0

Tells you the time left until your next class from a file("times.csv") in your home folder Add an arg to print just once
use csv::Reader;

use libc::{signal, alarm, pause};
use serde::Deserialize;
use std::env::args;
use std::sync::Mutex;

#[derive(PartialEq,Debug)]
enum lastDrawn {
    time,
    between,
    none,
}

#[derive(Debug, Deserialize)]
struct csvTime {
    name: String,
    begin: String,
    end: String,
}
#[derive(Debug)]
struct Time {
    hour: String,
    minute: String,
}

struct PrintableTime {
    hours: i64,
    minutes: i64,
    seconds: i64,
}
impl PrintableTime {
    fn from_duration(d:Duration) -> Self {
        let h =d.num_hours();
        let m = d.num_minutes()-(60*h);
        let s = d.num_seconds()-(60*m);
        return Self { hours: h, minutes: m, seconds: s };
    }
}
fn add_zero(n:i64) -> String {
    if n<10 {
        let mut s = n.to_string();
        s.insert(0, '0');
        return s;
    } else {
        return n.to_string();
    }
}
use std::{fmt, cmp::Ordering, io::Write};
impl fmt::Display for PrintableTime {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        {
            if self.seconds==0 {
                write!(f, "{}:{}",self.hours,add_zero(self.minutes))
            } else {
                write!(f, "{}:{}:{}",self.hours,add_zero(self.minutes),add_zero(self.seconds))
            }
        }
    }
}

use chrono::{prelude::Local, Duration};
#[derive(PartialEq)]
enum RelPos {
    Before,
    In,
    After,
}

static csvtimes:Mutex<Vec<csvTime>> = Mutex::new(Vec::new());
static drawn_global:Mutex<lastDrawn> = Mutex::new(lastDrawn::none);
///positive is up and right
fn move_relative(x:i32,y:i32) {//up down right left ABCD
    if x.is_positive() {
        print!("\x1b[{}C",x);
    } else if x.is_negative() {
        print!("\x1b[{}D",x.abs());
    }
    if y.is_positive() {
        print!("\x1b[{}A",y);
    } else if y.is_negative() {
        print!("\x1b[{}B",y.abs());
    }
}
#[inline]
fn move_to(x:u32) {
    print!("\x1b[{}G",x);
}
#[inline]
fn save_pos() {//save cursor pos
    print!("\x1b7");//7 is save
}
#[inline]
fn restore_pos() {//restore the cursor positon to the one that was saved
    print!("\x1b8");//8 is restore
}

fn handler (_:i32) {
    let current_time = Local::now().time();
    let mut time_class_pos: Vec<RelPos> = Vec::new();//time position relative to each class
    let mut found_class = false;
    let csvtimes_local = csvtimes.lock().unwrap();
    let mut drawn = drawn_global.lock().unwrap();
    for i in csvtimes_local.as_slice() {
        //make times with u8's and such
        let mut begin: Time = Time { hour: String::new(), minute: String::new() };
        let mut end: Time = Time { hour: String::new(), minute: String::new() };
        
        let mut t = i.begin.split(':').into_iter();
        begin.hour=t.next().expect("Couldn't get hour.").to_string();
        begin.minute=t.next().expect("Couldn't get minute.").to_string();
        
        t = i.end.split(':').into_iter();
        end.hour=t.next().expect("Couldn't get hour.").to_string();
        end.minute=t.next().expect("Couldn't get minute.").to_string();
        
        let begin_time = chrono::NaiveTime::parse_from_str(&i.begin,"%H:%M").expect("can't get time:(");
        let end_time = chrono::NaiveTime::parse_from_str(&i.end,"%H:%M").expect("can't get time:(");
        
        if (begin_time..=end_time).contains(&current_time) {
            time_class_pos.push(RelPos::In);
            
            let d = end_time.signed_duration_since(current_time);
            let p = PrintableTime::from_duration(d);
            
            let hr = format!("{}hr:",p.hours.to_string());
            let m = format!("{}m:", p.minutes.to_string());
            let TimeLeft = format!("{}{}{}s",if hr!="0hr:" {hr} else {"".to_string()},if m!="0m:" {m} else {"".to_string()},p.seconds);
            let CurrentTime = current_time.format("%I:%M:%S %P");
            let mut stdout = std::io::stdout();
            // let (x, y) = (0,0);
            if *drawn!=lastDrawn::time {
                //move to column 17 to make them all lined up
                print!("Class:");move_to(17);println!("{}",i.name);
                
                print!("TimeLeft:");
                move_to(17);
                save_pos();
                println!("{}",TimeLeft);
                // stdout.flush().unwrap();
                print!("Current Time:");move_to(17);println!("{}",CurrentTime);
                
            } else {
                restore_pos();
                move_relative(0, 2);
                print!("{}",TimeLeft);
                restore_pos();
                move_relative(0, 1);
                println!("{}",CurrentTime);
                
                // stdout.flush().unwrap();
            }
            
            *drawn=lastDrawn::time;
            found_class=true;
        } else {
            time_class_pos.push(
                match begin_time.cmp(&current_time) {
                    Ordering::Greater => RelPos::Before,
                    Ordering::Equal => RelPos::In,
                    Ordering::Less => 
                    match end_time.cmp(&current_time) {
                        Ordering::Less => RelPos::In,
                        Ordering::Equal => RelPos::In,
                        Ordering::Greater => RelPos::After,
                    },
                }
            );
        }
        unsafe{libc::alarm(1);}
    }//loop thru each class
    if found_class==false {
        //check if we before first class or after lass. then: we in between. else: we out of school
        let first = &time_class_pos[0];
        let last = time_class_pos.last().unwrap();
        if *drawn!=lastDrawn::between {
            if ((first == &RelPos::After) || (first == &RelPos::In)) && ((last == &RelPos::In) || (last == &RelPos::Before)) {
                println!("In between classes");
                *drawn=lastDrawn::between;
            } else {
                println!("Outta school B)");
            }
        }
        
    }
}
fn main() {
    let current_time = Local::now().time();
    // let CurrentTime = current_time.format("%I:%M:%S %P");
    // print!("Current Time:");move_to(17);println!("{}",CurrentTime);

    const TIME_FN: &str = "times.csv";
    let home = std::env::var("HOME").expect("Couldn't get $HOME var");
    let csvpath:String = format!("{home}/{fn}",home=home,fn=TIME_FN);
    // CSVPATH.push_str();//   "~/times.csv"
    
    
    let f_result = Reader::from_path(csvpath.clone());
    let mut f;
    if f_result.is_err() {
        println!("HOME FOLDER:{}",home);
        panic!("\nPlease put times.csv in your home folder (/Users/[your username]/times.csv)\nTo get to there:\nPress ⌘+Shift+G in finder. Type ~ and press enter.\n")
    } else {
        f=f_result.unwrap();
    }
    
    // let csvtimes_local = Vec::new();
    //make csvtimes with all strings
    for i in f.deserialize() {
        csvtimes.lock().unwrap().push(i.unwrap());
    }
    if csvtimes.lock().unwrap().is_empty() {
        panic!("{} is empty.\nexample:\n\nname,begin,end\n1st Math,12:05,13:00\n",csvpath);
    }
    
    let args = args();

    if args.count()>1 {
        handler(0);
    } else {
        unsafe {
            signal(libc::SIGALRM, handler as usize);
            loop {
                alarm(1);
                pause();
                // std::thread::sleep(std::time::Duration::from_secs(1));
            }
        }
    }
}