use std::io::Write;
use zs_utilinv_core::countermeter::{
    validate_logdata, FixpNum, Handle as CmHandle, LogCommand, LogEntry, NaiveDate,
};

struct ViewIo {
    iatty: bool,
    stdin: std::io::Stdin,
    stdout: std::io::Stdout,
    buf: String,
}

impl ViewIo {
    fn prompt(&mut self, q: &str) -> std::io::Result<&str> {
        if self.iatty {
            write!(&mut self.stdout, "{} $ ", q)?;
            self.stdout.flush()?;
        }
        self.buf.clear();
        self.stdin.read_line(&mut self.buf)?;
        if !self.iatty {
            writeln!(&mut self.stdout)?;
        }
        Ok(self.buf.trim())
    }

    fn prompt_yn(&mut self, q: &str) -> std::io::Result<bool> {
        let q2 = format!("{} ? [yn]", q);
        Ok(loop {
            break match self.prompt(&q2)?.to_lowercase().as_str() {
                "y" | "yes" | "j" | "ja" => true,
                "n" | "no" | "nein" => false,
                _ => continue,
            };
        })
    }
}

fn main() -> anyhow::Result<()> {
    let mut vio = ViewIo {
        iatty: atty::is(atty::Stream::Stdin) && atty::is(atty::Stream::Stdout),
        stdin: std::io::stdin(),
        stdout: std::io::stdout(),
        buf: String::new(),
    };
    std::fs::create_dir_all("cometers")?;

    let date: NaiveDate = loop {
        let tmp = vio.prompt("enter the date to edit")?;
        if tmp.is_empty() {
            return Ok(());
        }
        if let Ok(x) = NaiveDate::parse_from_str(tmp, "%Y-%m-%d") {
            break x;
        }
    };

    loop {
        let meter = vio.prompt("meter")?;
        if meter.is_empty() {
            break;
        }
        let meter = CmHandle::new(std::path::PathBuf::from("cometers"), meter.to_string());
        let mut meterlog = if !meter.log_path().exists() {
            eprintln!("ERROR: countermeter «{}» doesn't exist.", meter.name());
            if !vio.prompt_yn("would you like to init this cometer log")? {
                continue;
            }
            Default::default()
        } else {
            meter.read_logdata()?
        };
        for i in validate_logdata(&meterlog, Some(&date)) {
            println!("{}", i);
        }
        let use_res = on_meter(&mut vio, &date, meter.name(), &mut meterlog)?;
        if !use_res {
            // do nothing
        } else if meterlog.is_empty() {
            if vio.prompt_yn("WARN: would you like to clear+remove this cometer log")? {
                std::fs::remove_file(meter.log_path())?;
            }
        } else {
            for i in validate_logdata(&meterlog, Some(&date)) {
                println!("{}", i);
            }
            meter.write_logdata(&meterlog)?;
        }
    }
    Ok(())
}

#[derive(Clone)]
enum MeterAct {
    Value(Option<FixpNum>),
    Command(LogCommand),
    Comment(String),
}

fn on_meter(
    vio: &mut ViewIo,
    date: &NaiveDate,
    meter: &str,
    meterlog: &mut std::collections::BTreeSet<LogEntry>,
) -> std::io::Result<bool> {
    let prpt = format!("cm:{}", meter);
    for i in meterlog.iter().filter(|i| &i.date == date) {
        println!("{:?}", i);
    }
    Ok(loop {
        let tmp = match vio.prompt(&prpt)? {
            "" | "exit" | "quit" => break true,
            "qwos" => break false,
            "brk" => MeterAct::Command(LogCommand::Broken),
            "rst" => MeterAct::Command(LogCommand::Reset),
            "dec" => MeterAct::Command(LogCommand::Decrease),
            "inc" => MeterAct::Command(LogCommand::Increase),
            "-" => MeterAct::Value(None),
            "dlt" => {
                meterlog.retain(|i| &i.date != date);
                continue;
            }
            "p" => {
                for i in meterlog.iter().filter(|i| &i.date == date) {
                    println!("{:?}", i);
                }
                continue;
            }
            "?" | "help" => {
                println!(
                    r#"available log line types:
 brk .... broken
 rst .... reset
 dec .... decrease
 inc .... increase

 -   .... remove the registered value
 dlt .... delete the log line
 ?   .... print this help text
 exit ... exit this sub-repl, save updated entries
 qwos ... exit this sub-repl, don't save updates entries
 p   .... print the current entry

other allowed inputs:
 - a float number (must start with a digit)
 - "cmt " and then a comment
"#
                );
                continue;
            }
            tmp => {
                if tmp
                    .chars()
                    .next()
                    .map(|i| i.is_ascii_digit())
                    .unwrap_or(false)
                {
                    match tmp.parse::<FixpNum>() {
                        Ok(x) => MeterAct::Value(Some(x)),
                        Err(e) => {
                            eprintln!("ERROR: unable to parse float number: {}", e);
                            continue;
                        }
                    }
                } else if let Some(x) = tmp.strip_prefix("cmt ") {
                    MeterAct::Comment(x.to_string())
                } else {
                    eprintln!("ERROR: unknown meter action: {}", tmp);
                    continue;
                }
            }
        };
        let mut cnt = 0;
        *meterlog = std::mem::take(meterlog)
            .into_iter()
            .map(|mut i| {
                if &i.date == date {
                    match tmp.clone() {
                        MeterAct::Command(x) => i.cmd = x,
                        MeterAct::Value(x) => i.value = x,
                        MeterAct::Comment(x) => i.comment = x,
                    }
                    cnt += 1;
                }
                i
            })
            .collect();
        if cnt == 0 {
            let mut i = LogEntry {
                date: date.clone(),
                cmd: LogCommand::Broken,
                comment: String::new(),
                value: None,
            };
            match tmp {
                MeterAct::Command(x) => i.cmd = x,
                MeterAct::Value(x) => i.value = x,
                MeterAct::Comment(x) => i.comment = x,
            }
            meterlog.insert(i);
        }
        for i in validate_logdata(&meterlog, Some(&date)) {
            println!("{}", i);
        }
    })
}