//! Configuration definitions and file loading
//!
//! Alacritty reads from a config file at startup to determine various runtime
//! parameters including font family and style, font size, etc. In the future,
//! the config file will also hold user and platform specific keybindings.
use std::borrow::Cow;
use std::collections::HashMap;
use std::fs::File;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::mpsc;
use std::time::Duration;
use std::{env, fmt};

use font::Size;
use glutin::ModifiersState;
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
use serde::de::Error as SerdeError;
use serde::de::{MapAccess, Unexpected, Visitor};
use serde::{self, de, Deserialize};
use serde_yaml;

use crate::ansi::{Color, CursorStyle, NamedColor};
use crate::cli::Options;
use crate::index::{Column, Line};
use crate::input::{Action, Binding, KeyBinding, MouseBinding};
use crate::term::color::Rgb;

mod bindings;

pub const SOURCE_FILE_PATH: &str = file!();
const MAX_SCROLLBACK_LINES: u32 = 100_000;
static DEFAULT_ALACRITTY_CONFIG: &'static str =
    include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/alacritty.yml"));

#[serde(default)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
pub struct Selection {
    #[serde(deserialize_with = "deserialize_escape_chars")]
    pub semantic_escape_chars: String,
    #[serde(deserialize_with = "failure_default")]
    pub save_to_clipboard: bool,
}

impl Default for Selection {
    fn default() -> Selection {
        Selection {
            semantic_escape_chars: default_escape_chars(),
            save_to_clipboard: Default::default(),
        }
    }
}

fn deserialize_escape_chars<'a, D>(deserializer: D) -> ::std::result::Result<String, D::Error>
where
    D: de::Deserializer<'a>,
{
    match String::deserialize(deserializer) {
        Ok(escape_chars) => Ok(escape_chars),
        Err(err) => {
            error!("Problem with config: {}; using default value", err);
            Ok(default_escape_chars())
        },
    }
}

fn default_escape_chars() -> String {
    String::from(",│`|:\"' ()[]{}<>")
}

#[serde(default)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
pub struct ClickHandler {
    #[serde(deserialize_with = "deserialize_duration_ms")]
    pub threshold: Duration,
}

impl Default for ClickHandler {
    fn default() -> Self {
        ClickHandler { threshold: default_threshold_ms() }
    }
}

fn default_threshold_ms() -> Duration {
    Duration::from_millis(300)
}

fn deserialize_duration_ms<'a, D>(deserializer: D) -> ::std::result::Result<Duration, D::Error>
where
    D: de::Deserializer<'a>,
{
    match u64::deserialize(deserializer) {
        Ok(threshold_ms) => Ok(Duration::from_millis(threshold_ms)),
        Err(err) => {
            error!("Problem with config: {}; using default value", err);
            Ok(default_threshold_ms())
        },
    }
}

#[serde(default)]
#[derive(Default, Clone, Debug, Deserialize, PartialEq, Eq)]
pub struct Mouse {
    #[serde(deserialize_with = "failure_default")]
    pub double_click: ClickHandler,
    #[serde(deserialize_with = "failure_default")]
    pub triple_click: ClickHandler,
    #[serde(deserialize_with = "failure_default")]
    pub hide_when_typing: bool,
    #[serde(deserialize_with = "failure_default")]
    pub url: Url,

    // TODO: DEPRECATED
    pub faux_scrollback_lines: Option<usize>,
}

#[serde(default)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
pub struct Url {
    // Program for opening links
    #[serde(deserialize_with = "deserialize_launcher")]
    pub launcher: Option<CommandWrapper>,

    // Modifier used to open links
    #[serde(deserialize_with = "deserialize_modifiers")]
    pub modifiers: ModifiersState,
}

fn deserialize_launcher<'a, D>(
    deserializer: D,
) -> ::std::result::Result<Option<CommandWrapper>, D::Error>
where
    D: de::Deserializer<'a>,
{
    let default = Url::default().launcher;

    // Deserialize to generic value
    let val = match serde_yaml::Value::deserialize(deserializer) {
        Ok(val) => val,
        Err(err) => {
            error!("Problem with config: {}; using {}", err, default.clone().unwrap().program());
            return Ok(default);
        },
    };

    // Accept `None` to disable the launcher
    if val.as_str().filter(|v| v.to_lowercase() == "none").is_some() {
        return Ok(None);
    }

    match <Option<CommandWrapper>>::deserialize(val) {
        Ok(launcher) => Ok(launcher),
        Err(err) => {
            error!("Problem with config: {}; using {}", err, default.clone().unwrap().program());
            Ok(default)
        },
    }
}

impl Default for Url {
    fn default() -> Url {
        Url {
            #[cfg(not(any(target_os = "macos", windows)))]
            launcher: Some(CommandWrapper::Just(String::from("xdg-open"))),
            #[cfg(target_os = "macos")]
            launcher: Some(CommandWrapper::Just(String::from("open"))),
            #[cfg(windows)]
            launcher: Some(CommandWrapper::Just(String::from("explorer"))),
            modifiers: Default::default(),
        }
    }
}

fn deserialize_modifiers<'a, D>(deserializer: D) -> ::std::result::Result<ModifiersState, D::Error>
where
    D: de::Deserializer<'a>,
{
    ModsWrapper::deserialize(deserializer).map(ModsWrapper::into_inner)
}

/// `VisualBellAnimations` are modeled after a subset of CSS transitions and Robert
/// Penner's Easing Functions.
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
pub enum VisualBellAnimation {
    Ease,         // CSS
    EaseOut,      // CSS
    EaseOutSine,  // Penner
    EaseOutQuad,  // Penner
    EaseOutCubic, // Penner
    EaseOutQuart, // Penner
    EaseOutQuint, // Penner
    EaseOutExpo,  // Penner
    EaseOutCirc,  // Penner
    Linear,
}

impl Default for VisualBellAnimation {
    fn default() -> Self {
        VisualBellAnimation::EaseOutExpo
    }
}

#[serde(default)]
#[derive(Debug, Deserialize, PartialEq, Eq)]
pub struct VisualBellConfig {
    /// Visual bell animation function
    #[serde(deserialize_with = "failure_default")]
    animation: VisualBellAnimation,

    /// Visual bell duration in milliseconds
    #[serde(deserialize_with = "failure_default")]
    duration: u16,

    /// Visual bell flash color
    #[serde(deserialize_with = "rgb_from_hex")]
    color: Rgb,
}

impl Default for VisualBellConfig {
    fn default() -> VisualBellConfig {
        VisualBellConfig {
            animation: Default::default(),
            duration: Default::default(),
            color: default_visual_bell_color(),
        }
    }
}

fn default_visual_bell_color() -> Rgb {
    Rgb { r: 255, g: 255, b: 255 }
}

impl VisualBellConfig {
    /// Visual bell animation
    #[inline]
    pub fn animation(&self) -> VisualBellAnimation {
        self.animation
    }

    /// Visual bell duration in milliseconds
    #[inline]
    pub fn duration(&self) -> Duration {
        Duration::from_millis(u64::from(self.duration))
    }

    /// Visual bell flash color
    #[inline]
    pub fn color(&self) -> Rgb {
        self.color
    }
}

#[derive(Debug, Deserialize, PartialEq, Eq)]
pub struct Shell<'a> {
    program: Cow<'a, str>,

    #[serde(default, deserialize_with = "failure_default")]
    args: Vec<String>,
}

impl<'a> Shell<'a> {
    pub fn new<S>(program: S) -> Shell<'a>
    where
        S: Into<Cow<'a, str>>,
    {
        Shell { program: program.into(), args: Vec::new() }
    }

    pub fn new_with_args<S>(program: S, args: Vec<String>) -> Shell<'a>
    where
        S: Into<Cow<'a, str>>,
    {
        Shell { program: program.into(), args }
    }

    pub fn program(&self) -> &str {
        &*self.program
    }

    pub fn args(&self) -> &[String] {
        self.args.as_slice()
    }
}

/// Wrapper around f32 that represents an alpha value between 0.0 and 1.0
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Alpha(f32);

impl Alpha {
    pub fn new(value: f32) -> Self {
        Alpha(Self::clamp_to_valid_range(value))
    }

    pub fn set(&mut self, value: f32) {
        self.0 = Self::clamp_to_valid_range(value);
    }

    #[inline]
    pub fn get(self) -> f32 {
        self.0
    }

    fn clamp_to_valid_range(value: f32) -> f32 {
        if value < 0.0 {
            0.0
        } else if value > 1.0 {
            1.0
        } else {
            value
        }
    }
}

impl Default for Alpha {
    fn default() -> Self {
        Alpha(1.0)
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Decorations {
    Full,
    Transparent,
    Buttonless,
    None,
}

impl Default for Decorations {
    fn default() -> Decorations {
        Decorations::Full
    }
}

impl<'de> Deserialize<'de> for Decorations {
    fn deserialize<D>(deserializer: D) -> ::std::result::Result<Decorations, D::Error>
    where
        D: de::Deserializer<'de>,
    {
        struct DecorationsVisitor;

        impl<'de> Visitor<'de> for DecorationsVisitor {
            type Value = Decorations;

            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                f.write_str("Some subset of full|transparent|buttonless|none")
            }

            #[cfg(target_os = "macos")]
            fn visit_str<E>(self, value: &str) -> ::std::result::Result<Decorations, E>
            where
                E: de::Error,
            {
                match value.to_lowercase().as_str() {
                    "transparent" => Ok(Decorations::Transparent),
                    "buttonless" => Ok(Decorations::Buttonless),
                    "none" => Ok(Decorations::None),
                    "full" => Ok(Decorations::Full),
                    "true" => {
                        error!(
                            "Deprecated decorations boolean value, use one of \
                             transparent|buttonless|none|full instead; falling back to \"full\""
                        );
                        Ok(Decorations::Full)
                    },
                    "false" => {
                        error!(
                            "Deprecated decorations boolean value, use one of \
                             transparent|buttonless|none|full instead; falling back to \"none\""
                        );
                        Ok(Decorations::None)
                    },
                    _ => {
                        error!("Invalid decorations value: {}; using default value", value);
                        Ok(Decorations::Full)
                    },
                }
            }

            #[cfg(not(target_os = "macos"))]
            fn visit_str<E>(self, value: &str) -> ::std::result::Result<Decorations, E>
            where
                E: de::Error,
            {
                match value.to_lowercase().as_str() {
                    "none" => Ok(Decorations::None),
                    "full" => Ok(Decorations::Full),
                    "true" => {
                        error!(
                            "Deprecated decorations boolean value, use one of none|full instead; \
                             falling back to \"full\""
                        );
                        Ok(Decorations::Full)
                    },
                    "false" => {
                        error!(
                            "Deprecated decorations boolean value, use one of none|full instead; \
                             falling back to \"none\""
                        );
                        Ok(Decorations::None)
                    },
                    "transparent" | "buttonless" => {
                        error!("macOS-only decorations value: {}; using default value", value);
                        Ok(Decorations::Full)
                    },
                    _ => {
                        error!("Invalid decorations value: {}; using default value", value);
                        Ok(Decorations::Full)
                    },
                }
            }
        }

        deserializer.deserialize_str(DecorationsVisitor)
    }
}

#[serde(default)]
#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Eq)]
pub struct WindowConfig {
    /// Initial dimensions
    #[serde(default, deserialize_with = "failure_default")]
    dimensions: Dimensions,

    /// Initial position
    #[serde(default, deserialize_with = "failure_default")]
    position: Option<Delta<i32>>,

    /// Pixel padding
    #[serde(deserialize_with = "deserialize_padding")]
    padding: Delta<u8>,

    /// Draw the window with title bar / borders
    #[serde(deserialize_with = "failure_default")]
    decorations: Decorations,

    /// Spread out additional padding evenly
    #[serde(deserialize_with = "failure_default")]
    dynamic_padding: bool,

    /// Start maximized
    #[serde(deserialize_with = "failure_default")]
    start_maximized: bool,
}

impl Default for WindowConfig {
    fn default() -> Self {
        WindowConfig {
            dimensions: Default::default(),
            position: Default::default(),
            padding: default_padding(),
            decorations: Default::default(),
            dynamic_padding: Default::default(),
            start_maximized: Default::default(),
        }
    }
}

fn default_padding() -> Delta<u8> {
    Delta { x: 2, y: 2 }
}

fn deserialize_padding<'a, D>(deserializer: D) -> ::std::result::Result<Delta<u8>, D::Error>
where
    D: de::Deserializer<'a>,
{
    match Delta::deserialize(deserializer) {
        Ok(delta) => Ok(delta),
        Err(err) => {
            error!("Problem with config: {}; using default value", err);
            Ok(default_padding())
        },
    }
}

impl WindowConfig {
    pub fn decorations(&self) -> Decorations {
        self.decorations
    }

    pub fn dynamic_padding(&self) -> bool {
        self.dynamic_padding
    }

    pub fn start_maximized(&self) -> bool {
        self.start_maximized
    }
}

/// Top-level config type
#[derive(Debug, PartialEq, Deserialize)]
pub struct Config {
    /// Pixel padding
    #[serde(default, deserialize_with = "failure_default")]
    padding: Option<Delta<u8>>,

    /// TERM env variable
    #[serde(default, deserialize_with = "failure_default")]
    env: HashMap<String, String>,

    /// Font configuration
    #[serde(default, deserialize_with = "failure_default")]
    font: Font,

    /// Should show render timer
    #[serde(default, deserialize_with = "failure_default")]
    render_timer: bool,

    /// Should draw bold text with brighter colors instead of bold font
    #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")]
    draw_bold_text_with_bright_colors: bool,

    #[serde(default, deserialize_with = "failure_default")]
    colors: Colors,

    /// Background opacity from 0.0 to 1.0
    #[serde(default, deserialize_with = "failure_default")]
    background_opacity: Alpha,

    /// Window configuration
    #[serde(default, deserialize_with = "failure_default")]
    window: WindowConfig,

    /// Keybindings
    #[serde(default = "default_key_bindings", deserialize_with = "deserialize_key_bindings")]
    key_bindings: Vec<KeyBinding>,

    /// Bindings for the mouse
    #[serde(default = "default_mouse_bindings", deserialize_with = "deserialize_mouse_bindings")]
    mouse_bindings: Vec<MouseBinding>,

    #[serde(default, deserialize_with = "failure_default")]
    selection: Selection,

    #[serde(default, deserialize_with = "failure_default")]
    mouse: Mouse,

    /// Path to a shell program to run on startup
    #[serde(default, deserialize_with = "failure_default")]
    shell: Option<Shell<'static>>,

    /// Path where config was loaded from
    #[serde(default, deserialize_with = "failure_default")]
    config_path: Option<PathBuf>,

    /// Visual bell configuration
    #[serde(default, deserialize_with = "failure_default")]
    visual_bell: VisualBellConfig,

    /// Use dynamic title
    #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")]
    dynamic_title: bool,

    /// Live config reload
    #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")]
    live_config_reload: bool,

    /// Number of spaces in one tab
    #[serde(default = "default_tabspaces", deserialize_with = "deserialize_tabspaces")]
    tabspaces: usize,

    /// How much scrolling history to keep
    #[serde(default, deserialize_with = "failure_default")]
    scrolling: Scrolling,

    /// Cursor configuration
    #[serde(default, deserialize_with = "failure_default")]
    cursor: Cursor,

    /// Keep the log file after quitting
    #[serde(default, deserialize_with = "failure_default")]
    persistent_logging: bool,

    /// Enable experimental conpty backend instead of using winpty.
    /// Will only take effect on Windows 10 Oct 2018 and later.
    #[cfg(windows)]
    #[serde(default, deserialize_with = "failure_default")]
    enable_experimental_conpty_backend: bool,

    /// Send escape sequences using the alt key.
    #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")]
    alt_send_esc: bool,

    // TODO: DEPRECATED
    custom_cursor_colors: Option<bool>,

    // TODO: DEPRECATED
    hide_cursor_when_typing: Option<bool>,

    // TODO: DEPRECATED
    cursor_style: Option<CursorStyle>,

    // TODO: DEPRECATED
    unfocused_hollow_cursor: Option<bool>,

    // TODO: DEPRECATED
    dimensions: Option<Dimensions>,
}

impl Default for Config {
    fn default() -> Self {
        serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("default config is invalid")
    }
}

fn default_key_bindings() -> Vec<KeyBinding> {
    bindings::default_key_bindings()
}

fn default_mouse_bindings() -> Vec<MouseBinding> {
    bindings::default_mouse_bindings()
}

fn deserialize_key_bindings<'a, D>(
    deserializer: D,
) -> ::std::result::Result<Vec<KeyBinding>, D::Error>
where
    D: de::Deserializer<'a>,
{
    deserialize_bindings(deserializer, bindings::default_key_bindings())
}

fn deserialize_mouse_bindings<'a, D>(
    deserializer: D,
) -> ::std::result::Result<Vec<MouseBinding>, D::Error>
where
    D: de::Deserializer<'a>,
{
    deserialize_bindings(deserializer, bindings::default_mouse_bindings())
}

fn deserialize_bindings<'a, D, T>(
    deserializer: D,
    mut default: Vec<Binding<T>>,
) -> ::std::result::Result<Vec<Binding<T>>, D::Error>
where
    D: de::Deserializer<'a>,
    T: Copy + Eq + std::hash::Hash + std::fmt::Debug,
    Binding<T>: de::Deserialize<'a>,
{
    let mut bindings: Vec<Binding<T>> = failure_default_vec(deserializer)?;

    for binding in bindings.iter() {
        default.retain(|b| !b.triggers_match(binding));
    }

    bindings.extend(default);

    Ok(bindings)
}

fn failure_default_vec<'a, D, T>(deserializer: D) -> ::std::result::Result<Vec<T>, D::Error>
where
    D: de::Deserializer<'a>,
    T: Deserialize<'a>,
{
    // Deserialize as generic vector
    let vec = match Vec::<serde_yaml::Value>::deserialize(deserializer) {
        Ok(vec) => vec,
        Err(err) => {
            error!("Problem with config: {}; using empty vector", err);
            return Ok(Vec::new());
        },
    };

    // Move to lossy vector
    let mut bindings: Vec<T> = Vec::new();
    for value in vec {
        match T::deserialize(value) {
            Ok(binding) => bindings.push(binding),
            Err(err) => {
                error!("Problem with config: {}; skipping value", err);
            },
        }
    }

    Ok(bindings)
}

fn default_tabspaces() -> usize {
    8
}

fn deserialize_tabspaces<'a, D>(deserializer: D) -> ::std::result::Result<usize, D::Error>
where
    D: de::Deserializer<'a>,
{
    match usize::deserialize(deserializer) {
        Ok(value) => Ok(value),
        Err(err) => {
            error!("Problem with config: {}; using 8", err);
            Ok(default_tabspaces())
        },
    }
}

fn deserialize_true_bool<'a, D>(deserializer: D) -> ::std::result::Result<bool, D::Error>
where
    D: de::Deserializer<'a>,
{
    match bool::deserialize(deserializer) {
        Ok(value) => Ok(value),
        Err(err) => {
            error!("Problem with config: {}; using true", err);
            Ok(true)
        },
    }
}

fn default_true_bool() -> bool {
    true
}

fn failure_default<'a, D, T>(deserializer: D) -> ::std::result::Result<T, D::Error>
where
    D: de::Deserializer<'a>,
    T: Deserialize<'a> + Default,
{
    match T::deserialize(deserializer) {
        Ok(value) => Ok(value),
        Err(err) => {
            error!("Problem with config: {}; using default value", err);
            Ok(T::default())
        },
    }
}

/// Struct for scrolling related settings
#[serde(default)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
pub struct Scrolling {
    #[serde(deserialize_with = "deserialize_scrolling_history")]
    pub history: u32,
    #[serde(deserialize_with = "deserialize_scrolling_multiplier")]
    pub multiplier: u8,
    #[serde(deserialize_with = "deserialize_scrolling_multiplier")]
    pub faux_multiplier: u8,
    #[serde(deserialize_with = "failure_default")]
    pub auto_scroll: bool,
}

impl Default for Scrolling {
    fn default() -> Self {
        Self {
            history: default_scrolling_history(),
            multiplier: default_scrolling_multiplier(),
            faux_multiplier: default_scrolling_multiplier(),
            auto_scroll: Default::default(),
        }
    }
}

fn default_scrolling_history() -> u32 {
    10_000
}

// Default for normal and faux scrolling
fn default_scrolling_multiplier() -> u8 {
    3
}

fn deserialize_scrolling_history<'a, D>(deserializer: D) -> ::std::result::Result<u32, D::Error>
where
    D: de::Deserializer<'a>,
{
    match u32::deserialize(deserializer) {
        Ok(lines) => {
            if lines > MAX_SCROLLBACK_LINES {
                error!(
                    "Problem with config: scrollback size is {}, but expected a maximum of {}; \
                     using {1} instead",
                    lines, MAX_SCROLLBACK_LINES,
                );
                Ok(MAX_SCROLLBACK_LINES)
            } else {
                Ok(lines)
            }
        },
        Err(err) => {
            error!("Problem with config: {}; using default value", err);
            Ok(default_scrolling_history())
        },
    }
}

fn deserialize_scrolling_multiplier<'a, D>(deserializer: D) -> ::std::result::Result<u8, D::Error>
where
    D: de::Deserializer<'a>,
{
    match u8::deserialize(deserializer) {
        Ok(lines) => Ok(lines),
        Err(err) => {
            error!("Problem with config: {}; using default value", err);
            Ok(default_scrolling_multiplier())
        },
    }
}

/// Newtype for implementing deserialize on glutin Mods
///
/// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the
/// impl below.
#[derive(Debug, Copy, Clone, Hash, Default, Eq, PartialEq)]
struct ModsWrapper(ModifiersState);

impl ModsWrapper {
    fn into_inner(self) -> ModifiersState {
        self.0
    }
}

impl<'a> de::Deserialize<'a> for ModsWrapper {
    fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
    where
        D: de::Deserializer<'a>,
    {
        struct ModsVisitor;

        impl<'a> Visitor<'a> for ModsVisitor {
            type Value = ModsWrapper;

            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                f.write_str("Some subset of Command|Shift|Super|Alt|Option|Control")
            }

            fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModsWrapper, E>
            where
                E: de::Error,
            {
                let mut res = ModifiersState::default();
                for modifier in value.split('|') {
                    match modifier.trim() {
                        "Command" | "Super" => res.logo = true,
                        "Shift" => res.shift = true,
                        "Alt" | "Option" => res.alt = true,
                        "Control" => res.ctrl = true,
                        "None" => (),
                        _ => error!("Unknown modifier {:?}", modifier),
                    }
                }

                Ok(ModsWrapper(res))
            }
        }

        deserializer.deserialize_str(ModsVisitor)
    }
}

struct ActionWrapper(crate::input::Action);

impl ActionWrapper {
    fn into_inner(self) -> crate::input::Action {
        self.0
    }
}

impl<'a> de::Deserialize<'a> for ActionWrapper {
    fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
    where
        D: de::Deserializer<'a>,
    {
        struct ActionVisitor;

        impl<'a> Visitor<'a> for ActionVisitor {
            type Value = ActionWrapper;

            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                f.write_str(
                    "Paste, Copy, PasteSelection, IncreaseFontSize, DecreaseFontSize, \
                     ResetFontSize, ScrollPageUp, ScrollPageDown, ScrollToTop, ScrollToBottom, \
                     ClearHistory, Hide, ClearLogNotice, SpawnNewInstance, None or Quit",
                )
            }

            fn visit_str<E>(self, value: &str) -> ::std::result::Result<ActionWrapper, E>
            where
                E: de::Error,
            {
                Ok(ActionWrapper(match value {
                    "Paste" => Action::Paste,
                    "Copy" => Action::Copy,
                    "PasteSelection" => Action::PasteSelection,
                    "IncreaseFontSize" => Action::IncreaseFontSize,
                    "DecreaseFontSize" => Action::DecreaseFontSize,
                    "ResetFontSize" => Action::ResetFontSize,
                    "ScrollPageUp" => Action::ScrollPageUp,
                    "ScrollPageDown" => Action::ScrollPageDown,
                    "ScrollToTop" => Action::ScrollToTop,
                    "ScrollToBottom" => Action::ScrollToBottom,
                    "ClearHistory" => Action::ClearHistory,
                    "Hide" => Action::Hide,
                    "Quit" => Action::Quit,
                    "ClearLogNotice" => Action::ClearLogNotice,
                    "SpawnNewInstance" => Action::SpawnNewInstance,
                    "None" => Action::None,
                    _ => return Err(E::invalid_value(Unexpected::Str(value), &self)),
                }))
            }
        }
        deserializer.deserialize_str(ActionVisitor)
    }
}

#[serde(untagged)]
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
pub enum CommandWrapper {
    Just(String),
    WithArgs {
        program: String,
        #[serde(default)]
        args: Vec<String>,
    },
}

impl CommandWrapper {
    pub fn program(&self) -> &str {
        match self {
            CommandWrapper::Just(program) => program,
            CommandWrapper::WithArgs { program, .. } => program,
        }
    }

    pub fn args(&self) -> &[String] {
        match self {
            CommandWrapper::Just(_) => &[],
            CommandWrapper::WithArgs { args, .. } => args,
        }
    }
}

use crate::term::{mode, TermMode};

struct ModeWrapper {
    pub mode: TermMode,
    pub not_mode: TermMode,
}

impl<'a> de::Deserialize<'a> for ModeWrapper {
    fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
    where
        D: de::Deserializer<'a>,
    {
        struct ModeVisitor;

        impl<'a> Visitor<'a> for ModeVisitor {
            type Value = ModeWrapper;

            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                f.write_str("Combination of AppCursor | AppKeypad, possibly with negation (~)")
            }

            fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModeWrapper, E>
            where
                E: de::Error,
            {
                let mut res = ModeWrapper { mode: TermMode::empty(), not_mode: TermMode::empty() };

                for modifier in value.split('|') {
                    match modifier.trim() {
                        "AppCursor" => res.mode |= mode::TermMode::APP_CURSOR,
                        "~AppCursor" => res.not_mode |= mode::TermMode::APP_CURSOR,
                        "AppKeypad" => res.mode |= mode::TermMode::APP_KEYPAD,
                        "~AppKeypad" => res.not_mode |= mode::TermMode::APP_KEYPAD,
                        "~Alt" => res.not_mode |= mode::TermMode::ALT_SCREEN,
                        "Alt" => res.mode |= mode::TermMode::ALT_SCREEN,
                        _ => error!("Unknown mode {:?}", modifier),
                    }
                }

                Ok(res)
            }
        }
        deserializer.deserialize_str(ModeVisitor)
    }
}

struct MouseButton(::glutin::MouseButton);

impl MouseButton {
    fn into_inner(self) -> ::glutin::MouseButton {
        self.0
    }
}

impl<'a> de::Deserialize<'a> for MouseButton {
    fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
    where
        D: de::Deserializer<'a>,
    {
        struct MouseButtonVisitor;

        impl<'a> Visitor<'a> for MouseButtonVisitor {
            type Value = MouseButton;

            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                f.write_str("Left, Right, Middle, or a number")
            }

            fn visit_str<E>(self, value: &str) -> ::std::result::Result<MouseButton, E>
            where
                E: de::Error,
            {
                match value {
                    "Left" => Ok(MouseButton(::glutin::MouseButton::Left)),
                    "Right" => Ok(MouseButton(::glutin::MouseButton::Right)),
                    "Middle" => Ok(MouseButton(::glutin::MouseButton::Middle)),
                    _ => {
                        if let Ok(index) = u8::from_str(value) {
                            Ok(MouseButton(::glutin::MouseButton::Other(index)))
                        } else {
                            Err(E::invalid_value(Unexpected::Str(value), &self))
                        }
                    },
                }
            }
        }

        deserializer.deserialize_str(MouseButtonVisitor)
    }
}

/// Bindings are deserialized into a `RawBinding` before being parsed as a
/// `KeyBinding` or `MouseBinding`.
#[derive(PartialEq, Eq)]
struct RawBinding {
    key: Option<Key>,
    mouse: Option<::glutin::MouseButton>,
    mods: ModifiersState,
    mode: TermMode,
    notmode: TermMode,
    action: Action,
}

impl RawBinding {
    fn into_mouse_binding(self) -> ::std::result::Result<MouseBinding, Self> {
        if let Some(mouse) = self.mouse {
            Ok(Binding {
                trigger: mouse,
                mods: self.mods,
                action: self.action,
                mode: self.mode,
                notmode: self.notmode,
            })
        } else {
            Err(self)
        }
    }

    fn into_key_binding(self) -> ::std::result::Result<KeyBinding, Self> {
        if let Some(key) = self.key {
            Ok(KeyBinding {
                trigger: key,
                mods: self.mods,
                action: self.action,
                mode: self.mode,
                notmode: self.notmode,
            })
        } else {
            Err(self)
        }
    }
}

impl<'a> de::Deserialize<'a> for RawBinding {
    fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
    where
        D: de::Deserializer<'a>,
    {
        enum Field {
            Key,
            Mods,
            Mode,
            Action,
            Chars,
            Mouse,
            Command,
        }

        impl<'a> de::Deserialize<'a> for Field {
            fn deserialize<D>(deserializer: D) -> ::std::result::Result<Field, D::Error>
            where
                D: de::Deserializer<'a>,
            {
                struct FieldVisitor;

                static FIELDS: &'static [&'static str] =
                    &["key", "mods", "mode", "action", "chars", "mouse", "command"];

                impl<'a> Visitor<'a> for FieldVisitor {
                    type Value = Field;

                    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                        f.write_str("binding fields")
                    }

                    fn visit_str<E>(self, value: &str) -> ::std::result::Result<Field, E>
                    where
                        E: de::Error,
                    {
                        match value {
                            "key" => Ok(Field::Key),
                            "mods" => Ok(Field::Mods),
                            "mode" => Ok(Field::Mode),
                            "action" => Ok(Field::Action),
                            "chars" => Ok(Field::Chars),
                            "mouse" => Ok(Field::Mouse),
                            "command" => Ok(Field::Command),
                            _ => Err(E::unknown_field(value, FIELDS)),
                        }
                    }
                }

                deserializer.deserialize_str(FieldVisitor)
            }
        }

        struct RawBindingVisitor;
        impl<'a> Visitor<'a> for RawBindingVisitor {
            type Value = RawBinding;

            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                f.write_str("binding specification")
            }

            fn visit_map<V>(self, mut map: V) -> ::std::result::Result<RawBinding, V::Error>
            where
                V: MapAccess<'a>,
            {
                let mut mods: Option<ModifiersState> = None;
                let mut key: Option<Key> = None;
                let mut chars: Option<String> = None;
                let mut action: Option<crate::input::Action> = None;
                let mut mode: Option<TermMode> = None;
                let mut not_mode: Option<TermMode> = None;
                let mut mouse: Option<::glutin::MouseButton> = None;
                let mut command: Option<CommandWrapper> = None;

                use ::serde::de::Error;

                while let Some(struct_key) = map.next_key::<Field>()? {
                    match struct_key {
                        Field::Key => {
                            if key.is_some() {
                                return Err(<V::Error as Error>::duplicate_field("key"));
                            }

                            let val = map.next_value::<serde_yaml::Value>()?;
                            if val.is_u64() {
                                let scancode = val.as_u64().unwrap();
                                if scancode > u64::from(::std::u32::MAX) {
                                    return Err(<V::Error as Error>::custom(format!(
                                        "Invalid key binding, scancode too big: {}",
                                        scancode
                                    )));
                                }
                                key = Some(Key::Scancode(scancode as u32));
                            } else {
                                let k = Key::deserialize(val).map_err(V::Error::custom)?;
                                key = Some(k);
                            }
                        },
                        Field::Mods => {
                            if mods.is_some() {
                                return Err(<V::Error as Error>::duplicate_field("mods"));
                            }

                            mods = Some(map.next_value::<ModsWrapper>()?.into_inner());
                        },
                        Field::Mode => {
                            if mode.is_some() {
                                return Err(<V::Error as Error>::duplicate_field("mode"));
                            }

                            let mode_deserializer = map.next_value::<ModeWrapper>()?;
                            mode = Some(mode_deserializer.mode);
                            not_mode = Some(mode_deserializer.not_mode);
                        },
                        Field::Action => {
                            if action.is_some() {
                                return Err(<V::Error as Error>::duplicate_field("action"));
                            }

                            action = Some(map.next_value::<ActionWrapper>()?.into_inner());
                        },
                        Field::Chars => {
                            if chars.is_some() {
                                return Err(<V::Error as Error>::duplicate_field("chars"));
                            }

                            chars = Some(map.next_value()?);
                        },
                        Field::Mouse => {
                            if chars.is_some() {
                                return Err(<V::Error as Error>::duplicate_field("mouse"));
                            }

                            mouse = Some(map.next_value::<MouseButton>()?.into_inner());
                        },
                        Field::Command => {
                            if command.is_some() {
                                return Err(<V::Error as Error>::duplicate_field("command"));
                            }

                            command = Some(map.next_value::<CommandWrapper>()?);
                        },
                    }
                }

                let action = match (action, chars, command) {
                    (Some(action), None, None) => action,
                    (None, Some(chars), None) => Action::Esc(chars),
                    (None, None, Some(cmd)) => match cmd {
                        CommandWrapper::Just(program) => Action::Command(program, vec![]),
                        CommandWrapper::WithArgs { program, args } => {
                            Action::Command(program, args)
                        },
                    },
                    (None, None, None) => {
                        return Err(V::Error::custom("must specify chars, action or command"));
                    },
                    _ => {
                        return Err(V::Error::custom("must specify only chars, action or command"))
                    },
                };

                let mode = mode.unwrap_or_else(TermMode::empty);
                let not_mode = not_mode.unwrap_or_else(TermMode::empty);
                let mods = mods.unwrap_or_else(ModifiersState::default);

                if mouse.is_none() && key.is_none() {
                    return Err(V::Error::custom("bindings require mouse button or key"));
                }

                Ok(RawBinding { mode, notmode: not_mode, action, key, mouse, mods })
            }
        }

        const FIELDS: &[&str] = &["key", "mods", "mode", "action", "chars", "mouse", "command"];

        deserializer.deserialize_struct("RawBinding", FIELDS, RawBindingVisitor)
    }
}

impl<'a> de::Deserialize<'a> for Alpha {
    fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
    where
        D: de::Deserializer<'a>,
    {
        let value = f32::deserialize(deserializer)?;
        Ok(Alpha::new(value))
    }
}

impl<'a> de::Deserialize<'a> for MouseBinding {
    fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
    where
        D: de::Deserializer<'a>,
    {
        let raw = RawBinding::deserialize(deserializer)?;
        raw.into_mouse_binding().map_err(|_| D::Error::custom("expected mouse binding"))
    }
}

impl<'a> de::Deserialize<'a> for KeyBinding {
    fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
    where
        D: de::Deserializer<'a>,
    {
        let raw = RawBinding::deserialize(deserializer)?;
        raw.into_key_binding().map_err(|_| D::Error::custom("expected key binding"))
    }
}

/// Errors occurring during config loading
#[derive(Debug)]
pub enum Error {
    /// Config file not found
    NotFound,

    /// Config file empty
    Empty,

    /// Couldn't read $HOME environment variable
    ReadingEnvHome(env::VarError),

    /// io error reading file
    Io(io::Error),

    /// Not valid yaml or missing parameters
    Yaml(serde_yaml::Error),
}

#[serde(default)]
#[derive(Debug, Deserialize, PartialEq, Eq)]
pub struct Colors {
    #[serde(deserialize_with = "failure_default")]
    pub primary: PrimaryColors,
    #[serde(deserialize_with = "failure_default")]
    pub cursor: CursorColors,
    #[serde(deserialize_with = "failure_default")]
    pub selection: SelectionColors,
    #[serde(deserialize_with = "deserialize_normal_colors")]
    pub normal: AnsiColors,
    #[serde(deserialize_with = "deserialize_bright_colors")]
    pub bright: AnsiColors,
    #[serde(deserialize_with = "failure_default")]
    pub dim: Option<AnsiColors>,
    #[serde(deserialize_with = "failure_default_vec")]
    pub indexed_colors: Vec<IndexedColor>,
}

impl Default for Colors {
    fn default() -> Colors {
        Colors {
            primary: Default::default(),
            cursor: Default::default(),
            selection: Default::default(),
            normal: default_normal_colors(),
            bright: default_bright_colors(),
            dim: Default::default(),
            indexed_colors: Default::default(),
        }
    }
}

fn default_normal_colors() -> AnsiColors {
    AnsiColors {
        black: Rgb { r: 0x00, g: 0x00, b: 0x00 },
        red: Rgb { r: 0xd5, g: 0x4e, b: 0x53 },
        green: Rgb { r: 0xb9, g: 0xca, b: 0x4a },
        yellow: Rgb { r: 0xe6, g: 0xc5, b: 0x47 },
        blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda },
        magenta: Rgb { r: 0xc3, g: 0x97, b: 0xd8 },
        cyan: Rgb { r: 0x70, g: 0xc0, b: 0xba },
        white: Rgb { r: 0xea, g: 0xea, b: 0xea },
    }
}

fn default_bright_colors() -> AnsiColors {
    AnsiColors {
        black: Rgb { r: 0x66, g: 0x66, b: 0x66 },
        red: Rgb { r: 0xff, g: 0x33, b: 0x34 },
        green: Rgb { r: 0x9e, g: 0xc4, b: 0x00 },
        yellow: Rgb { r: 0xe7, g: 0xc5, b: 0x47 },
        blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda },
        magenta: Rgb { r: 0xb7, g: 0x7e, b: 0xe0 },
        cyan: Rgb { r: 0x54, g: 0xce, b: 0xd6 },
        white: Rgb { r: 0xff, g: 0xff, b: 0xff },
    }
}

fn deserialize_normal_colors<'a, D>(deserializer: D) -> ::std::result::Result<AnsiColors, D::Error>
where
    D: de::Deserializer<'a>,
{
    match AnsiColors::deserialize(deserializer) {
        Ok(escape_chars) => Ok(escape_chars),
        Err(err) => {
            error!("Problem with config: {}; using default value", err);
            Ok(default_normal_colors())
        },
    }
}

fn deserialize_bright_colors<'a, D>(deserializer: D) -> ::std::result::Result<AnsiColors, D::Error>
where
    D: de::Deserializer<'a>,
{
    match AnsiColors::deserialize(deserializer) {
        Ok(escape_chars) => Ok(escape_chars),
        Err(err) => {
            error!("Problem with config: {}; using default value", err);
            Ok(default_bright_colors())
        },
    }
}

#[derive(Debug, Deserialize, PartialEq, Eq)]
pub struct IndexedColor {
    #[serde(deserialize_with = "deserialize_color_index")]
    pub index: u8,
    #[serde(deserialize_with = "rgb_from_hex")]
    pub color: Rgb,
}

fn deserialize_color_index<'a, D>(deserializer: D) -> ::std::result::Result<u8, D::Error>
where
    D: de::Deserializer<'a>,
{
    match u8::deserialize(deserializer) {
        Ok(index) => {
            if index < 16 {
                error!(
                    "Problem with config: indexed_color's index is {}, but a value bigger than 15 \
                     was expected; ignoring setting",
                    index
                );

                // Return value out of range to ignore this color
                Ok(0)
            } else {
                Ok(index)
            }
        },
        Err(err) => {
            error!("Problem with config: {}; ignoring setting", err);

            // Return value out of range to ignore this color
            Ok(0)
        },
    }
}

#[serde(default)]
#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq)]
pub struct Cursor {
    #[serde(deserialize_with = "failure_default")]
    pub style: CursorStyle,
    #[serde(deserialize_with = "deserialize_true_bool")]
    pub unfocused_hollow: bool,
}

impl Default for Cursor {
    fn default() -> Self {
        Self { style: Default::default(), unfocused_hollow: true }
    }
}

#[serde(default)]
#[derive(Debug, Copy, Clone, Default, Deserialize, PartialEq, Eq)]
pub struct CursorColors {
    #[serde(deserialize_with = "deserialize_optional_color")]
    pub text: Option<Rgb>,
    #[serde(deserialize_with = "deserialize_optional_color")]
    pub cursor: Option<Rgb>,
}

#[serde(default)]
#[derive(Debug, Copy, Clone, Default, Deserialize, PartialEq, Eq)]
pub struct SelectionColors {
    #[serde(deserialize_with = "deserialize_optional_color")]
    pub text: Option<Rgb>,
    #[serde(deserialize_with = "deserialize_optional_color")]
    pub background: Option<Rgb>,
}

#[serde(default)]
#[derive(Debug, Deserialize, PartialEq, Eq)]
pub struct PrimaryColors {
    #[serde(deserialize_with = "rgb_from_hex")]
    pub background: Rgb,
    #[serde(deserialize_with = "rgb_from_hex")]
    pub foreground: Rgb,
    #[serde(deserialize_with = "deserialize_optional_color")]
    pub bright_foreground: Option<Rgb>,
    #[serde(deserialize_with = "deserialize_optional_color")]
    pub dim_foreground: Option<Rgb>,
}

impl Default for PrimaryColors {
    fn default() -> Self {
        PrimaryColors {
            background: default_background(),
            foreground: default_foreground(),
            bright_foreground: Default::default(),
            dim_foreground: Default::default(),
        }
    }
}

fn deserialize_optional_color<'a, D>(
    deserializer: D,
) -> ::std::result::Result<Option<Rgb>, D::Error>
where
    D: de::Deserializer<'a>,
{
    match Option::deserialize(deserializer) {
        Ok(Some(color)) => {
            let color: serde_yaml::Value = color;
            Ok(Some(rgb_from_hex(color).unwrap()))
        },
        Ok(None) => Ok(None),
        Err(err) => {
            error!("Problem with config: {}; using standard foreground color", err);
            Ok(None)
        },
    }
}

fn default_background() -> Rgb {
    Rgb { r: 0, g: 0, b: 0 }
}

fn default_foreground() -> Rgb {
    Rgb { r: 0xea, g: 0xea, b: 0xea }
}

/// The 8-colors sections of config
#[derive(Debug, Deserialize, PartialEq, Eq)]
pub struct AnsiColors {
    #[serde(deserialize_with = "rgb_from_hex")]
    pub black: Rgb,
    #[serde(deserialize_with = "rgb_from_hex")]
    pub red: Rgb,
    #[serde(deserialize_with = "rgb_from_hex")]
    pub green: Rgb,
    #[serde(deserialize_with = "rgb_from_hex")]
    pub yellow: Rgb,
    #[serde(deserialize_with = "rgb_from_hex")]
    pub blue: Rgb,
    #[serde(deserialize_with = "rgb_from_hex")]
    pub magenta: Rgb,
    #[serde(deserialize_with = "rgb_from_hex")]
    pub cyan: Rgb,
    #[serde(deserialize_with = "rgb_from_hex")]
    pub white: Rgb,
}

/// Deserialize an Rgb from a hex string
///
/// This is *not* the deserialize impl for Rgb since we want a symmetric
/// serialize/deserialize impl for ref tests.
fn rgb_from_hex<'a, D>(deserializer: D) -> ::std::result::Result<Rgb, D::Error>
where
    D: de::Deserializer<'a>,
{
    struct RgbVisitor;

    impl<'a> Visitor<'a> for RgbVisitor {
        type Value = Rgb;

        fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            f.write_str("hex color like 0xff00ff")
        }

        fn visit_str<E>(self, value: &str) -> ::std::result::Result<Rgb, E>
        where
            E: ::serde::de::Error,
        {
            Rgb::from_str(&value[..])
                .map_err(|_| E::custom("failed to parse rgb; expected hex color like 0xff00ff"))
        }
    }

    let rgb = deserializer.deserialize_str(RgbVisitor);

    // Use #ff00ff as fallback color
    match rgb {
        Ok(rgb) => Ok(rgb),
        Err(err) => {
            error!("Problem with config: {}; using color #ff00ff", err);
            Ok(Rgb { r: 255, g: 0, b: 255 })
        },
    }
}

impl FromStr for Rgb {
    type Err = ();

    fn from_str(s: &str) -> ::std::result::Result<Rgb, ()> {
        let mut chars = s.chars();
        let mut rgb = Rgb::default();

        macro_rules! component {
            ($($c:ident),*) => {
                $(
                    match chars.next().and_then(|c| c.to_digit(16)) {
                        Some(val) => rgb.$c = (val as u8) << 4,
                        None => return Err(())
                    }

                    match chars.next().and_then(|c| c.to_digit(16)) {
                        Some(val) => rgb.$c |= val as u8,
                        None => return Err(())
                    }
                )*
            }
        }

        match chars.next() {
            Some('0') => {
                if chars.next() != Some('x') {
                    return Err(());
                }
            },
            Some('#') => (),
            _ => return Err(()),
        }

        component!(r, g, b);

        Ok(rgb)
    }
}

impl ::std::error::Error for Error {
    fn cause(&self) -> Option<&dyn (::std::error::Error)> {
        match *self {
            Error::NotFound | Error::Empty => None,
            Error::ReadingEnvHome(ref err) => Some(err),
            Error::Io(ref err) => Some(err),
            Error::Yaml(ref err) => Some(err),
        }
    }

    fn description(&self) -> &str {
        match *self {
            Error::NotFound => "Couldn't locate config file",
            Error::Empty => "Empty config file",
            Error::ReadingEnvHome(ref err) => err.description(),
            Error::Io(ref err) => err.description(),
            Error::Yaml(ref err) => err.description(),
        }
    }
}

impl ::std::fmt::Display for Error {
    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
        match *self {
            Error::NotFound | Error::Empty => {
                write!(f, "{}", ::std::error::Error::description(self))
            },
            Error::ReadingEnvHome(ref err) => {
                write!(f, "Couldn't read $HOME environment variable: {}", err)
            },
            Error::Io(ref err) => write!(f, "Error reading config file: {}", err),
            Error::Yaml(ref err) => write!(f, "Problem with config: {}", err),
        }
    }
}

impl From<env::VarError> for Error {
    fn from(val: env::VarError) -> Error {
        Error::ReadingEnvHome(val)
    }
}

impl From<io::Error> for Error {
    fn from(val: io::Error) -> Error {
        if val.kind() == io::ErrorKind::NotFound {
            Error::NotFound
        } else {
            Error::Io(val)
        }
    }
}

impl From<serde_yaml::Error> for Error {
    fn from(val: serde_yaml::Error) -> Error {
        Error::Yaml(val)
    }
}

/// Result from config loading
pub type Result<T> = ::std::result::Result<T, Error>;

impl Config {
    /// Get the location of the first found default config file paths
    /// according to the following order:
    ///
    /// 1. $XDG_CONFIG_HOME/alacritty/alacritty.yml
    /// 2. $XDG_CONFIG_HOME/alacritty.yml
    /// 3. $HOME/.config/alacritty/alacritty.yml
    /// 4. $HOME/.alacritty.yml
    #[cfg(not(windows))]
    pub fn installed_config<'a>() -> Option<Cow<'a, Path>> {
        // Try using XDG location by default
        ::xdg::BaseDirectories::with_prefix("alacritty")
            .ok()
            .and_then(|xdg| xdg.find_config_file("alacritty.yml"))
            .or_else(|| {
                ::xdg::BaseDirectories::new()
                    .ok()
                    .and_then(|fallback| fallback.find_config_file("alacritty.yml"))
            })
            .or_else(|| {
                if let Ok(home) = env::var("HOME") {
                    // Fallback path: $HOME/.config/alacritty/alacritty.yml
                    let fallback = PathBuf::from(&home).join(".config/alacritty/alacritty.yml");
                    if fallback.exists() {
                        return Some(fallback);
                    }
                    // Fallback path: $HOME/.alacritty.yml
                    let fallback = PathBuf::from(&home).join(".alacritty.yml");
                    if fallback.exists() {
                        return Some(fallback);
                    }
                }
                None
            })
            .map(Into::into)
    }

    // TODO: Remove old configuration location warning (Deprecated 03/12/2018)
    #[cfg(windows)]
    pub fn installed_config<'a>() -> Option<Cow<'a, Path>> {
        let old = dirs::home_dir().map(|path| path.join("alacritty.yml"));
        let new = dirs::config_dir().map(|path| path.join("alacritty\\alacritty.yml"));

        if let Some(old_path) = old.as_ref().filter(|old| old.exists()) {
            warn!(
                "Found configuration at: {}; this file should be moved to the new location: {}",
                old_path.to_string_lossy(),
                new.as_ref().map(|new| new.to_string_lossy()).unwrap(),
            );

            old.map(Cow::from)
        } else {
            new.filter(|new| new.exists()).map(Cow::from)
        }
    }

    #[cfg(not(windows))]
    pub fn write_defaults() -> io::Result<Cow<'static, Path>> {
        let path = xdg::BaseDirectories::with_prefix("alacritty")
            .map_err(|err| io::Error::new(io::ErrorKind::NotFound, err.to_string().as_str()))
            .and_then(|p| p.place_config_file("alacritty.yml"))?;

        File::create(&path)?.write_all(DEFAULT_ALACRITTY_CONFIG.as_bytes())?;

        Ok(path.into())
    }

    #[cfg(windows)]
    pub fn write_defaults() -> io::Result<Cow<'static, Path>> {
        let mut path = dirs::config_dir().ok_or_else(|| {
            io::Error::new(io::ErrorKind::NotFound, "Couldn't find profile directory")
        })?;

        path = path.join("alacritty/alacritty.yml");

        std::fs::create_dir_all(path.parent().unwrap())?;

        File::create(&path)?.write_all(DEFAULT_ALACRITTY_CONFIG.as_bytes())?;

        Ok(path.into())
    }

    /// Get list of colors
    ///
    /// The ordering returned here is expected by the terminal. Colors are simply indexed in this
    /// array for performance.
    pub fn colors(&self) -> &Colors {
        &self.colors
    }

    #[inline]
    pub fn background_opacity(&self) -> Alpha {
        self.background_opacity
    }

    pub fn key_bindings(&self) -> &[KeyBinding] {
        &self.key_bindings[..]
    }

    pub fn mouse_bindings(&self) -> &[MouseBinding] {
        &self.mouse_bindings[..]
    }

    pub fn mouse(&self) -> &Mouse {
        &self.mouse
    }

    pub fn selection(&self) -> &Selection {
        &self.selection
    }

    pub fn tabspaces(&self) -> usize {
        self.tabspaces
    }

    pub fn padding(&self) -> &Delta<u8> {
        self.padding.as_ref().unwrap_or(&self.window.padding)
    }

    #[inline]
    pub fn draw_bold_text_with_bright_colors(&self) -> bool {
        self.draw_bold_text_with_bright_colors
    }

    /// Get font config
    #[inline]
    pub fn font(&self) -> &Font {
        &self.font
    }

    /// Get window dimensions
    #[inline]
    pub fn dimensions(&self) -> Dimensions {
        self.dimensions.unwrap_or(self.window.dimensions)
    }

    #[inline]
    pub fn position(&self) -> Option<Delta<i32>> {
        self.window.position
    }

    /// Get window config
    #[inline]
    pub fn window(&self) -> &WindowConfig {
        &self.window
    }

    /// Get visual bell config
    #[inline]
    pub fn visual_bell(&self) -> &VisualBellConfig {
        &self.visual_bell
    }

    /// Should show render timer
    #[inline]
    pub fn render_timer(&self) -> bool {
        self.render_timer
    }

    #[cfg(target_os = "macos")]
    #[inline]
    pub fn use_thin_strokes(&self) -> bool {
        self.font.use_thin_strokes
    }

    #[cfg(not(target_os = "macos"))]
    #[inline]
    pub fn use_thin_strokes(&self) -> bool {
        false
    }

    pub fn path(&self) -> Option<&Path> {
        self.config_path.as_ref().map(PathBuf::as_path)
    }

    pub fn shell(&self) -> Option<&Shell<'_>> {
        self.shell.as_ref()
    }

    pub fn env(&self) -> &HashMap<String, String> {
        &self.env
    }

    /// Should hide mouse cursor when typing
    #[inline]
    pub fn hide_mouse_when_typing(&self) -> bool {
        self.hide_cursor_when_typing.unwrap_or(self.mouse.hide_when_typing)
    }

    /// Style of the cursor
    #[inline]
    pub fn cursor_style(&self) -> CursorStyle {
        self.cursor_style.unwrap_or(self.cursor.style)
    }

    /// Use hollow block cursor when unfocused
    #[inline]
    pub fn unfocused_hollow_cursor(&self) -> bool {
        self.unfocused_hollow_cursor.unwrap_or(self.cursor.unfocused_hollow)
    }

    /// Live config reload
    #[inline]
    pub fn live_config_reload(&self) -> bool {
        self.live_config_reload
    }

    #[inline]
    pub fn dynamic_title(&self) -> bool {
        self.dynamic_title
    }

    /// Scrolling settings
    #[inline]
    pub fn scrolling(&self) -> Scrolling {
        self.scrolling
    }

    /// Cursor foreground color
    #[inline]
    pub fn cursor_text_color(&self) -> Option<Color> {
        self.colors.cursor.text.map(|_| Color::Named(NamedColor::CursorText))
    }

    /// Cursor background color
    #[inline]
    pub fn cursor_cursor_color(&self) -> Option<Color> {
        self.colors.cursor.cursor.map(|_| Color::Named(NamedColor::Cursor))
    }

    /// Enable experimental conpty backend (Windows only)
    #[cfg(windows)]
    #[inline]
    pub fn enable_experimental_conpty_backend(&self) -> bool {
        self.enable_experimental_conpty_backend
    }

    /// Send escape sequences using the alt key
    #[inline]
    pub fn alt_send_esc(&self) -> bool {
        self.alt_send_esc
    }

    // Update the history size, used in ref tests
    pub fn set_history(&mut self, history: u32) {
        self.scrolling.history = history;
    }

    /// Keep the log file after quitting Alacritty
    #[inline]
    pub fn persistent_logging(&self) -> bool {
        self.persistent_logging
    }

    /// Overrides the `dynamic_title` configuration based on `--title`.
    pub fn update_dynamic_title(mut self, options: &Options) -> Self {
        if options.title.is_some() {
            self.dynamic_title = false;
        }
        self
    }

    pub fn load_from(path: PathBuf) -> Config {
        let mut config = Config::reload_from(&path).unwrap_or_else(|_| Config::default());
        config.config_path = Some(path);
        config
    }

    pub fn reload_from(path: &PathBuf) -> Result<Config> {
        match Config::read_config(path) {
            Ok(config) => Ok(config),
            Err(err) => {
                error!("Unable to load config {:?}: {}", path, err);
                Err(err)
            },
        }
    }

    fn read_config(path: &PathBuf) -> Result<Config> {
        let mut contents = String::new();
        File::open(path)?.read_to_string(&mut contents)?;

        // Prevent parsing error with empty string
        if contents.is_empty() {
            return Ok(Config::default());
        }

        let mut config: Config = serde_yaml::from_str(&contents)?;
        config.print_deprecation_warnings();

        Ok(config)
    }

    fn print_deprecation_warnings(&mut self) {
        if self.dimensions.is_some() {
            warn!("Config dimensions is deprecated; please use window.dimensions instead");
        }

        if self.padding.is_some() {
            warn!("Config padding is deprecated; please use window.padding instead");
        }

        if self.mouse.faux_scrollback_lines.is_some() {
            warn!(
                "Config mouse.faux_scrollback_lines is deprecated; please use \
                 mouse.faux_scrolling_lines instead"
            );
        }

        if let Some(custom_cursor_colors) = self.custom_cursor_colors {
            warn!("Config custom_cursor_colors is deprecated");

            if !custom_cursor_colors {
                self.colors.cursor.cursor = None;
                self.colors.cursor.text = None;
            }
        }

        if self.cursor_style.is_some() {
            warn!("Config cursor_style is deprecated; please use cursor.style instead");
        }

        if self.hide_cursor_when_typing.is_some() {
            warn!(
                "Config hide_cursor_when_typing is deprecated; please use mouse.hide_when_typing \
                 instead"
            );
        }

        if self.unfocused_hollow_cursor.is_some() {
            warn!(
                "Config unfocused_hollow_cursor is deprecated; please use cursor.unfocused_hollow \
                 instead"
            );
        }
    }
}

/// Window Dimensions
///
/// Newtype to avoid passing values incorrectly
#[serde(default)]
#[derive(Default, Debug, Copy, Clone, Deserialize, PartialEq, Eq)]
pub struct Dimensions {
    /// Window width in character columns
    #[serde(deserialize_with = "failure_default")]
    columns: Column,

    /// Window Height in character lines
    #[serde(deserialize_with = "failure_default")]
    lines: Line,
}

impl Dimensions {
    pub fn new(columns: Column, lines: Line) -> Self {
        Dimensions { columns, lines }
    }

    /// Get lines
    #[inline]
    pub fn lines_u32(&self) -> u32 {
        self.lines.0 as u32
    }

    /// Get columns
    #[inline]
    pub fn columns_u32(&self) -> u32 {
        self.columns.0 as u32
    }
}

/// A delta for a point in a 2 dimensional plane
#[serde(default, bound(deserialize = "T: Deserialize<'de> + Default"))]
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
pub struct Delta<T: Default + PartialEq + Eq> {
    /// Horizontal change
    #[serde(deserialize_with = "failure_default")]
    pub x: T,
    /// Vertical change
    #[serde(deserialize_with = "failure_default")]
    pub y: T,
}

trait DeserializeSize: Sized {
    fn deserialize<'a, D>(_: D) -> ::std::result::Result<Self, D::Error>
    where
        D: serde::de::Deserializer<'a>;
}

impl DeserializeSize for Size {
    fn deserialize<'a, D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
    where
        D: serde::de::Deserializer<'a>,
    {
        use std::marker::PhantomData;

        struct NumVisitor<__D> {
            _marker: PhantomData<__D>,
        }

        impl<'a, __D> Visitor<'a> for NumVisitor<__D>
        where
            __D: serde::de::Deserializer<'a>,
        {
            type Value = f64;

            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                f.write_str("f64 or u64")
            }

            fn visit_f64<E>(self, value: f64) -> ::std::result::Result<Self::Value, E>
            where
                E: ::serde::de::Error,
            {
                Ok(value)
            }

            fn visit_u64<E>(self, value: u64) -> ::std::result::Result<Self::Value, E>
            where
                E: ::serde::de::Error,
            {
                Ok(value as f64)
            }
        }

        let size = deserializer
            .deserialize_any(NumVisitor::<D> { _marker: PhantomData })
            .map(|v| Size::new(v as _));

        // Use default font size as fallback
        match size {
            Ok(size) => Ok(size),
            Err(err) => {
                let size = default_font_size();
                error!("Problem with config: {}; using size {}", err, size.as_f32_pts());
                Ok(size)
            },
        }
    }
}

/// Font config
///
/// Defaults are provided at the level of this struct per platform, but not per
/// field in this struct. It might be nice in the future to have defaults for
/// each value independently. Alternatively, maybe erroring when the user
/// doesn't provide complete config is Ok.
#[serde(default)]
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
pub struct Font {
    /// Normal font face
    #[serde(deserialize_with = "failure_default")]
    normal: FontDescription,

    /// Bold font face
    #[serde(deserialize_with = "failure_default")]
    italic: SecondaryFontDescription,

    /// Italic font face
    #[serde(deserialize_with = "failure_default")]
    bold: SecondaryFontDescription,

    /// Font size in points
    #[serde(deserialize_with = "DeserializeSize::deserialize")]
    pub size: Size,

    /// Extra spacing per character
    #[serde(deserialize_with = "failure_default")]
    offset: Delta<i8>,

    /// Glyph offset within character cell
    #[serde(deserialize_with = "failure_default")]
    glyph_offset: Delta<i8>,

    #[cfg(target_os = "macos")]
    #[serde(deserialize_with = "deserialize_true_bool")]
    use_thin_strokes: bool,

    // TODO: Deprecated
    #[serde(deserialize_with = "deserialize_scale_with_dpi")]
    scale_with_dpi: Option<()>,
}

impl Default for Font {
    fn default() -> Font {
        Font {
            #[cfg(target_os = "macos")]
            use_thin_strokes: true,
            size: default_font_size(),
            normal: Default::default(),
            bold: Default::default(),
            italic: Default::default(),
            scale_with_dpi: Default::default(),
            glyph_offset: Default::default(),
            offset: Default::default(),
        }
    }
}

impl Font {
    /// Get the font size in points
    #[inline]
    pub fn size(&self) -> Size {
        self.size
    }

    /// Get offsets to font metrics
    #[inline]
    pub fn offset(&self) -> &Delta<i8> {
        &self.offset
    }

    /// Get cell offsets for glyphs
    #[inline]
    pub fn glyph_offset(&self) -> &Delta<i8> {
        &self.glyph_offset
    }

    /// Get a font clone with a size modification
    pub fn with_size(self, size: Size) -> Font {
        Font { size, ..self }
    }

    // Get normal font description
    pub fn normal(&self) -> &FontDescription {
        &self.normal
    }

    // Get italic font description
    pub fn italic(&self) -> FontDescription {
        self.italic.desc(&self.normal)
    }

    // Get bold font description
    pub fn bold(&self) -> FontDescription {
        self.bold.desc(&self.normal)
    }
}

fn default_font_size() -> Size {
    Size::new(11.)
}

fn deserialize_scale_with_dpi<'a, D>(deserializer: D) -> ::std::result::Result<Option<()>, D::Error>
where
    D: de::Deserializer<'a>,
{
    // This is necessary in order to get serde to complete deserialization of the configuration
    let _ignored = bool::deserialize(deserializer);
    error!(
        "The scale_with_dpi setting has been removed, on X11 the WINIT_HIDPI_FACTOR environment \
         variable can be used instead."
    );
    Ok(None)
}

/// Description of the normal font
#[serde(default)]
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
pub struct FontDescription {
    #[serde(deserialize_with = "failure_default")]
    pub family: String,
    #[serde(deserialize_with = "failure_default")]
    pub style: Option<String>,
}

impl Default for FontDescription {
    fn default() -> FontDescription {
        FontDescription {
            #[cfg(not(any(target_os = "macos", windows)))]
            family: "monospace".into(),
            #[cfg(target_os = "macos")]
            family: "Menlo".into(),
            #[cfg(windows)]
            family: "Consolas".into(),
            style: None,
        }
    }
}

/// Description of the italic and bold font
#[serde(default)]
#[derive(Debug, Default, Deserialize, Clone, PartialEq, Eq)]
pub struct SecondaryFontDescription {
    #[serde(deserialize_with = "failure_default")]
    family: Option<String>,
    #[serde(deserialize_with = "failure_default")]
    style: Option<String>,
}

impl SecondaryFontDescription {
    pub fn desc(&self, fallback: &FontDescription) -> FontDescription {
        FontDescription {
            family: self.family.clone().unwrap_or_else(|| fallback.family.clone()),
            style: self.style.clone(),
        }
    }
}

pub struct Monitor {
    _thread: ::std::thread::JoinHandle<()>,
    rx: mpsc::Receiver<PathBuf>,
}

pub trait OnConfigReload {
    fn on_config_reload(&mut self);
}

impl OnConfigReload for crate::display::Notifier {
    fn on_config_reload(&mut self) {
        self.notify();
    }
}

impl Monitor {
    /// Get pending config changes
    pub fn pending(&self) -> Option<PathBuf> {
        let mut config = None;
        while let Ok(new) = self.rx.try_recv() {
            config = Some(new);
        }

        config
    }

    pub fn new<H, P>(path: P, mut handler: H) -> Monitor
    where
        H: OnConfigReload + Send + 'static,
        P: Into<PathBuf>,
    {
        let path = path.into();

        let (config_tx, config_rx) = mpsc::channel();

        Monitor {
            _thread: crate::util::thread::spawn_named("config watcher", move || {
                let (tx, rx) = mpsc::channel();
                // The Duration argument is a debouncing period.
                let mut watcher =
                    watcher(tx, Duration::from_millis(10)).expect("Unable to spawn file watcher");
                let config_path = ::std::fs::canonicalize(path).expect("canonicalize config path");

                // Get directory of config
                let mut parent = config_path.clone();
                parent.pop();

                // Watch directory
                watcher
                    .watch(&parent, RecursiveMode::NonRecursive)
                    .expect("watch alacritty.yml dir");

                loop {
                    match rx.recv().expect("watcher event") {
                        DebouncedEvent::Rename(..) => continue,
                        DebouncedEvent::Write(path)
                        | DebouncedEvent::Create(path)
                        | DebouncedEvent::Chmod(path) => {
                            if path != config_path {
                                continue;
                            }

                            let _ = config_tx.send(path);
                            handler.on_config_reload();
                        },
                        _ => {},
                    }
                }
            }),
            rx: config_rx,
        }
    }
}

#[derive(Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Key {
    Scancode(u32),
    Key1,
    Key2,
    Key3,
    Key4,
    Key5,
    Key6,
    Key7,
    Key8,
    Key9,
    Key0,
    A,
    B,
    C,
    D,
    E,
    F,
    G,
    H,
    I,
    J,
    K,
    L,
    M,
    N,
    O,
    P,
    Q,
    R,
    S,
    T,
    U,
    V,
    W,
    X,
    Y,
    Z,
    Escape,
    F1,
    F2,
    F3,
    F4,
    F5,
    F6,
    F7,
    F8,
    F9,
    F10,
    F11,
    F12,
    F13,
    F14,
    F15,
    F16,
    F17,
    F18,
    F19,
    F20,
    F21,
    F22,
    F23,
    F24,
    Snapshot,
    Scroll,
    Pause,
    Insert,
    Home,
    Delete,
    End,
    PageDown,
    PageUp,
    Left,
    Up,
    Right,
    Down,
    Back,
    Return,
    Space,
    Compose,
    Numlock,
    Numpad0,
    Numpad1,
    Numpad2,
    Numpad3,
    Numpad4,
    Numpad5,
    Numpad6,
    Numpad7,
    Numpad8,
    Numpad9,
    AbntC1,
    AbntC2,
    Add,
    Apostrophe,
    Apps,
    At,
    Ax,
    Backslash,
    Calculator,
    Capital,
    Colon,
    Comma,
    Convert,
    Decimal,
    Divide,
    Equals,
    Grave,
    Kana,
    Kanji,
    LAlt,
    LBracket,
    LControl,
    LShift,
    LWin,
    Mail,
    MediaSelect,
    MediaStop,
    Minus,
    Multiply,
    Mute,
    MyComputer,
    NavigateForward,
    NavigateBackward,
    NextTrack,
    NoConvert,
    NumpadComma,
    NumpadEnter,
    NumpadEquals,
    OEM102,
    Period,
    PlayPause,
    Power,
    PrevTrack,
    RAlt,
    RBracket,
    RControl,
    RShift,
    RWin,
    Semicolon,
    Slash,
    Sleep,
    Stop,
    Subtract,
    Sysrq,
    Tab,
    Underline,
    Unlabeled,
    VolumeDown,
    VolumeUp,
    Wake,
    WebBack,
    WebFavorites,
    WebForward,
    WebHome,
    WebRefresh,
    WebSearch,
    WebStop,
    Yen,
    Caret,
    Copy,
    Paste,
    Cut,
}

impl Key {
    pub fn from_glutin_input(key: ::glutin::VirtualKeyCode) -> Self {
        use glutin::VirtualKeyCode::*;
        // Thank you, vim macros and regex!
        match key {
            Key1 => Key::Key1,
            Key2 => Key::Key2,
            Key3 => Key::Key3,
            Key4 => Key::Key4,
            Key5 => Key::Key5,
            Key6 => Key::Key6,
            Key7 => Key::Key7,
            Key8 => Key::Key8,
            Key9 => Key::Key9,
            Key0 => Key::Key0,
            A => Key::A,
            B => Key::B,
            C => Key::C,
            D => Key::D,
            E => Key::E,
            F => Key::F,
            G => Key::G,
            H => Key::H,
            I => Key::I,
            J => Key::J,
            K => Key::K,
            L => Key::L,
            M => Key::M,
            N => Key::N,
            O => Key::O,
            P => Key::P,
            Q => Key::Q,
            R => Key::R,
            S => Key::S,
            T => Key::T,
            U => Key::U,
            V => Key::V,
            W => Key::W,
            X => Key::X,
            Y => Key::Y,
            Z => Key::Z,
            Escape => Key::Escape,
            F1 => Key::F1,
            F2 => Key::F2,
            F3 => Key::F3,
            F4 => Key::F4,
            F5 => Key::F5,
            F6 => Key::F6,
            F7 => Key::F7,
            F8 => Key::F8,
            F9 => Key::F9,
            F10 => Key::F10,
            F11 => Key::F11,
            F12 => Key::F12,
            F13 => Key::F13,
            F14 => Key::F14,
            F15 => Key::F15,
            F16 => Key::F16,
            F17 => Key::F17,
            F18 => Key::F18,
            F19 => Key::F19,
            F20 => Key::F20,
            F21 => Key::F21,
            F22 => Key::F22,
            F23 => Key::F23,
            F24 => Key::F24,
            Snapshot => Key::Snapshot,
            Scroll => Key::Scroll,
            Pause => Key::Pause,
            Insert => Key::Insert,
            Home => Key::Home,
            Delete => Key::Delete,
            End => Key::End,
            PageDown => Key::PageDown,
            PageUp => Key::PageUp,
            Left => Key::Left,
            Up => Key::Up,
            Right => Key::Right,
            Down => Key::Down,
            Back => Key::Back,
            Return => Key::Return,
            Space => Key::Space,
            Compose => Key::Compose,
            Numlock => Key::Numlock,
            Numpad0 => Key::Numpad0,
            Numpad1 => Key::Numpad1,
            Numpad2 => Key::Numpad2,
            Numpad3 => Key::Numpad3,
            Numpad4 => Key::Numpad4,
            Numpad5 => Key::Numpad5,
            Numpad6 => Key::Numpad6,
            Numpad7 => Key::Numpad7,
            Numpad8 => Key::Numpad8,
            Numpad9 => Key::Numpad9,
            AbntC1 => Key::AbntC1,
            AbntC2 => Key::AbntC2,
            Add => Key::Add,
            Apostrophe => Key::Apostrophe,
            Apps => Key::Apps,
            At => Key::At,
            Ax => Key::Ax,
            Backslash => Key::Backslash,
            Calculator => Key::Calculator,
            Capital => Key::Capital,
            Colon => Key::Colon,
            Comma => Key::Comma,
            Convert => Key::Convert,
            Decimal => Key::Decimal,
            Divide => Key::Divide,
            Equals => Key::Equals,
            Grave => Key::Grave,
            Kana => Key::Kana,
            Kanji => Key::Kanji,
            LAlt => Key::LAlt,
            LBracket => Key::LBracket,
            LControl => Key::LControl,
            LShift => Key::LShift,
            LWin => Key::LWin,
            Mail => Key::Mail,
            MediaSelect => Key::MediaSelect,
            MediaStop => Key::MediaStop,
            Minus => Key::Minus,
            Multiply => Key::Multiply,
            Mute => Key::Mute,
            MyComputer => Key::MyComputer,
            NavigateForward => Key::NavigateForward,
            NavigateBackward => Key::NavigateBackward,
            NextTrack => Key::NextTrack,
            NoConvert => Key::NoConvert,
            NumpadComma => Key::NumpadComma,
            NumpadEnter => Key::NumpadEnter,
            NumpadEquals => Key::NumpadEquals,
            OEM102 => Key::OEM102,
            Period => Key::Period,
            PlayPause => Key::PlayPause,
            Power => Key::Power,
            PrevTrack => Key::PrevTrack,
            RAlt => Key::RAlt,
            RBracket => Key::RBracket,
            RControl => Key::RControl,
            RShift => Key::RShift,
            RWin => Key::RWin,
            Semicolon => Key::Semicolon,
            Slash => Key::Slash,
            Sleep => Key::Sleep,
            Stop => Key::Stop,
            Subtract => Key::Subtract,
            Sysrq => Key::Sysrq,
            Tab => Key::Tab,
            Underline => Key::Underline,
            Unlabeled => Key::Unlabeled,
            VolumeDown => Key::VolumeDown,
            VolumeUp => Key::VolumeUp,
            Wake => Key::Wake,
            WebBack => Key::WebBack,
            WebFavorites => Key::WebFavorites,
            WebForward => Key::WebForward,
            WebHome => Key::WebHome,
            WebRefresh => Key::WebRefresh,
            WebSearch => Key::WebSearch,
            WebStop => Key::WebStop,
            Yen => Key::Yen,
            Caret => Key::Caret,
            Copy => Key::Copy,
            Paste => Key::Paste,
            Cut => Key::Cut,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{Config, DEFAULT_ALACRITTY_CONFIG};
    use crate::cli::Options;

    #[test]
    fn parse_config() {
        let config: Config =
            ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config");

        // Sanity check that mouse bindings are being parsed
        assert!(!config.mouse_bindings.is_empty());

        // Sanity check that key bindings are being parsed
        assert!(!config.key_bindings.is_empty());
    }

    #[test]
    fn dynamic_title_ignoring_options_by_default() {
        let config: Config =
            ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config");
        let old_dynamic_title = config.dynamic_title;
        let options = Options::default();
        let config = config.update_dynamic_title(&options);
        assert_eq!(old_dynamic_title, config.dynamic_title);
    }

    #[test]
    fn dynamic_title_overridden_by_options() {
        let config: Config =
            ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config");
        let mut options = Options::default();
        options.title = Some("foo".to_owned());
        let config = config.update_dynamic_title(&options);
        assert!(!config.dynamic_title);
    }

    #[test]
    fn default_match_empty() {
        let default = Config::default();

        let empty = serde_yaml::from_str("key: val\n").unwrap();

        assert_eq!(default, empty);
    }
}
