use crate::clone_ext::CloneExt;
use crate::filename::{NotePath, NotePathStr};
use crate::{config::LocalLinkKind, error::NoteError};
use html_escape;
use parking_lot::RwLock;
use parse_hyperlinks::parser::Link;
use parse_hyperlinks_extras::iterator_html::HtmlLinkInlineImage;
use percent_encoding::percent_decode_str;
use std::path::MAIN_SEPARATOR_STR;
use std::{
    borrow::Cow,
    collections::HashSet,
    path::{Component, Path, PathBuf},
    sync::Arc,
};
pub(crate) const HTML_EXT: &str = ".html";
const FORMAT_SEPARATOR: char = '?';
const FROM_TO_SEPARATOR: char = ':';
const FORMAT_ONLY_SORT_TAG: &str = "?";
fn assemble_link(
    root_path: &Path,
    docdir: &Path,
    dest: &Path,
    rewrite_rel_paths: bool,
    rewrite_abs_paths: bool,
) -> Option<PathBuf> {
    fn append(path: &mut PathBuf, append: &Path) {
        for dir in append.components() {
            match dir {
                Component::ParentDir => {
                    if !path.pop() {
                        let path_is_relative = {
                            let mut c = path.components();
                            !(c.next() == Some(Component::RootDir)
                                || c.next() == Some(Component::RootDir))
                        };
                        if path_is_relative {
                            path.push(Component::ParentDir.as_os_str());
                        } else {
                            path.clear();
                            break;
                        }
                    }
                }
                Component::Normal(c) => path.push(c),
                _ => {}
            }
        }
    }
    let dest_is_relative = {
        let mut c = dest.components();
        !(c.next() == Some(Component::RootDir) || c.next() == Some(Component::RootDir))
    };
    debug_assert!(docdir.starts_with(root_path));
    let mut link = match (rewrite_rel_paths, rewrite_abs_paths, dest_is_relative) {
        (true, false, true) => {
            let link = PathBuf::from(Component::RootDir.as_os_str());
            link.join(docdir.strip_prefix(root_path).ok()?)
        }
        (true, true, true) => docdir.to_path_buf(),
        (false, _, true) => PathBuf::new(),
        (_, false, false) => PathBuf::from(Component::RootDir.as_os_str()),
        (_, true, false) => root_path.to_path_buf(),
    };
    append(&mut link, dest);
    if link.as_os_str().is_empty() {
        None
    } else {
        Some(link)
    }
}
trait Hyperlink {
    fn decode_ampersand_and_percent(&mut self);
    #[allow(clippy::ptr_arg)]
    fn is_local_fn(value: &Cow<str>) -> bool;
    fn strip_local_scheme(&mut self);
    fn strip_scheme_fn(input: &mut Cow<str>);
    fn is_autolink(&self) -> bool;
    fn rebase_local_link(
        &mut self,
        root_path: &Path,
        docdir: &Path,
        rewrite_rel_paths: bool,
        rewrite_abs_paths: bool,
    ) -> Result<(), NoteError>;
    fn expand_shorthand_link(&mut self, root_path: &Path) -> Result<(), NoteError>;
    fn rewrite_autolink(&mut self);
    fn apply_format_attribute(&mut self);
    fn get_local_link_dest_path(&self) -> Option<&Path>;
    fn get_local_link_src_path(&self) -> Option<&Path>;
    fn append_html_ext(&mut self);
    fn to_html(&self) -> String;
}
impl<'a> Hyperlink for Link<'a> {
    #[inline]
    fn decode_ampersand_and_percent(&mut self) {
        fn dec_amp(val: &mut Cow<str>) {
            let decoded_text = html_escape::decode_html_entities(val);
            if matches!(&decoded_text, Cow::Owned(..)) {
                let decoded_text = Cow::Owned(decoded_text.into_owned());
                let _ = std::mem::replace(val, decoded_text);
            }
        }
        fn dec_amp_percent(val: &mut Cow<str>) {
            dec_amp(val);
            let decoded_dest = percent_decode_str(val.as_ref()).decode_utf8().unwrap();
            if matches!(&decoded_dest, Cow::Owned(..)) {
                let decoded_dest = Cow::Owned(decoded_dest.into_owned());
                let _ = std::mem::replace(val, decoded_dest);
            }
        }
        match self {
            Link::Text2Dest(text1, dest, title) => {
                dec_amp(text1);
                dec_amp_percent(dest);
                dec_amp(title);
            }
            Link::Image(alt, src) => {
                dec_amp(alt);
                dec_amp_percent(src);
            }
            Link::Image2Dest(text1, alt, src, text2, dest, title) => {
                dec_amp(text1);
                dec_amp(alt);
                dec_amp_percent(src);
                dec_amp(text2);
                dec_amp_percent(dest);
                dec_amp(title);
            }
            _ => unimplemented!(),
        };
    }
    fn is_local_fn(dest: &Cow<str>) -> bool {
        !((dest.contains("://") && !dest.contains(":///"))
            || dest.starts_with("mailto:")
            || dest.starts_with("tel:"))
    }
    fn strip_local_scheme(&mut self) {
        fn strip(dest: &mut Cow<str>) {
            if <Link<'_> as Hyperlink>::is_local_fn(dest) {
                <Link<'_> as Hyperlink>::strip_scheme_fn(dest);
            }
        }
        match self {
            Link::Text2Dest(_, dest, _title) => strip(dest),
            Link::Image2Dest(_, _, src, _, dest, _) => {
                strip(src);
                strip(dest);
            }
            Link::Image(_, src) => strip(src),
            _ => {}
        };
    }
    fn strip_scheme_fn(inout: &mut Cow<str>) {
        let output = inout
            .trim_start_matches("https://")
            .trim_start_matches("https:")
            .trim_start_matches("http://")
            .trim_start_matches("http:")
            .trim_start_matches("tpnote:")
            .trim_start_matches("mailto:")
            .trim_start_matches("tel:");
        if output != inout.as_ref() {
            let _ = std::mem::replace(inout, Cow::Owned(output.to_string()));
        }
    }
    fn is_autolink(&self) -> bool {
        let (text, dest) = match self {
            Link::Text2Dest(text, dest, _title) => (text, dest),
            Link::Image(alt, source) => (alt, source),
            _ => return false,
        };
        text == dest
    }
    fn rebase_local_link(
        &mut self,
        root_path: &Path,
        docdir: &Path,
        rewrite_rel_paths: bool,
        rewrite_abs_paths: bool,
    ) -> Result<(), NoteError> {
        let do_rebase = |path: &mut Cow<str>| -> Result<(), NoteError> {
            if <Link as Hyperlink>::is_local_fn(path) {
                let dest_out = assemble_link(
                    root_path,
                    docdir,
                    Path::new(path.as_ref()),
                    rewrite_rel_paths,
                    rewrite_abs_paths,
                )
                .ok_or(NoteError::InvalidLocalPath {
                    path: path.as_ref().to_string(),
                })?;
                let new_dest = Cow::Owned(dest_out.to_str().unwrap_or_default().to_string());
                let _ = std::mem::replace(path, new_dest);
            }
            Ok(())
        };
        match self {
            Link::Text2Dest(_, dest, _) => do_rebase(dest),
            Link::Image2Dest(_, _, src, _, dest, _) => do_rebase(src).and_then(|_| do_rebase(dest)),
            Link::Image(_, src) => do_rebase(src),
            _ => unimplemented!(),
        }
    }
    fn expand_shorthand_link(&mut self, root_path: &Path) -> Result<(), NoteError> {
        let shorthand_link = match self {
            Link::Text2Dest(_, dest, _) => dest,
            Link::Image2Dest(_, _, _, _, dest, _) => dest,
            _ => return Ok(()),
        };
        if !<Link as Hyperlink>::is_local_fn(shorthand_link) {
            return Ok(());
        }
        let (shorthand_str, shorthand_format) = match shorthand_link.split_once(FORMAT_SEPARATOR) {
            Some((path, fmt)) => (path, Some(fmt)),
            None => (shorthand_link.as_ref(), None),
        };
        let shorthand_path = Path::new(shorthand_str);
        if let Some(sort_tag) = shorthand_str.is_valid_sort_tag() {
            let shorthand_path = shorthand_path
                .strip_prefix(MAIN_SEPARATOR_STR)
                .unwrap_or(shorthand_path);
            let mut full_shorthand_path = root_path.to_path_buf();
            full_shorthand_path.push(shorthand_path);
            let found = full_shorthand_path
                .parent()
                .and_then(|dir| dir.find_file_with_sort_tag(sort_tag));
            if let Some(path) = found {
                let found_link = path.strip_prefix(root_path).unwrap();
                let mut found_link = Path::new(MAIN_SEPARATOR_STR)
                    .join(found_link)
                    .to_str()
                    .unwrap_or_default()
                    .to_string();
                if let Some(fmt) = shorthand_format {
                    found_link.push(FORMAT_SEPARATOR);
                    found_link.push_str(fmt);
                }
                let _ = std::mem::replace(shorthand_link, Cow::Owned(found_link));
            } else {
                return Err(NoteError::CanNotExpandShorthandLink {
                    path: full_shorthand_path.to_string_lossy().into_owned(),
                });
            }
        }
        Ok(())
    }
    fn rewrite_autolink(&mut self) {
        let text = match self {
            Link::Text2Dest(text, _, _) => text,
            Link::Image(alt, _) => alt,
            _ => return,
        };
        <Link as Hyperlink>::strip_scheme_fn(text);
    }
    fn apply_format_attribute(&mut self) {
        let (text, dest) = match self {
            Link::Text2Dest(text, dest, _) => (text, dest),
            Link::Image(alt, source) => (alt, source),
            _ => return,
        };
        if !<Link as Hyperlink>::is_local_fn(dest) {
            return;
        }
        let (path, format) = match dest.split_once(FORMAT_SEPARATOR) {
            Some(s) => s,
            None => return,
        };
        let mut short_text = Path::new(path)
            .file_name()
            .unwrap_or_default()
            .to_str()
            .unwrap_or_default();
        match format.split_once(FROM_TO_SEPARATOR) {
            None => {
                match format {
                    FORMAT_ONLY_SORT_TAG => short_text = Path::new(path).disassemble().0,
                    "" => short_text = Path::new(path).disassemble().2,
                    _ => {
                        short_text = Path::new(path).disassemble().2;
                        if let Some(idx) = short_text.rfind(format) {
                            short_text = &short_text[..idx];
                        };
                    }
                }
            }
            Some((from, to)) => {
                if !from.is_empty() {
                    if let Some(idx) = short_text.find(from) {
                        short_text = &short_text[(idx + from.len())..];
                    };
                }
                if !to.is_empty() {
                    if let Some(idx) = short_text.rfind(to) {
                        short_text = &short_text[..idx];
                    };
                }
            }
        }
        let _ = std::mem::replace(text, Cow::Owned(short_text.to_string()));
        let _ = std::mem::replace(dest, Cow::Owned(path.to_string()));
    }
    fn get_local_link_dest_path(&self) -> Option<&Path> {
        let dest = match self {
            Link::Text2Dest(_, dest, _) => dest,
            Link::Image2Dest(_, _, _, _, dest, _) => dest,
            _ => return None,
        };
        if <Link as Hyperlink>::is_local_fn(dest) {
            Some(Path::new(dest.as_ref()))
        } else {
            None
        }
    }
    fn get_local_link_src_path(&self) -> Option<&Path> {
        let src = match self {
            Link::Image2Dest(_, _, src, _, _, _) => src,
            Link::Image(_, src) => src,
            _ => return None,
        };
        if <Link as Hyperlink>::is_local_fn(src) {
            Some(Path::new(src.as_ref()))
        } else {
            None
        }
    }
    fn append_html_ext(&mut self) {
        let dest = match self {
            Link::Text2Dest(_, dest, _) => dest,
            Link::Image2Dest(_, _, _, _, dest, _) => dest,
            _ => return,
        };
        if <Link as Hyperlink>::is_local_fn(dest) {
            let path = dest.as_ref();
            if path.has_tpnote_ext() {
                let mut newpath = path.to_string();
                newpath.push_str(HTML_EXT);
                let _ = std::mem::replace(dest, Cow::Owned(newpath));
            }
        }
    }
    fn to_html(&self) -> String {
        fn enc_amp(val: Cow<str>) -> Cow<str> {
            let s = html_escape::encode_double_quoted_attribute(val.as_ref());
            if s == val {
                val
            } else {
                Cow::Owned(s.to_string())
            }
        }
        fn repl_backspace_enc_amp(val: Cow<str>) -> Cow<str> {
            let val = if val.as_ref().contains('\\') {
                Cow::Owned(val.to_string().replace('\\', "/"))
            } else {
                val
            };
            let s = html_escape::encode_double_quoted_attribute(val.as_ref());
            if s == val {
                val
            } else {
                Cow::Owned(s.to_string())
            }
        }
        match self {
            Link::Text2Dest(text, dest, title) => {
                let title_html = if !title.is_empty() {
                    format!(" title=\"{}\"", enc_amp(title.shallow_clone()))
                } else {
                    "".to_string()
                };
                format!(
                    "<a href=\"{}\"{}>{}</a>",
                    repl_backspace_enc_amp(dest.shallow_clone()),
                    title_html,
                    text
                )
            }
            Link::Image2Dest(text1, alt, src, text2, dest, title) => {
                let title_html = if !title.is_empty() {
                    format!(" title=\"{}\"", enc_amp(title.shallow_clone()))
                } else {
                    "".to_string()
                };
                format!(
                    "<a href=\"{}\"{}>{}<img src=\"{}\" alt=\"{}\">{}</a>",
                    repl_backspace_enc_amp(dest.shallow_clone()),
                    title_html,
                    text1,
                    repl_backspace_enc_amp(src.shallow_clone()),
                    enc_amp(alt.shallow_clone()),
                    text2
                )
            }
            Link::Image(alt, src) => {
                format!(
                    "<img src=\"{}\" alt=\"{}\">",
                    repl_backspace_enc_amp(src.shallow_clone()),
                    enc_amp(alt.shallow_clone())
                )
            }
            _ => unimplemented!(),
        }
    }
}
#[inline]
pub fn rewrite_links(
    html_input: String,
    root_path: &Path,
    docdir: &Path,
    local_link_kind: LocalLinkKind,
    rewrite_ext: bool,
    allowed_local_links: Arc<RwLock<HashSet<PathBuf>>>,
) -> String {
    let (rewrite_rel_paths, rewrite_abs_paths) = match local_link_kind {
        LocalLinkKind::Off => (false, false),
        LocalLinkKind::Short => (true, false),
        LocalLinkKind::Long => (true, true),
    };
    let mut rest = &*html_input;
    let mut html_out = String::new();
    for ((skipped, _consumed, remaining), mut link) in HtmlLinkInlineImage::new(&html_input) {
        html_out.push_str(skipped);
        rest = remaining;
        let mut link_is_autolink = link.is_autolink();
        link.decode_ampersand_and_percent();
        link_is_autolink = link_is_autolink || link.is_autolink();
        link.strip_local_scheme();
        match link
            .rebase_local_link(root_path, docdir, rewrite_rel_paths, rewrite_abs_paths)
            .and_then(|_| link.expand_shorthand_link(root_path))
        {
            Ok(()) => {}
            Err(e) => {
                let e = e.to_string();
                let e = html_escape::encode_text(&e);
                html_out.push_str(&format!("<i>{}</i>", e));
                continue;
            }
        };
        if link_is_autolink {
            link.rewrite_autolink();
        }
        link.apply_format_attribute();
        if let Some(dest_path) = link.get_local_link_dest_path() {
            allowed_local_links.write().insert(dest_path.to_path_buf());
        };
        if let Some(src_path) = link.get_local_link_src_path() {
            allowed_local_links.write().insert(src_path.to_path_buf());
        };
        if rewrite_ext {
            link.append_html_ext();
        }
        html_out.push_str(&link.to_html());
    }
    html_out.push_str(rest);
    log::debug!(
        "Viewer: referenced allowed local files: {}",
        allowed_local_links
            .read_recursive()
            .iter()
            .map(|p| {
                let mut s = "\n    '".to_string();
                s.push_str(&p.display().to_string());
                s
            })
            .collect::<String>()
    );
    html_out
    }
#[cfg(test)]
mod tests {
    use crate::error::NoteError;
    use crate::html::assemble_link;
    use crate::html::rewrite_links;
    use parking_lot::RwLock;
    use parse_hyperlinks::parser::Link;
    use parse_hyperlinks_extras::parser::parse_html::take_link;
    use std::borrow::Cow;
    use std::{
        collections::HashSet,
        path::{Path, PathBuf},
        sync::Arc,
    };
    use super::Hyperlink;
    #[test]
    fn test_assemble_link() {
        let output = assemble_link(
            Path::new("/my"),
            Path::new("/my/doc/path"),
            Path::new("../local/link to/note.md"),
            true,
            false,
        )
        .unwrap();
        assert_eq!(output, Path::new("/doc/local/link to/note.md"));
        let output = assemble_link(
            Path::new("/my"),
            Path::new("/my/doc/path"),
            Path::new("../local/link to/note.md"),
            false,
            false,
        )
        .unwrap();
        assert_eq!(output, Path::new("../local/link to/note.md"));
        let output = assemble_link(
            Path::new("/my"),
            Path::new("/my/doc/path"),
            Path::new("/test/../abs/local/link to/note.md"),
            false,
            false,
        )
        .unwrap();
        assert_eq!(output, Path::new("/abs/local/link to/note.md"));
        let output = assemble_link(
            Path::new("/my"),
            Path::new("/my/doc/path"),
            Path::new("/../local/link to/note.md"),
            false,
            false,
        );
        assert_eq!(output, None);
        let output = assemble_link(
            Path::new("/my"),
            Path::new("/my/doc/path"),
            Path::new("/abs/local/link to/note.md"),
            false,
            true,
        )
        .unwrap();
        assert_eq!(output, Path::new("/my/abs/local/link to/note.md"));
        let output = assemble_link(
            Path::new("/my"),
            Path::new("/my/doc/path"),
            Path::new("/test/../abs/local/link to/note.md"),
            false,
            false,
        )
        .unwrap();
        assert_eq!(output, Path::new("/abs/local/link to/note.md"));
        let output = assemble_link(
            Path::new("/my"),
            Path::new("/my/doc/path"),
            Path::new("abs/local/link to/note.md"),
            true,
            true,
        )
        .unwrap();
        assert_eq!(output, Path::new("/my/doc/path/abs/local/link to/note.md"));
    }
    #[test]
    fn test_decode_html_escape_and_percent() {
        let mut input = Link::Text2Dest(Cow::from("text"), Cow::from("dest"), Cow::from("title"));
        let expected = Link::Text2Dest(Cow::from("text"), Cow::from("dest"), Cow::from("title"));
        input.decode_ampersand_and_percent();
        let output = input;
        assert_eq!(output, expected);
        let mut input = Link::Text2Dest(
            Cow::from("te%20xt"),
            Cow::from("de%20st"),
            Cow::from("title"),
        );
        let expected =
            Link::Text2Dest(Cow::from("te%20xt"), Cow::from("de st"), Cow::from("title"));
        input.decode_ampersand_and_percent();
        let output = input;
        assert_eq!(output, expected);
        let mut input =
            Link::Text2Dest(Cow::from("text"), Cow::from("d:e%20st"), Cow::from("title"));
        let expected = Link::Text2Dest(Cow::from("text"), Cow::from("d:e st"), Cow::from("title"));
        input.decode_ampersand_and_percent();
        let output = input;
        assert_eq!(output, expected);
        let mut input = Link::Text2Dest(
            Cow::from("a&"lt"),
            Cow::from("a&"lt"),
            Cow::from("a&"lt"),
        );
        let expected = Link::Text2Dest(
            Cow::from("a&\"lt"),
            Cow::from("a&\"lt"),
            Cow::from("a&\"lt"),
        );
        input.decode_ampersand_and_percent();
        let output = input;
        assert_eq!(output, expected);
        let mut input = Link::Image(Cow::from("al%20t"), Cow::from("de%20st"));
        let expected = Link::Image(Cow::from("al%20t"), Cow::from("de st"));
        input.decode_ampersand_and_percent();
        let output = input;
        assert_eq!(output, expected);
        let mut input = Link::Image(Cow::from("a\\lt"), Cow::from("d\\est"));
        let expected = Link::Image(Cow::from("a\\lt"), Cow::from("d\\est"));
        input.decode_ampersand_and_percent();
        let output = input;
        assert_eq!(output, expected);
        let mut input = Link::Image(Cow::from("a&"lt"), Cow::from("a&"lt"));
        let expected = Link::Image(Cow::from("a&\"lt"), Cow::from("a&\"lt"));
        input.decode_ampersand_and_percent();
        let output = input;
        assert_eq!(output, expected);
    }
    #[test]
    fn test_is_local() {
        let input = Cow::from("/path/My doc.md");
        assert!(<Link as Hyperlink>::is_local_fn(&input));
        let input = Cow::from("tpnote:path/My doc.md");
        assert!(<Link as Hyperlink>::is_local_fn(&input));
        let input = Cow::from("tpnote:/path/My doc.md");
        assert!(<Link as Hyperlink>::is_local_fn(&input));
        let input = Cow::from("https://getreu.net");
        assert!(!<Link as Hyperlink>::is_local_fn(&input));
    }
    #[test]
    fn strip_local_scheme() {
        let mut input = Link::Text2Dest(
            Cow::from("xyz"),
            Cow::from("https://getreu.net"),
            Cow::from("xyz"),
        );
        let expected = input.clone();
        input.strip_local_scheme();
        assert_eq!(input, expected);
        let mut input = Link::Text2Dest(
            Cow::from("xyz"),
            Cow::from("tpnote:/dir/My doc.md"),
            Cow::from("xyz"),
        );
        let expected = Link::Text2Dest(
            Cow::from("xyz"),
            Cow::from("/dir/My doc.md"),
            Cow::from("xyz"),
        );
        input.strip_local_scheme();
        assert_eq!(input, expected);
    }
    #[test]
    fn test_is_autolink() {
        let input = Link::Image(Cow::from("abc"), Cow::from("abc"));
        assert!(input.is_autolink());
        let input = Link::Text2Dest(Cow::from("abc"), Cow::from("abc"), Cow::from("xyz"));
        assert!(input.is_autolink());
        let input = Link::Image(Cow::from("abc"), Cow::from("abcd"));
        assert!(!input.is_autolink());
        let input = Link::Text2Dest(Cow::from("abc"), Cow::from("abcd"), Cow::from("xyz"));
        assert!(!input.is_autolink());
    }
    #[test]
    fn test_rewrite_local_link() {
        let root_path = Path::new("/my/");
        let docdir = Path::new("/my/abs/note path/");
        let mut input = take_link("<a href=\"ftp://getreu.net\">Blog</a>")
            .unwrap()
            .1
             .1;
        input
            .rebase_local_link(root_path, docdir, true, false)
            .unwrap();
        assert!(input.get_local_link_dest_path().is_none());
        let root_path = Path::new("/my/");
        let docdir = Path::new("/my/abs/note path/");
        let mut input = take_link("<img src=\"down/./down/../../t m p.jpg\" alt=\"Image\" />")
            .unwrap()
            .1
             .1;
        let expected = "<img src=\"/abs/note path/t m p.jpg\" \
            alt=\"Image\">";
        input
            .rebase_local_link(root_path, docdir, true, false)
            .unwrap();
        let outpath = input.get_local_link_src_path().unwrap();
        let output = input.to_html();
        assert_eq!(output, expected);
        assert_eq!(outpath, PathBuf::from("/abs/note path/t m p.jpg"));
        let mut input = take_link("<img src=\"down/./../../t m p.jpg\" alt=\"Image\" />")
            .unwrap()
            .1
             .1;
        let expected = "<img src=\"/abs/t m p.jpg\" alt=\"Image\">";
        input
            .rebase_local_link(root_path, docdir, true, false)
            .unwrap();
        let outpath = input.get_local_link_src_path().unwrap();
        let output = input.to_html();
        assert_eq!(output, expected);
        assert_eq!(outpath, PathBuf::from("/abs/t m p.jpg"));
        let mut input = take_link("<a href=\"./down/./../my note 1.md\">my note 1</a>")
            .unwrap()
            .1
             .1;
        let expected = "<a href=\"/abs/note path/my note 1.md\">my note 1</a>";
        input
            .rebase_local_link(root_path, docdir, true, false)
            .unwrap();
        let outpath = input.get_local_link_dest_path().unwrap();
        let output = input.to_html();
        assert_eq!(output, expected);
        assert_eq!(outpath, PathBuf::from("/abs/note path/my note 1.md"));
        let mut input = take_link("<a href=\"/dir/./down/../my note 1.md\">my note 1</a>")
            .unwrap()
            .1
             .1;
        let expected = "<a href=\"/dir/my note 1.md\">my note 1</a>";
        input
            .rebase_local_link(root_path, docdir, true, false)
            .unwrap();
        let outpath = input.get_local_link_dest_path().unwrap();
        let output = input.to_html();
        assert_eq!(output, expected);
        assert_eq!(outpath, PathBuf::from("/dir/my note 1.md"));
        let mut input = take_link("<a href=\"./down/./../dir/my note 1.md\">my note 1</a>")
            .unwrap()
            .1
             .1;
        let expected = "<a href=\"dir/my note 1.md\">my note 1</a>";
        input
            .rebase_local_link(root_path, docdir, false, false)
            .unwrap();
        let outpath = input.get_local_link_dest_path().unwrap();
        let output = input.to_html();
        assert_eq!(output, expected);
        assert_eq!(outpath, PathBuf::from("dir/my note 1.md"));
        let mut input = take_link("<a href=\"./down/./../dir/my note 1.md\">my note 1</a>")
            .unwrap()
            .1
             .1;
        let expected = "<a href=\"/path/dir/my note 1.md\">my note 1</a>";
        input
            .rebase_local_link(
                Path::new("/my/note/"),
                Path::new("/my/note/path/"),
                true,
                false,
            )
            .unwrap();
        let outpath = input.get_local_link_dest_path().unwrap();
        let output = input.to_html();
        assert_eq!(output, expected);
        assert_eq!(outpath, PathBuf::from("/path/dir/my note 1.md"));
        let mut input = take_link("<a href=\"/down/./../dir/my note 1.md\">my note 1</a>")
            .unwrap()
            .1
             .1;
        let expected = "<a href=\"/dir/my note 1.md\">my note 1</a>";
        input
            .rebase_local_link(root_path, Path::new("/my/ignored/"), true, false)
            .unwrap();
        let outpath = input.get_local_link_dest_path().unwrap();
        let output = input.to_html();
        assert_eq!(output, expected);
        assert_eq!(outpath, PathBuf::from("/dir/my note 1.md"));
        let mut input = take_link("<a href=\"/down/../../dir/my note 1.md\">my note 1</a>")
            .unwrap()
            .1
             .1;
        let output = input
            .rebase_local_link(root_path, Path::new("/my/notepath/"), true, false)
            .unwrap_err();
        assert!(matches!(output, NoteError::InvalidLocalPath { .. }));
        let mut input = take_link("<a href=\"../../dir/my note 1.md\">my note 1</a>")
            .unwrap()
            .1
             .1;
        let output = input
            .rebase_local_link(root_path, Path::new("/my/notepath/"), true, false)
            .unwrap_err();
        assert!(matches!(output, NoteError::InvalidLocalPath { .. }));
        let root_path = Path::new("/");
        let mut input = take_link("<a href=\"../../dir/my note 1.md\">my note 1</a>")
            .unwrap()
            .1
             .1;
        let output = input
            .rebase_local_link(root_path, Path::new("/my/"), true, false)
            .unwrap_err();
        assert!(matches!(output, NoteError::InvalidLocalPath { .. }));
        let root_path = Path::new("/my");
        let mut input = take_link("<a href=\"../../dir/my note 1.md\">my note 1</a>")
            .unwrap()
            .1
             .1;
        let output = input
            .rebase_local_link(root_path, Path::new("/my/notepath"), true, false)
            .unwrap_err();
        assert!(matches!(output, NoteError::InvalidLocalPath { .. }));
        let root_path = Path::new("/my");
        let mut input =
            take_link("<a href=\"tpnote:dir/3.0-my note.md\">tpnote:dir/3.0-my note.md</a>")
                .unwrap()
                .1
                 .1;
        input.strip_local_scheme();
        input
            .rebase_local_link(root_path, Path::new("/my/path"), true, false)
            .unwrap();
        input.rewrite_autolink();
        input.apply_format_attribute();
        let outpath = input.get_local_link_dest_path().unwrap();
        let output = input.to_html();
        let expected = "<a href=\"/path/dir/3.0-my note.md\">dir/3.0-my note.md</a>";
        assert_eq!(output, expected);
        assert_eq!(outpath, PathBuf::from("/path/dir/3.0-my note.md"));
        let root_path = Path::new("/my");
        let mut input = take_link("<a href=\"tpnote:dir/3.0\">tpnote:dir/3.0</a>")
            .unwrap()
            .1
             .1;
        input.strip_local_scheme();
        input
            .rebase_local_link(root_path, Path::new("/my/path"), true, false)
            .unwrap();
        input.rewrite_autolink();
        input.apply_format_attribute();
        let outpath = input.get_local_link_dest_path().unwrap();
        let output = input.to_html();
        let expected = "<a href=\"/path/dir/3.0\">dir/3.0</a>";
        assert_eq!(output, expected);
        assert_eq!(outpath, PathBuf::from("/path/dir/3.0"));
        let root_path = Path::new("/my");
        let mut input = take_link(
            "<a href=\
            \"/uri\">link <em>foo <strong>bar</strong> <code>#</code></em>\
            </a>",
        )
        .unwrap()
        .1
         .1;
        input.strip_local_scheme();
        input
            .rebase_local_link(root_path, Path::new("/my/path"), true, false)
            .unwrap();
        let outpath = input.get_local_link_dest_path().unwrap();
        let expected = "<a href=\"/uri\">link <em>foo <strong>bar\
            </strong> <code>#</code></em></a>";
        let output = input.to_html();
        assert_eq!(output, expected);
        assert_eq!(outpath, PathBuf::from("/uri"));
    }
    #[test]
    fn test_rewrite_autolink() {
        let mut input = Link::Text2Dest(
            Cow::from("http://getreu.net"),
            Cow::from("http://getreu.net"),
            Cow::from("title"),
        );
        let expected = Link::Text2Dest(
            Cow::from("getreu.net"),
            Cow::from("http://getreu.net"),
            Cow::from("title"),
        );
        input.rewrite_autolink();
        let output = input;
        assert_eq!(output, expected);
        let mut input = Link::Text2Dest(
            Cow::from("/dir/3.0"),
            Cow::from("/dir/3.0-My note.md"),
            Cow::from("title"),
        );
        let expected = Link::Text2Dest(
            Cow::from("/dir/3.0"),
            Cow::from("/dir/3.0-My note.md"),
            Cow::from("title"),
        );
        input.rewrite_autolink();
        let output = input;
        assert_eq!(output, expected);
        let mut input = Link::Text2Dest(
            Cow::from("tpnote:/dir/3.0"),
            Cow::from("/dir/3.0-My note.md"),
            Cow::from("title"),
        );
        let expected = Link::Text2Dest(
            Cow::from("/dir/3.0"),
            Cow::from("/dir/3.0-My note.md"),
            Cow::from("title"),
        );
        input.rewrite_autolink();
        let output = input;
        assert_eq!(output, expected);
        let mut input = Link::Text2Dest(
            Cow::from("tpnote:/dir/3.0"),
            Cow::from("/dir/3.0-My note.md?"),
            Cow::from("title"),
        );
        let expected = Link::Text2Dest(
            Cow::from("/dir/3.0"),
            Cow::from("/dir/3.0-My note.md?"),
            Cow::from("title"),
        );
        input.rewrite_autolink();
        let output = input;
        assert_eq!(output, expected);
        let mut input = Link::Text2Dest(
            Cow::from("/dir/3.0-My note.md"),
            Cow::from("/dir/3.0-My note.md"),
            Cow::from("title"),
        );
        let expected = Link::Text2Dest(
            Cow::from("/dir/3.0-My note.md"),
            Cow::from("/dir/3.0-My note.md"),
            Cow::from("title"),
        );
        input.rewrite_autolink();
        let output = input;
        assert_eq!(output, expected);
    }
    #[test]
    fn test_apply_format_attribute() {
        let mut input = Link::Text2Dest(
            Cow::from("tpnote:/dir/3.0"),
            Cow::from("/dir/3.0-My note.md"),
            Cow::from("title"),
        );
        let expected = Link::Text2Dest(
            Cow::from("tpnote:/dir/3.0"),
            Cow::from("/dir/3.0-My note.md"),
            Cow::from("title"),
        );
        input.apply_format_attribute();
        let output = input;
        assert_eq!(output, expected);
        let mut input = Link::Text2Dest(
            Cow::from("does not matter"),
            Cow::from("/dir/3.0-My note.md?"),
            Cow::from("title"),
        );
        let expected = Link::Text2Dest(
            Cow::from("My note"),
            Cow::from("/dir/3.0-My note.md"),
            Cow::from("title"),
        );
        input.apply_format_attribute();
        let output = input;
        assert_eq!(output, expected);
        let mut input = Link::Text2Dest(
            Cow::from("/dir/3.0-My note--red_blue_green.jpg"),
            Cow::from("/dir/3.0-My note--red_blue_green.jpg"),
            Cow::from("title"),
        );
        let expected = Link::Text2Dest(
            Cow::from("/dir/3.0-My note--red_blue_green.jpg"),
            Cow::from("/dir/3.0-My note--red_blue_green.jpg"),
            Cow::from("title"),
        );
        input.apply_format_attribute();
        let output = input;
        assert_eq!(output, expected);
        let mut input = Link::Text2Dest(
            Cow::from("does not matter"),
            Cow::from("/dir/3.0-My note--red_blue_green.jpg?"),
            Cow::from("title"),
        );
        let expected = Link::Text2Dest(
            Cow::from("My note--red_blue_green"),
            Cow::from("/dir/3.0-My note--red_blue_green.jpg"),
            Cow::from("title"),
        );
        input.apply_format_attribute();
        let output = input;
        assert_eq!(output, expected);
        let mut input = Link::Text2Dest(
            Cow::from("does not matter"),
            Cow::from("/dir/3.0-My note--red_blue_green.jpg?--"),
            Cow::from("title"),
        );
        let expected = Link::Text2Dest(
            Cow::from("My note"),
            Cow::from("/dir/3.0-My note--red_blue_green.jpg"),
            Cow::from("title"),
        );
        input.apply_format_attribute();
        let output = input;
        assert_eq!(output, expected);
        let mut input = Link::Text2Dest(
            Cow::from("does not matter"),
            Cow::from("/dir/3.0-My note--red_blue_green.jpg?_"),
            Cow::from("title"),
        );
        let expected = Link::Text2Dest(
            Cow::from("My note--red_blue"),
            Cow::from("/dir/3.0-My note--red_blue_green.jpg"),
            Cow::from("title"),
        );
        input.apply_format_attribute();
        let output = input;
        assert_eq!(output, expected);
        let mut input = Link::Text2Dest(
            Cow::from("does not matter"),
            Cow::from("/dir/3.0-My note--red_blue_green.jpg?:"),
            Cow::from("title"),
        );
        let expected = Link::Text2Dest(
            Cow::from("3.0-My note--red_blue_green.jpg"),
            Cow::from("/dir/3.0-My note--red_blue_green.jpg"),
            Cow::from("title"),
        );
        input.apply_format_attribute();
        let output = input;
        assert_eq!(output, expected);
        let mut input = Link::Text2Dest(
            Cow::from("does not matter"),
            Cow::from("/dir/3.0-My note--red_blue_green.jpg?--:."),
            Cow::from("title"),
        );
        let expected = Link::Text2Dest(
            Cow::from("red_blue_green"),
            Cow::from("/dir/3.0-My note--red_blue_green.jpg"),
            Cow::from("title"),
        );
        input.apply_format_attribute();
        let output = input;
        assert_eq!(output, expected);
        let mut input = Link::Text2Dest(
            Cow::from("does not matter"),
            Cow::from("/dir/3.0-My note--red_blue_green.jpg?_:_"),
            Cow::from("title"),
        );
        let expected = Link::Text2Dest(
            Cow::from("blue"),
            Cow::from("/dir/3.0-My note--red_blue_green.jpg"),
            Cow::from("title"),
        );
        input.apply_format_attribute();
        let output = input;
        assert_eq!(output, expected);
    }
    #[test]
    fn text_get_local_link_path() {
        let input = Link::Text2Dest(Cow::from("xyz"), Cow::from("/dir/3.0"), Cow::from("title"));
        assert_eq!(
            input.get_local_link_dest_path(),
            Some(Path::new("/dir/3.0"))
        );
        let input = Link::Text2Dest(
            Cow::from("xyz"),
            Cow::from("http://getreu.net"),
            Cow::from("title"),
        );
        assert_eq!(input.get_local_link_dest_path(), None);
    }
    #[test]
    fn test_append_html_ext() {
        let mut input = Link::Text2Dest(
            Cow::from("abc"),
            Cow::from("/dir/3.0-My note.md"),
            Cow::from("title"),
        );
        let expected = Link::Text2Dest(
            Cow::from("abc"),
            Cow::from("/dir/3.0-My note.md.html"),
            Cow::from("title"),
        );
        input.append_html_ext();
        let output = input;
        assert_eq!(output, expected);
    }
    #[test]
    fn test_to_html() {
        let input = Link::Text2Dest(
            Cow::from("te\\x/t"),
            Cow::from("de\\s/t"),
            Cow::from("ti\\t/le"),
        );
        let expected = "<a href=\"de/s/t\" title=\"ti\\t/le\">te\\x/t</a>";
        let output = input.to_html();
        assert_eq!(output, expected);
        let input = Link::Text2Dest(
            Cow::from("te&> xt"),
            Cow::from("de&> st"),
            Cow::from("ti&> tle"),
        );
        let expected = "<a href=\"de&> st\" title=\"ti&> tle\">te&> xt</a>";
        let output = input.to_html();
        assert_eq!(output, expected);
        let input = Link::Image(Cow::from("al&t"), Cow::from("sr&c"));
        let expected = "<img src=\"sr&c\" alt=\"al&t\">";
        let output = input.to_html();
        assert_eq!(output, expected);
        let input = Link::Text2Dest(Cow::from("te&> xt"), Cow::from("de&> st"), Cow::from(""));
        let expected = "<a href=\"de&> st\">te&> xt</a>";
        let output = input.to_html();
        assert_eq!(output, expected);
    }
    #[test]
    fn test_rewrite_links() {
        use crate::config::LocalLinkKind;
        let allowed_urls = Arc::new(RwLock::new(HashSet::new()));
        let input = "abc<a href=\"ftp://getreu.net\">Blog</a>\
            def<a href=\"https://getreu.net\">https://getreu.net</a>\
            ghi<img src=\"t m p.jpg\" alt=\"test 1\" />\
            jkl<a href=\"down/../down/my note 1.md\">my note 1</a>\
            mno<a href=\"http:./down/../dir/my note.md\">http:./down/../dir/my note.md</a>\
            pqr<a href=\"http:/down/../dir/my note.md\">\
            http:/down/../dir/my note.md</a>\
            stu<a href=\"http:/../dir/underflow/my note.md\">\
            not allowed dir</a>\
            vwx<a href=\"http:../../../not allowed dir/my note.md\">\
            not allowed</a>"
            .to_string();
        let expected = "abc<a href=\"ftp://getreu.net\">Blog</a>\
            def<a href=\"https://getreu.net\">getreu.net</a>\
            ghi<img src=\"/abs/note path/t m p.jpg\" alt=\"test 1\">\
            jkl<a href=\"/abs/note path/down/my note 1.md\">my note 1</a>\
            mno<a href=\"/abs/note path/dir/my note.md\">./down/../dir/my note.md</a>\
            pqr<a href=\"/dir/my note.md\">/down/../dir/my note.md</a>\
            stu<i><INVALID: /../dir/underflow/my note.md></i>\
            vwx<i><INVALID: ../../../not allowed dir/my note.md></i>"
            .to_string();
        let root_path = Path::new("/my/");
        let docdir = Path::new("/my/abs/note path/");
        let output = rewrite_links(
            input,
            root_path,
            docdir,
            LocalLinkKind::Short,
            false,
            allowed_urls.clone(),
        );
        let url = allowed_urls.read_recursive();
        assert!(url.contains(&PathBuf::from("/abs/note path/t m p.jpg")));
        assert!(url.contains(&PathBuf::from("/abs/note path/dir/my note.md")));
        assert!(url.contains(&PathBuf::from("/abs/note path/down/my note 1.md")));
        assert_eq!(output, expected);
    }
    #[test]
    fn test_rewrite_links2() {
        use crate::config::LocalLinkKind;
        let allowed_urls = Arc::new(RwLock::new(HashSet::new()));
        let input = "abd<a href=\"tpnote:dir/my note.md\">\
            <img src=\"/imagedir/favicon-32x32.png\" alt=\"logo\"></a>abd"
            .to_string();
        let expected = "abd<a href=\"/abs/note path/dir/my note.md\">\
            <img src=\"/imagedir/favicon-32x32.png\" alt=\"logo\"></a>abd";
        let root_path = Path::new("/my/");
        let docdir = Path::new("/my/abs/note path/");
        let output = rewrite_links(
            input,
            root_path,
            docdir,
            LocalLinkKind::Short,
            false,
            allowed_urls.clone(),
        );
        let url = allowed_urls.read_recursive();
        println!("{:?}", allowed_urls.read_recursive());
        assert!(url.contains(&PathBuf::from("/abs/note path/dir/my note.md")));
        assert_eq!(output, expected);
    }
}