// Copyright 2022 Frank Uhlig
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn extract_tags() {
        let contents = "\
tags: C/C++, code, algorithms
";
        // XXX: no file operations for the simple tasks
        assert_eq!(vec!["C/C++", "code", "algorithms"], get_tags(contents));
    }
}

use std::io;
use std::fs;
use std::env;
use std::error::Error;

#[derive(Debug)]
enum Action {
    List,
    Edit,
    Show,
}

#[derive(Debug)]
pub struct Config {
    fpath : String,
    action : Action,
}

impl Config {
    pub fn new(mut args : env::Args) -> Result<Config, &'static str> {
        args.next(); // skip program name (or assert that it's the correct thing?)

        let action_cli = args.next().expect("no action");

        let action = if action_cli.contains("list") {
            Action::List
        } else if action_cli.contains("edit") {
            Action::Edit
        } else if action_cli.contains("show") {
            Action::Show
        } else {
            Action::List // or do nothing or panic // XXX: already panicking above
        };

        let config_str = match env::var("TN_CONFIG") {
            Ok(c) => c,
            Err(_) => String::from(env::var("HOME").unwrap() + "/.tn"),
        };

        Ok(Config {
            fpath: String::from(config_str),
            action: action,
        })
    }
}

pub fn get_notes(content: &str) -> Vec<&str> {
    content
        .lines()
        .filter(|line| line.contains("todo:"))
        .collect()
}


use std::io::prelude::*; // contains read_line for `BufReader<File>`
use std::io::BufReader;
use std::fs::File;

pub fn get_title(content: &str) -> Result<String, Box<dyn Error>> {
    let fname = content.replace("todo:", "");
    let f = File::open(fname)?;

    let mut reader = BufReader::new(f);

    for line in reader.lines() {
        if let Ok(cont) = line {
            if cont.starts_with('=') {
                return Ok(String::from(cont
                                       .trim_start_matches("=")
                                       .trim_start_matches(" ")));
            }
        } else {
            return Err(Box::from("I/O failed"));
        }
    }

    Err(Box::from("title not found"))
}

pub fn get_due(content: &str) -> Result<String, Box<dyn Error>> {
    let fname = content.replace("todo:", "");
    let f = File::open(fname)?;

    let mut reader = BufReader::new(f);

    for line in reader.lines() {
        if let Ok(cont) = line {
            if cont.starts_with("* due:") {
                return Ok(String::from(cont
                                       .trim_start_matches("* due:")
                                       .trim_start()
                                       .trim_end()));
            }
        } else {
            return Err(Box::from("I/O failed"));
        }
    }

    Err(Box::from("tags not found"))
}

pub fn get_tags(content: &str) -> Result<Vec<String>, Box<dyn Error>> {
    let fname = content.replace("todo:", "");
    let f = File::open(fname)?;

    let mut reader = BufReader::new(f);

    for line in reader.lines() {
        if let Ok(cont) = line {
            if cont.starts_with("* tags:") {
                return Ok(cont
                          .trim_start_matches("* tags:")
                          .trim_start_matches(" ")
                          .split(",")
                          .map(|blubb| String::from(blubb))
                          .collect());
            }
        } else {
            return Err(Box::from("I/O failed"));
        }
    }

    Err(Box::from("tags not found"))
}

pub fn list_notes(config: &Config) -> Result<(), Box<dyn Error>> {
    // XXX: more concrete error message needed?
    let contents = fs::read_to_string(&config.fpath)?;
    let notes = get_notes(&contents);

    /* XXX: differences between the two latter
        for n in notes {
        for n in notes.iter() {
    */

    println!("title             tags            due             completion");
    println!("============================================================");
    for n in notes {
        print!("{}", get_title(n)?);
        print!("    ");
        for t in get_tags(n)? {
            print!("{},", t);
        }
        print!("     ");
        print!("{}", get_due(n)?);
        println!("");
    }

    Ok(())
}

pub fn show_notes(config: &Config) -> Result<(), Box<dyn Error>> {
    Err(Box::from("show not implemented"))
}

pub fn edit_notes(config: &Config) -> Result<(), Box<dyn Error>> {
    Err(Box::from("edit not implemented"))
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    // get content that is re-usable by multiple functions

    match config.action {
        Action::List => list_notes(&config),
        Action::Edit => edit_notes(&config),
        Action::Show => show_notes(&config),
    }
}