use std::path::Path;
use std::path::PathBuf;

use std::str::Lines;

use std::fs;
use std::io;
use std::env;
#[derive(Debug)]
struct TangleError {
    report: String,
}

impl TangleError {
    fn new (report: &str) -> Self {
        TangleError {
            report: report.to_string (),
        }
    }
}
fn meta_block_line_p (line: &str) -> bool {
    if ! line .trim_start () .starts_with ("---") {
        false
    } else {
        let string = line.trim ();
        string .chars () .all (|x| x == '-')
    }
}
fn meta_block_collect_string (
    lines: &mut Lines,
) -> Option <String> {
    let mut result = String::new ();
    for line in lines {
        if meta_block_line_p (line) {
            return Some (result);
        } else {
            result.push_str (line);
            result.push ('\n');
        }
    }
    None
}
fn find_meta_block (string: &str) -> Option <String> {
    let mut lines = string.lines ();
    if let Some (first_line) = lines.next () {
        if meta_block_line_p (first_line) {
            meta_block_collect_string (&mut lines)
        } else {
            None
        }
    } else {
        None
    }
}
const DESTINATION_PREFIX: &'static str = "tangle:";

fn destination_line_p (line: &str) -> bool {
    line .trim_start () .starts_with (DESTINATION_PREFIX)
}

fn find_destination (string: &str) -> Option <String> {
    let meta_block = find_meta_block (string);
    if meta_block.is_none () {
        return None;
    }
    let string = meta_block .unwrap ();
    for line in string.lines () {
        if destination_line_p (line) {
            let destination = &line [DESTINATION_PREFIX.len () ..];
            let destination = destination.trim ();
            return Some (destination.to_string ());
        }
    }
    None
}

#[test]
fn test_find_destination () {
    let example = format! (
        "{}\n{}\n{}\n",
        "---",
        "tangle: core.rs",
        "---",
    );
    let destination = find_destination (&example) .unwrap ();
    assert_eq! (destination, "core.rs");
}
const BLOCK_BEGIN: &'static str = "```";
const BLOCK_END: &'static str = "```";

fn block_begin_line_p (line: &str) -> bool {
    line .trim_start () .starts_with (BLOCK_BEGIN)
}

fn block_end_line_p (line: &str) -> bool {
    line .trim_start () .starts_with (BLOCK_END)
}
fn tangle_collect (
    result: &mut String,
    lines: &mut Lines,
) -> Result <(), TangleError> {
    for line in lines {
        if block_end_line_p (line) {
            return Ok (());
        } else {
            result.push_str (line);
            result.push ('\n');
        }
    }
    let error = TangleError::new ("block_end mismatch");
    Err (error)
}
fn tangle (string: &str) -> Result <String, TangleError> {
    let mut result = String::new ();
    let mut lines = string.lines ();
    while let Some (line) = lines.next () {
        if block_begin_line_p (line) {
            tangle_collect (&mut result, &mut lines)?;
        }
    }
    Ok (result)
}
#[test]
fn test_tangle () {
    let example = format! (
        "{}\n{}\n{}\n{}\n",
        "``` rust",
        "hi",
        "hi",
        "```",
    );
    let expect = format! (
        "{}\n{}\n",
        "hi",
        "hi",
    );
    let result = tangle (&example) .unwrap ();
    assert_eq! (expect, result);
    let example = format! (
        "{}\n{}\n{}\n{}\n",
        "    ``` rust",
        "    hi",
        "    hi",
        "    ```",
    );
    let expect = format! (
        "{}\n{}\n",
        "    hi",
        "    hi",
    );
    let result = tangle (&example) .unwrap ();
    assert_eq! (expect, result);
}
fn good_path_p (path: &Path) -> bool {
    for component in path.iter () {
        if let Some (string) = component.to_str () {
            if string.starts_with ('.') {
                if ! string .chars () .all (|x| x == '.') {
                    return false;
                }
            }
        } else {
            return false;
        }
    }
    true
}
pub fn md_file_p (file: &Path) -> bool {
    if let Some (os_string) = file.extension () {
        if let Some (string) = os_string.to_str () {
            string == "md"
        } else {
            false
        }
    } else {
        false
    }
}
pub fn file_tangle (file: &Path) -> io::Result <()> {
    if ! md_file_p (file) {
        return Ok (());
    }
    let string = fs::read_to_string (file)?;
    if let Some (destination) = find_destination (&string) {
        let result = tangle (&string) .unwrap ();
        let mut destination_path = PathBuf::new ();
        destination_path.push (file);
        destination_path.pop ();
        destination_path.push (destination);
        println! (
            "- tangle : {:?} => {:?}",
            file.canonicalize ()?,
            destination_path.canonicalize ()?);
        fs::write (&destination_path, result)
    } else {
        Ok (())
    }
}
pub fn dir_tangle (dir: &Path) -> io::Result <()> {
    for entry in dir.read_dir ()? {
        if let Ok (entry) = entry {
            if good_path_p (&entry.path ()) {
                if entry.file_type ()? .is_file () {
                    file_tangle (&entry.path ())?
                }
            }
        }
    }
    Ok (())
}
pub fn dir_tangle_rec (dir: &Path) -> io::Result <()> {
    for entry in dir.read_dir ()? {
        if let Ok (entry) = entry {
            if good_path_p (&entry.path ()) {
                if entry.file_type ()? .is_file () {
                    file_tangle (&entry.path ())?
                } else if entry.file_type ()? .is_dir () {
                    dir_tangle_rec (&entry.path ())?
                }
            }
        }
    }
    Ok (())
}
pub fn absolute_lize (path: &Path) -> PathBuf {
    if path.is_relative () {
        let mut absolute_path = env::current_dir () .unwrap ();
        absolute_path.push (path);
        absolute_path
    } else {
        path.to_path_buf ()
    }
}
pub fn tangle_all_before_build () -> io::Result <()> {
    let path = Path::new (".");
    let current_dir = env::current_dir () .unwrap ();
    println! ("- md_tangle_engine");
    println! ("  tangle_all_before_build");
    println! ("  current_dir : {:?}", current_dir);
    let path = absolute_lize (&path);
    dir_tangle_rec (&path)
}
