/*
 * meli
 *
 * Copyright 2019 Manos Pitsidianakis
 *
 * This file is part of meli.
 *
 * meli is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * meli is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with meli. If not, see <http://www.gnu.org/licenses/>.
 */

/*! Find mailcap entries to execute attachments.
 */
use crate::state::Context;
use crate::types::{create_temp_file, ForkType, UIEvent};
use melib::attachments::decode;
use melib::text_processing::GlobMatch;
use melib::{email::Attachment, MeliError, Result};
use std::collections::HashMap;
use std::io::Read;
use std::io::Write;
use std::path::PathBuf;
use std::process::{Command, Stdio};

pub struct MailcapEntry {
    command: String,
    /* Pass to pager */
    copiousoutput: bool,
}

impl MailcapEntry {
    pub fn execute(a: &Attachment, context: &mut Context) -> Result<()> {
        /* lookup order:
         *  $XDG_CONFIG_HOME/meli/mailcap:$XDG_CONFIG_HOME/.mailcap:$HOME/.mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap
         */
        let xdg_dirs =
            xdg::BaseDirectories::with_prefix("meli").map_err(|e| MeliError::new(e.to_string()))?;
        let mut mailcap_path = xdg_dirs
            .place_config_file("mailcap")
            .map_err(|e| MeliError::new(e.to_string()))?;
        if !mailcap_path.exists() {
            mailcap_path = xdg::BaseDirectories::new()
                .map_err(|e| MeliError::new(e.to_string()))?
                .place_config_file("mailcap")?;
            if !mailcap_path.exists() {
                if let Ok(home) = std::env::var("HOME") {
                    mailcap_path = PathBuf::from(format!("{}/.mailcap", home));
                }
                if !mailcap_path.exists() {
                    mailcap_path = PathBuf::from("/etc/mailcap");
                    if !mailcap_path.exists() {
                        mailcap_path = PathBuf::from("/usr/etc/mailcap");
                        if !mailcap_path.exists() {
                            mailcap_path = PathBuf::from("/usr/local/etc/mailcap");
                        }
                        if !mailcap_path.exists() {
                            return Err(MeliError::new("No mailcap file found."));
                        }
                    }
                }
            }
        }

        let mut hash_map = HashMap::new();
        let mut content = String::new();

        std::fs::File::open(mailcap_path.as_path())?.read_to_string(&mut content)?;
        let content_type = a.content_type().to_string();

        let mut result = None;
        let mut lines_iter = content.lines();
        while let Some(l) = lines_iter.next() {
            let l = l.trim();
            if l.starts_with('#') {
                continue;
            }
            if l.is_empty() {
                continue;
            }

            if l.ends_with('\\') {
                let l = format!("{}{}", &l[..l.len() - 2], lines_iter.next().unwrap());
                let mut parts_iter = l.split(';');
                let key = parts_iter.next().unwrap();
                let cmd = parts_iter.next().unwrap();
                //let flags = parts_iter.next().unwrap();
                if key.starts_with(&content_type) || key.matches_glob(&content_type) {
                    let mut copiousoutput = false;
                    while let Some(flag) = parts_iter.next() {
                        if flag.trim() == "copiousoutput" {
                            copiousoutput = true;
                        } else {
                            debug!("unknown mailcap flag: {}", flag);
                        }
                    }

                    result = Some(MailcapEntry {
                        command: cmd.to_string(),
                        copiousoutput,
                    });
                    break;
                }
                hash_map.insert(key.to_string(), cmd.to_string());
            } else {
                let mut parts_iter = l.split(';');
                let key = parts_iter.next().unwrap();
                let cmd = parts_iter.next().unwrap();
                //let flags = parts_iter.next().unwrap();
                if key.starts_with(&content_type) || key.matches_glob(&content_type) {
                    let mut copiousoutput = false;
                    while let Some(flag) = parts_iter.next() {
                        if flag.trim() == "copiousoutput" {
                            copiousoutput = true;
                        } else {
                            debug!("unknown mailcap flag: {}", flag);
                        }
                    }

                    result = Some(MailcapEntry {
                        command: cmd.to_string(),
                        copiousoutput,
                    });
                    break;
                }
                hash_map.insert(key.to_string(), cmd.to_string());
            }
        }

        match result {
            None => Err(MeliError::new("Not found")),
            Some(MailcapEntry {
                command,
                copiousoutput,
            }) => {
                let parts = split_command!(command);
                let (cmd, args) = (parts[0], &parts[1..]);
                let mut f = None;
                let mut needs_stdin = true;
                let params = a.parameters();
                /* TODO: See mailcap(5)
                 * - replace "\%" with "%" and unescape other blackslash uses.
                 * - "%n" and "%F".
                 * - test=xxx field.
                 */
                let args = args
                    .iter()
                    .map(|arg| match *arg {
                        "%s" => {
                            needs_stdin = false;
                            let _f = create_temp_file(&decode(a, None), None, None, true);
                            let p = _f.path().display().to_string();
                            f = Some(_f);
                            p
                        }
                        "%t" => a.content_type().to_string(),
                        param if param.starts_with("%{") && param.ends_with('}') => {
                            let param = &param["%{".len()..param.len() - 1];
                            if let Some(v) = params.iter().find(|(k, _)| *k == param.as_bytes()) {
                                String::from_utf8_lossy(v.1).into()
                            } else if param == "charset" {
                                String::from("utf-8")
                            } else {
                                String::new()
                            }
                        }
                        a => a.to_string(),
                    })
                    .collect::<Vec<String>>();
                let cmd_string = format!("{} {}", cmd, args.join(" "));
                melib::log(
                    format!("Executing: sh -c \"{}\"", cmd_string.replace("\"", "\\\"")),
                    melib::DEBUG,
                );
                if copiousoutput {
                    let out = if needs_stdin {
                        let mut child = Command::new("sh")
                            .args(&["-c", &cmd_string])
                            .stdin(Stdio::piped())
                            .stdout(Stdio::piped())
                            .spawn()?;

                        child.stdin.as_mut().unwrap().write_all(&decode(a, None))?;
                        child.wait_with_output()?.stdout
                    } else {
                        let child = Command::new("sh")
                            .args(&["-c", &cmd_string])
                            .stdin(Stdio::piped())
                            .stdout(Stdio::piped())
                            .spawn()?;

                        child.wait_with_output()?.stdout
                    };
                    let pager_cmd = if let Ok(v) = std::env::var("PAGER") {
                        std::borrow::Cow::from(v)
                    } else {
                        std::borrow::Cow::from("less")
                    };

                    let mut pager = Command::new(pager_cmd.as_ref())
                        .stdin(Stdio::piped())
                        .stdout(Stdio::inherit())
                        .spawn()?;
                    pager.stdin.as_mut().unwrap().write_all(&out)?;
                    debug!(pager.wait_with_output()?.stdout);
                } else {
                    if needs_stdin {
                        let mut child = Command::new("sh")
                            .args(&["-c", &cmd_string])
                            .stdin(Stdio::piped())
                            .stdout(Stdio::inherit())
                            .spawn()?;

                        child.stdin.as_mut().unwrap().write_all(&decode(a, None))?;
                        debug!(child.wait_with_output()?.stdout);
                    } else {
                        let child = Command::new("sh")
                            .args(&["-c", &cmd_string])
                            .stdin(Stdio::inherit())
                            .stdout(Stdio::inherit())
                            .spawn()?;

                        debug!(child.wait_with_output()?.stdout);
                    }
                }
                context.replies.push_back(UIEvent::Fork(ForkType::Finished));
                Ok(())
            }
        }
    }
}
