/*
 * meli - subcommands.rs
 *
 * Copyright 2017-2018 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/>.
 */

#[cfg(feature = "cli-docs")]
use std::io::prelude::*;
use std::{
    path::PathBuf,
    process::{Command, Stdio},
};

use crossbeam::channel::{Receiver, Sender};
#[cfg(feature = "cli-docs")]
use flate2::bufread::GzDecoder;
use melib::Result;

use crate::{args::*, *};

pub fn create_config(path: Option<PathBuf>) -> Result<()> {
    let config_path = if let Some(path) = path {
        path
    } else {
        conf::get_config_file()?
    };
    if config_path.exists() {
        return Err(Error::new(format!(
            "File `{}` already exists.\nMaybe you meant to specify another path?",
            config_path.display()
        )));
    }
    conf::create_config_file(&config_path)?;
    Ok(())
}

pub fn edit_config() -> Result<()> {
    let editor = std::env::var("EDITOR")
        .or_else(|_| std::env::var("VISUAL"))
        .map_err(|err| {
            format!("Could not find any value in environment variables EDITOR and VISUAL. {err}")
        })?;
    let config_path = crate::conf::get_config_file()?;

    let mut cmd = Command::new(editor);

    let mut handle = &mut cmd;
    for c in crate::conf::get_included_configs(config_path)? {
        handle = handle.arg(&c);
    }
    let mut handle = handle
        .stdin(Stdio::inherit())
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .spawn()?;
    handle.wait()?;
    Ok(())
}

#[cfg(feature = "cli-docs")]
pub fn man(page: manpages::ManPages, source: bool) -> Result<String> {
    const MANPAGES: [&[u8]; 4] = [
        include_bytes!(concat!(env!("OUT_DIR"), "/meli.txt.gz")),
        include_bytes!(concat!(env!("OUT_DIR"), "/meli.conf.txt.gz")),
        include_bytes!(concat!(env!("OUT_DIR"), "/meli-themes.txt.gz")),
        include_bytes!(concat!(env!("OUT_DIR"), "/meli.7.txt.gz")),
    ];
    const MANPAGES_MDOC: [&[u8]; 4] = [
        include_bytes!(concat!(env!("OUT_DIR"), "/meli.mdoc.gz")),
        include_bytes!(concat!(env!("OUT_DIR"), "/meli.conf.mdoc.gz")),
        include_bytes!(concat!(env!("OUT_DIR"), "/meli-themes.mdoc.gz")),
        include_bytes!(concat!(env!("OUT_DIR"), "/meli.7.mdoc.gz")),
    ];

    let mut gz = GzDecoder::new(if source {
        MANPAGES_MDOC[page as usize]
    } else {
        MANPAGES[page as usize]
    });
    let mut v = String::with_capacity(
        str::parse::<usize>(unsafe {
            std::str::from_utf8_unchecked(gz.header().unwrap().comment().unwrap())
        })
        .unwrap_or_else(|_| panic!("{:?} was not compressed with size comment header", page)),
    );
    gz.read_to_string(&mut v)?;

    Ok(v)
}

#[cfg(feature = "cli-docs")]
pub fn pager(v: String, no_raw: Option<Option<bool>>) -> Result<()> {
    if let Some(no_raw) = no_raw {
        match no_raw {
            Some(true) => {}
            None if (unsafe { libc::isatty(libc::STDOUT_FILENO) == 1 }) => {}
            Some(false) | None => {
                println!("{v}");
                return Ok(());
            }
        }
    } else if unsafe { libc::isatty(libc::STDOUT_FILENO) != 1 } {
        println!("{v}");
        return Ok(());
    }

    let mut handle = Command::new("sh")
        .arg("-c")
        .arg(std::env::var("PAGER").unwrap_or_else(|_| "more".to_string()))
        .stdin(Stdio::piped())
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .spawn()?;
    handle.stdin.take().unwrap().write_all(v.as_bytes())?;
    handle.wait()?;

    Ok(())
}

#[cfg(not(feature = "cli-docs"))]
pub fn man(_: ManOpt) -> Result<()> {
    Err(Error::new("error: this version of meli was not build with embedded documentation (cargo feature `cli-docs`). You might have it installed as manpages (eg `man meli`), otherwise check https://meli-email.org"))
}

pub fn compiled_with() -> Result<()> {
    #[cfg(feature = "notmuch")]
    println!("notmuch");
    #[cfg(feature = "jmap")]
    println!("jmap");
    #[cfg(feature = "sqlite3")]
    println!("sqlite3");
    #[cfg(feature = "smtp")]
    println!("smtp");
    #[cfg(feature = "regexp")]
    println!("regexp");
    #[cfg(feature = "dbus-notifications")]
    println!("dbus-notifications");
    #[cfg(feature = "cli-docs")]
    println!("cli-docs");
    #[cfg(feature = "gpgme")]
    println!("gpgme");
    Ok(())
}

pub fn test_config(path: Option<PathBuf>) -> Result<()> {
    let config_path = if let Some(path) = path {
        path
    } else {
        crate::conf::get_config_file()?
    };
    conf::FileSettings::validate(config_path, true, false)?;
    Ok(())
}

pub fn view(
    path: PathBuf,
    sender: Sender<ThreadEvent>,
    receiver: Receiver<ThreadEvent>,
) -> Result<State> {
    if !path.exists() {
        return Err(Error::new(format!(
            "`{}` is not a valid path",
            path.display()
        )));
    } else if !path.is_file() {
        return Err(Error::new(format!("`{}` is a directory", path.display())));
    }
    let bytes = std::fs::read(&path)
        .chain_err_summary(|| format!("Could not read from `{}`", path.display()))?;
    let wrapper = Mail::new(bytes, Some(Flag::SEEN))
        .chain_err_summary(|| format!("Could not parse `{}`", path.display()))?;
    let mut state = State::new(
        Some(Settings::without_accounts().unwrap_or_default()),
        sender,
        receiver,
    )?;
    let main_loop_handler = state.context.main_loop_handler.clone();
    state.register_component(Box::new(EnvelopeView::new(
        wrapper,
        None,
        None,
        None,
        main_loop_handler,
    )));
    Ok(state)
}
