extern crate zip;

use super::*;

/// Represents a `.tdesktop-theme` file structure.
#[derive(PartialEq, Clone, Debug, Default)]
pub struct TdesktopTheme {
  /// Holds the theme's wallpaper.
  pub wallpaper: Option<Wallpaper>,
  pub(crate) variables: Variables,
}

impl TdesktopTheme {
  /// Creates a new, empty `TdesktopTheme`.
  ///
  /// # Examples
  ///
  /// ```
  /// use tdesktop_theme::*;
  ///
  /// let mut theme = TdesktopTheme::new();
  ///
  /// assert_eq!(theme.wallpaper, None);
  /// assert_eq!(theme.len(), 0);
  /// ```
  pub fn new() -> TdesktopTheme {
    TdesktopTheme {
      wallpaper: None,
      variables: IndexMap::new(),
    }
  }

  /// Creates an empty theme with capacity for `n` variables.
  ///
  /// You may need this if you're hardcoding a theme, so you only allocate
  /// memory once instead of, for example, 451 times.
  /// [`generate_default_theme`] uses this.
  ///
  /// [`generate_default_theme`]: ./fn.generate_default_theme.html
  pub fn with_capacity(n: usize) -> TdesktopTheme {
    TdesktopTheme {
      wallpaper: None,
      variables: IndexMap::with_capacity(n),
    }
  }

  /// Parsers the bytes and returns the parse result. Parses both
  /// `.tdesktop-palette` and `.tdesktop-theme` files.
  ///
  /// # Examples
  ///
  /// ```
  /// use tdesktop_theme::*;
  ///
  /// let contents = b"windowBg: #ffffff; // this is a comment
  /// /*
  ///   this is a multiline comment
  /// */ windowFg: #000000;
  /// windowBoldFg: windowFg;";
  ///
  /// let theme = TdesktopTheme::from_bytes(contents).unwrap();
  ///
  /// assert_eq!(theme.wallpaper, None);
  /// assert_eq!(
  ///   theme.get_variable("windowBg"),
  ///   Some(&Value::Color([0xff; 4])),
  /// );
  /// assert_eq!(
  ///   theme.get_variable("windowFg"),
  ///   Some(&Value::Color([0x00, 0x00, 0x00, 0xff])),
  /// );
  /// assert_eq!(
  ///   theme.get_variable("windowBoldFg"),
  ///   Some(&Value::Link("windowFg".to_string())),
  /// );
  /// ```
  pub fn from_bytes(bytes: &[u8]) -> Result<TdesktopTheme, ParseError> {
    parse::parse(bytes)
  }

  /// Sets `variable`'s value to `Value::color(color)`.
  ///
  /// # Notes
  ///
  /// If variable's name is invalid, then an error is returned.
  ///
  /// # Examples
  ///
  /// ```
  /// use tdesktop_theme::*;
  ///
  /// let mut theme = TdesktopTheme::new();
  ///
  /// theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();
  /// assert_eq!(
  ///   theme.get_variable("windowBg"),
  ///   Some(&Value::Color([0xff; 4])),
  /// );
  /// ```
  pub fn set_variable(
    &mut self,
    variable: String,
    color: Color,
  ) -> Result<(), String> {
    if !utils::is_variable_name_valid(&variable) {
      return Err(format!("Got invalid variable name: {}", variable));
    }

    self.variables.insert(variable, Value::Color(color));

    Ok(())
  }

  /// Gets the `variable`'s raw value. A raw value means that it may be either
  /// a color or a link to another variable.
  ///
  /// # Example
  ///
  /// ```
  /// use tdesktop_theme::*;
  ///
  /// let mut theme = TdesktopTheme::new();
  /// theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();
  /// theme
  ///   .link_variable("windowActiveBg".to_string(), "windowBg".to_string())
  ///   .unwrap();
  ///
  /// assert_eq!(
  ///   theme.get_variable("windowBg"),
  ///   Some(&Value::Color([0xff; 4])),
  /// );
  ///
  /// assert_eq!(
  ///   theme.get_variable("windowActiveBg"),
  ///   Some(&Value::Link("windowBg".to_string())),
  /// );
  pub fn get_variable(&self, variable: &str) -> Option<&Value> {
    self.variables.get(variable)
  }

  /// Checks that the theme contains `variable` and returns a boolean.
  ///
  /// # Examples
  ///
  /// ```
  /// use tdesktop_theme::*;
  ///
  /// let mut theme = TdesktopTheme::new();
  ///
  /// theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();
  ///
  /// assert!(theme.has_variable("windowBg"));
  /// assert!(!theme.has_variable("windowFg"));
  /// ```
  pub fn has_variable(&self, variable: &str) -> bool {
    self.variables.contains_key(variable)
  }

  /// Deletes `variable`.
  ///
  /// # Notes
  ///
  /// Since `IndexMap.remove` is used under the hood, variables order is
  /// distorted.
  ///
  /// This method won't unlink variables that depended on this variable before
  /// deleting.
  ///
  /// # Examples
  ///
  /// ```
  /// use tdesktop_theme::*;
  ///
  /// let mut theme = TdesktopTheme::new();
  ///
  /// theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();
  /// theme.delete_variable("windowBg");
  ///
  /// assert!(!theme.has_variable("windowBg"));
  /// ```
  pub fn delete_variable(&mut self, variable: &str) {
    self.variables.remove(variable);
  }

  /// Links `variable` to `link_to`.
  ///
  /// # Examples
  ///
  /// ```
  /// use tdesktop_theme::*;
  ///
  /// let mut theme = TdesktopTheme::new();
  /// theme
  ///   .link_variable("windowBoldFg".to_string(), "windowFg".to_string())
  ///   .unwrap();
  ///
  /// assert_eq!(
  ///   theme.get_variable("windowBoldFg"),
  ///   Some(&Value::Link("windowFg".to_string()))
  /// );
  /// ```
  pub fn link_variable(
    &mut self,
    variable: String,
    link_to: String,
  ) -> Result<(), String> {
    if !utils::is_variable_name_valid(&variable) {
      return Err(format!("Got invalid variable name: {}", variable));
    }

    if !utils::is_variable_name_valid(&link_to) {
      return Err(format!("Got invalid variable name: {}", link_to));
    }

    self.variables.insert(variable, Value::Link(link_to));

    Ok(())
  }

  /// Gets `variables`'s real color value and assigns it to `variable`, so that
  /// `variable` doesn't depend on other variables anymore.
  ///
  /// # Notes
  /// If it can't resolve `variable` to a color, then `variable` is deleted.
  ///
  /// # Examples
  ///
  /// ```
  /// use tdesktop_theme::*;
  ///
  /// let mut theme = TdesktopTheme::new();
  ///
  /// theme.set_variable("windowFg".to_string(), [0x00; 4]).unwrap();
  /// theme
  ///   .link_variable("windowBoldFg".to_string(), "windowFg".to_string())
  ///   .unwrap();
  /// theme.unlink_variable("windowBoldFg");
  ///
  /// assert_eq!(
  ///   theme.get_variable("windowBoldFg"),
  ///   Some(&Value::Color([0x00; 4])),
  /// );
  /// ```
  pub fn unlink_variable(&mut self, variable: &str) {
    let color = if let Some(color) = self.resolve_variable(&variable) {
      Some(*color)
    } else {
      None
    };

    if let Some(color) = color {
      *self.variables.get_mut(variable).unwrap() = Value::Color(color);
    } else {
      self.variables.remove(variable);
    }
  }

  /// Gets `variable`'s real color value. That is, this method resolves all
  /// links until it gets a color value or `None`.
  ///
  /// # Examples
  ///
  /// ```
  /// use tdesktop_theme::*;
  ///
  /// let mut theme = TdesktopTheme::new();
  ///
  /// theme.set_variable("windowFg".to_string(), [0xff; 4]).unwrap();
  /// theme
  ///   .link_variable("windowBoldFg".to_string(), "windowFg".to_string())
  ///   .unwrap();
  ///
  /// assert_eq!(
  ///   theme.resolve_variable("windowBoldFg"),
  ///   Some(&[0xff; 4]),
  /// );
  /// ```
  pub fn resolve_variable(&self, variable: &str) -> Option<&Color> {
    let mut current_variable = variable;

    loop {
      match self.variables.get(current_variable) {
        None => return None,
        Some(Value::Color(color)) => return Some(color),
        Some(Value::Link(link)) => current_variable = link,
      };
    }
  }

  /// Returns amount of variables in the theme.
  ///
  /// # Example
  ///
  /// ```
  /// use tdesktop_theme::*;
  ///
  /// let mut theme = TdesktopTheme::new();
  ///
  /// assert_eq!(theme.len(), 0);
  /// theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();
  /// assert_eq!(theme.len(), 1);
  /// ```
  pub fn len(&self) -> usize {
    self.variables.len()
  }

  /// Returns `true` if the theme has no variables.
  ///
  /// # Example
  ///
  /// ```
  /// use tdesktop_theme::*;
  ///
  /// let mut theme = TdesktopTheme::new();
  ///
  /// assert!(theme.is_empty());
  /// theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();
  /// assert!(!theme.is_empty());
  /// ```
  pub fn is_empty(&self) -> bool {
    self.variables.is_empty()
  }

  /// Returns an iterator over the theme's variables.
  ///
  /// # Examples
  ///
  /// ```
  /// use tdesktop_theme::*;
  ///
  /// let theme = TdesktopTheme::from_bytes(b"
  ///   windowBg: #ffffff;
  ///   windowFg: #000000;
  /// ").unwrap();
  ///
  /// for variable in theme.variables() {
  ///   println!("{}", variable);
  ///   assert!(theme.has_variable(variable));
  /// }
  /// ```
  pub fn variables(&self) -> indexmap::map::Keys<String, Value> {
    self.variables.keys()
  }

  /// Serializes the palette of the theme. That is, only the variables without
  /// the wallpaper; the result is to be saved in a `.tdesktop-palette` file.
  ///
  /// # Examples
  ///
  /// ```
  /// use tdesktop_theme::*;
  ///
  /// let mut theme = TdesktopTheme::new();
  ///
  /// theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();
  /// theme
  ///   .link_variable("windowActiveBg".to_string(), "windowBg".to_string())
  ///   .unwrap();
  ///
  /// assert_eq!(theme.to_palette_bytes(), b"windowBg: #ffffffff;
  /// windowActiveBg: windowBg;
  /// ".to_vec());
  /// ```
  pub fn to_palette_bytes(&self) -> Vec<u8> {
    serialize::serialize_palette(&self.variables)
  }

  /// Zips the theme. It returns a buffer that is to be saved as
  /// `.tdesktop-theme`.
  ///
  /// # Examples
  ///
  /// ```
  /// use tdesktop_theme::*;
  ///
  /// let mut theme = TdesktopTheme::new();
  ///
  /// theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();
  /// theme
  ///   .link_variable("windowActiveBg".to_string(), "windowBg".to_string())
  ///   .unwrap();
  ///
  /// // Go check that on your own!
  /// std::fs::write(
  ///   "./tests/ignore/my-theme.tdesktop-theme",
  ///   theme.to_zip_bytes().unwrap(),
  /// ).unwrap();
  /// ```
  pub fn to_zip_bytes(&self) -> Result<Vec<u8>, zip::result::ZipError> {
    serialize::serialize_theme(&self.variables, &self.wallpaper)
  }

  /// Returns an interator over mutable color values.
  ///
  /// # Example
  ///
  /// ```
  /// use tdesktop_theme::*;
  ///
  /// let mut theme = TdesktopTheme::new();
  ///
  /// theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();
  ///
  /// for (variable, value) in theme.variables_mut() {
  ///   *value = Value::Color([0x00; 4]);
  /// }
  ///
  /// assert_eq!(
  ///   theme.get_variable("windowBg"),
  ///   Some(&Value::Color([0x00; 4])),
  /// );
  /// ```
  pub fn variables_mut(&mut self) -> indexmap::map::IterMut<'_, String, Value> {
    self.variables.iter_mut()
  }
}

/// Iterates over variables and their raw values.
///
/// # Examples
///
/// ```
/// use tdesktop_theme::*;
///
/// let theme = TdesktopTheme::from_bytes(b"
///   windowFg: #212121;
///   windowBoldFg: windowFg;
/// ").unwrap();
///
/// for (variable, value) in &theme {
///   match value {
///     Value::Color([red, green, blue, alpha]) => {
///       println!("{} has the value:", variable);
///       println!("  Red: {}", red);
///       println!("  Green: {}", green);
///       println!("  Blue: {}", blue);
///       println!("  Alpha: {}", alpha);
///     },
///     Value::Link(link) => {
///       println!("{} is linked to {}", variable, link);
///     },
///   };
/// };
/// ```
impl<'a> IntoIterator for &'a TdesktopTheme {
  type Item = (&'a String, &'a Value);
  type IntoIter = indexmap::map::Iter<'a, String, Value>;

  fn into_iter(self) -> Self::IntoIter {
    self.variables.iter()
  }
}

/// Clones all variables from `other` to `self`.
///
/// # Notes
///
/// `other`'s wallpaper is taken if it's `Some(...)`. Otherwise, `self`'s
/// wallpaper is taken.
///
/// You can do both `&theme + &other_theme` and `theme + other_theme`. In the
/// first case, variables and wallpapers are cloned; in the second case, `theme`
/// and `other_theme` are borrowed. Choose the right way depending on your case.
///
/// # Examples
///
/// ```
/// use tdesktop_theme::*;
///
/// let mut theme = TdesktopTheme::new();
///
/// theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();
/// theme.set_variable("windowFg".to_string(), [0x00; 4]).unwrap();
/// theme.wallpaper = Some(Wallpaper {
///   wallpaper_type: WallpaperType::Tiled,
///   extension: WallpaperExtension::Jpg,
///   bytes: b"Pretend it's a wallpaper".to_vec(),
/// });
///
/// let mut other_theme = TdesktopTheme::new();
///
/// other_theme.set_variable("windowFg".to_string(), [0x21; 4]).unwrap();
/// other_theme
///   .link_variable("windowBoldFg".to_string(), "windowFg".to_string())
///   .unwrap();
///
/// let third_theme = &theme + &other_theme;
///
/// assert_eq!(third_theme.wallpaper, theme.wallpaper);
/// assert_eq!(
///   third_theme.get_variable("windowBg"),
///   Some(&Value::Color([0xff; 4])),
/// );
/// assert_eq!(
///   third_theme.get_variable("windowFg"),
///   Some(&Value::Color([0x21; 4])),
/// );
/// assert_eq!(
///   third_theme.get_variable("windowBoldFg"),
///   Some(&Value::Link("windowFg".to_string())),
/// );
/// ```
impl<'a> std::ops::Add for &'a TdesktopTheme {
  type Output = TdesktopTheme;

  fn add(self, other: &TdesktopTheme) -> Self::Output {
    let mut final_theme = TdesktopTheme::with_capacity(other.len());

    final_theme.variables = self.variables.clone();

    for (variable, value) in &other.variables {
      final_theme.variables.insert(variable.to_string(), value.clone());
    }

    if let Some(wallpaper) = &other.wallpaper {
      final_theme.wallpaper = Some(wallpaper.clone());
    } else {
      final_theme.wallpaper = self.wallpaper.clone();
    }

    final_theme
  }
}

/// Takes all variables from `other` and adds to `self`.
///
/// # Notes
///
/// `other`'s wallpaper is taken if it's `Some(...)`. Otherwise, `self`'s
/// wallpaper is taken.
///
/// You can do both `&theme + &other_theme` and `theme + other_theme`. In the
/// first case, variables and wallpapers are cloned; in the second case, `theme`
/// and `other_theme` are borrowed. Choose the right way depending on your case.
impl std::ops::Add for TdesktopTheme {
  type Output = TdesktopTheme;

  fn add(self, other: TdesktopTheme) -> Self::Output {
    let mut final_theme = TdesktopTheme::with_capacity(other.len());

    final_theme.variables = self.variables;

    for (variable, value) in other.variables {
      final_theme.variables.insert(variable, value);
    }

    if let Some(wallpaper) = other.wallpaper {
      final_theme.wallpaper = Some(wallpaper);
    } else {
      final_theme.wallpaper = self.wallpaper;
    }

    final_theme
  }
}

/// Clones all variables from `other` to `self`.
///
/// # Notes
///
/// `other`'s wallpaper is taken if it's `Some(...)`.
///
/// # Examples
///
/// ```
/// use tdesktop_theme::*;
///
/// let mut theme = TdesktopTheme::new();
///
/// theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();
/// theme.set_variable("windowFg".to_string(), [0x00; 4]).unwrap();
/// theme.wallpaper = Some(Wallpaper {
///   wallpaper_type: WallpaperType::Tiled,
///   extension: WallpaperExtension::Jpg,
///   bytes: b"Pretend it's a wallpaper".to_vec(),
/// });
///
/// let mut other_theme = TdesktopTheme::new();
///
/// other_theme.set_variable("windowFg".to_string(), [0x21; 4]).unwrap();
/// other_theme
///   .link_variable("windowBoldFg".to_string(), "windowFg".to_string())
///   .unwrap();
///
/// theme += other_theme;
///
/// assert_eq!(theme.wallpaper, Some(Wallpaper {
///   wallpaper_type: WallpaperType::Tiled,
///   extension: WallpaperExtension::Jpg,
///   bytes: b"Pretend it's a wallpaper".to_vec(),
/// }));
///
/// assert_eq!(
///   theme.get_variable("windowBg"),
///   Some(&Value::Color([0xff; 4])),
/// );
/// assert_eq!(
///   theme.get_variable("windowFg"),
///   Some(&Value::Color([0x21; 4])),
/// );
/// assert_eq!(
///   theme.get_variable("windowBoldFg"),
///   Some(&Value::Link("windowFg".to_string())),
/// );
/// ```
impl std::ops::AddAssign for TdesktopTheme {
  fn add_assign(&mut self, other: TdesktopTheme) {
    for (variable, value) in other.variables {
      self.variables.insert(variable, value);
    }

    if let Some(wallpaper) = other.wallpaper {
      self.wallpaper = Some(wallpaper);
    }
  }
}

/// Clones `other`'s variables that are absent in `self`.
///
/// # Notes
///
/// `other`'s wallpaper is taken if `self`'s wallpaper is `None`.
///
/// # Examples
///
/// ```
/// use tdesktop_theme::*;
///
/// let mut theme = TdesktopTheme::new();
///
/// theme.set_variable("windowBg".to_string(), [0x00; 4]).unwrap();
///
/// let default_theme = default_themes::classic();
///
/// let normalized_theme = &theme | &default_theme;
///
/// assert_eq!(
///   normalized_theme.get_variable("windowBg"),
///   Some(&Value::Color([0x00; 4])),
/// );
///
/// assert_eq!(
///   normalized_theme.get_variable("windowFg"),
///   Some(&Value::Color([0x00, 0x00, 0x00, 0xff])),
/// );
/// ```
impl<'a> std::ops::BitOr for &'a TdesktopTheme {
  type Output = TdesktopTheme;

  fn bitor(self, other: &TdesktopTheme) -> Self::Output {
    other + self
  }
}

/// Moves `other`'s variables that are absent in `self`.
///
/// # Notes
///
/// `other`'s wallpaper is taken if `self`'s wallpaper is `None`.
///
/// You can do both `&theme | &other_theme` and `theme | other_theme`. In the
/// first case, variables and wallpapers are cloned; in the second case, `theme`
/// and `other_theme` are borrowed. Choose the right way depending on your case.
impl std::ops::BitOr for TdesktopTheme {
  type Output = TdesktopTheme;

  fn bitor(self, other: TdesktopTheme) -> Self::Output {
    other + self
  }
}

/// Clones `other`'s variables that are absent in `self`.
///
/// # Notes
///
/// `other`'s wallpaper is taken if `self`'s wallpaper is `None`.
///
/// You can do both `&theme | &other_theme` and `theme | other_theme`. In the
/// first case, variables and wallpapers are cloned; in the second case, `theme`
/// and `other_theme` are borrowed. Choose the right way depending on your case.
///
/// # Examples
///
/// ```
/// use tdesktop_theme::*;
///
/// let mut theme = TdesktopTheme::new();
///
/// theme.set_variable("windowBg".to_string(), [0x00; 4]).unwrap();
///
/// let default_theme = default_themes::classic();
///
/// let normalized_theme = &theme | &default_theme;
///
/// assert_eq!(
///   normalized_theme.get_variable("windowBg"),
///   Some(&Value::Color([0x00; 4])),
/// );
///
/// assert_eq!(
///   normalized_theme.get_variable("windowFg"),
///   Some(&Value::Color([0x00, 0x00, 0x00, 0xff])),
/// );
/// ```
impl std::ops::BitOrAssign for TdesktopTheme {
  fn bitor_assign(&mut self, other: TdesktopTheme) {
    for (variable, value) in other.variables {
      self.variables.entry(variable).or_insert(value);
    }

    if self.wallpaper.is_none() {
      self.wallpaper = other.wallpaper;
    }
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn creates_empty_theme() {
    let theme = TdesktopTheme::new();

    assert_eq!(theme.wallpaper, None);
    assert_eq!(theme.variables, indexmap! {});
  }

  #[test]
  fn sets_variables_values() {
    let mut theme = TdesktopTheme::new();

    theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();

    assert_eq!(
      theme.variables,
      indexmap! {
        "windowBg".to_string() => Value::Color([0xff; 4]),
      },
    );

    theme.set_variable("windowBg".to_string(), [0x00; 4]).unwrap();

    assert_eq!(
      theme.variables,
      indexmap! {
        "windowBg".to_string() => Value::Color([0x00; 4]),
      },
    );
  }

  #[test]
  fn gets_variables_values_correctly() {
    let mut theme = TdesktopTheme::new();

    theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();
    theme
      .link_variable("windowBoldFg".to_string(), "windowFg".to_string())
      .unwrap();

    assert_eq!(theme.get_variable("windowBg"), Some(&Value::Color([0xff; 4])));
    assert_eq!(theme.get_variable("windowFg"), None);
    assert_eq!(
      theme.get_variable("windowBoldFg"),
      Some(&Value::Link("windowFg".to_string())),
    );
  }

  #[test]
  fn links_variables_correctly() {
    let mut theme = TdesktopTheme::new();

    theme
      .link_variable("windowBoldFg".to_string(), "windowFg".to_string())
      .unwrap();

    assert_eq!(
      theme.variables,
      indexmap! {
        "windowBoldFg".to_string() => Value::Link("windowFg".to_string())
      },
    );
  }

  #[test]
  fn resolves_variables_correctly() {
    let mut theme = TdesktopTheme::new();

    theme.set_variable("windowFg".to_string(), [0x00; 4]).unwrap();
    theme
      .link_variable("windowBoldFg".to_string(), "windowFg".to_string())
      .unwrap();
    theme
      .link_variable("windowBoldFgOver".to_string(), "windowBoldFg".to_string())
      .unwrap();

    assert_eq!(theme.resolve_variable("windowBoldFgOver"), Some(&[0x00; 4]));
    assert_eq!(theme.resolve_variable("windowFg"), Some(&[0x00; 4]));
    assert_eq!(theme.resolve_variable("windowBg"), None);
  }

  #[test]
  fn unlinks_variables_correctly() {
    let mut theme = TdesktopTheme::new();

    theme.set_variable("windowFg".to_string(), [0x00; 4]).unwrap();
    theme
      .link_variable("windowBoldFg".to_string(), "windowFg".to_string())
      .unwrap();
    theme
      .link_variable("windowActiveBg".to_string(), "windowBg".to_string())
      .unwrap();
    theme.unlink_variable("windowBoldFg");
    theme.unlink_variable("windowActiveBg");

    assert_eq!(
      theme.variables,
      indexmap! {
        "windowFg".to_string() => Value::Color([0x00; 4]),
        "windowBoldFg".to_string() => Value::Color([0x00; 4]),
      },
    );
  }

  #[test]
  fn checks_variable_existance_correctly() {
    let mut theme = TdesktopTheme::new();

    theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();

    assert!(theme.has_variable("windowBg"));
    assert!(!theme.has_variable("windowFg"));
  }

  #[test]
  fn deletes_variables() {
    let mut theme = TdesktopTheme::new();

    theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();
    theme.delete_variable("windowBg");

    assert_eq!(theme.variables, indexmap! {});
  }

  #[test]
  fn len_is_correct() {
    let mut theme = TdesktopTheme::new();

    assert_eq!(theme.len(), 0);
    theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();
    assert_eq!(theme.len(), 1);
  }

  #[test]
  fn iterates_variables_correcly() {
    let mut expected_variables = indexmap! {
      "windowBg".to_string() => (true, false),
      "windowFg".to_string() => (true, false),
      "windowBoldFg".to_string() => (false, false),
    };

    let theme = TdesktopTheme::from_bytes(
      b"windowBg: #ffffff;
        windowFg: #000000;",
    )
    .unwrap();

    for variable in theme.variables() {
      expected_variables.get_mut(variable).unwrap().1 = true;
    }

    assert!(expected_variables.iter().all(|(_, (x, y))| x == y));
  }

  #[test]
  fn iterates_over_theme_correctly() {
    let theme = TdesktopTheme::from_bytes(
      b"windowFg: #212121;
        windowBoldFg: windowFg;",
    )
    .unwrap();

    let mut iterated_over = indexmap! {};

    for (variable, value) in &theme {
      iterated_over.insert(variable.to_string(), value.clone());
    }

    assert_eq!(theme.variables, iterated_over);
  }

  #[test]
  fn adds_themes_correctly() {
    let mut theme = TdesktopTheme::new();

    theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();
    theme.set_variable("windowFg".to_string(), [0x00; 4]).unwrap();
    theme.wallpaper = Some(Wallpaper {
      wallpaper_type: WallpaperType::Tiled,
      extension: WallpaperExtension::Jpg,
      bytes: b"Pretend it's a wallpaper".to_vec(),
    });

    let mut other_theme = TdesktopTheme::new();

    other_theme.set_variable("windowFg".to_string(), [0x21; 4]).unwrap();
    other_theme
      .link_variable("windowBoldFg".to_string(), "windowFg".to_string())
      .unwrap();

    let third_theme = &theme + &other_theme;

    assert_eq!(third_theme.wallpaper, theme.wallpaper);

    assert_eq!(
      third_theme.variables,
      indexmap! {
        "windowBg".to_string() => Value::Color([0xff; 4]),
        "windowFg".to_string() => Value::Color([0x21; 4]),
        "windowBoldFg".to_string() => Value::Link("windowFg".to_string()),
      }
    );

    let third_theme = theme + other_theme;

    assert_eq!(
      third_theme.wallpaper,
      Some(Wallpaper {
        wallpaper_type: WallpaperType::Tiled,
        extension: WallpaperExtension::Jpg,
        bytes: b"Pretend it's a wallpaper".to_vec(),
      })
    );

    assert_eq!(
      third_theme.variables,
      indexmap! {
        "windowBg".to_string() => Value::Color([0xff; 4]),
        "windowFg".to_string() => Value::Color([0x21; 4]),
        "windowBoldFg".to_string() => Value::Link("windowFg".to_string()),
      }
    );

    let mut theme = TdesktopTheme::new();

    theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();
    theme.set_variable("windowFg".to_string(), [0x00; 4]).unwrap();
    theme.wallpaper = Some(Wallpaper {
      wallpaper_type: WallpaperType::Tiled,
      extension: WallpaperExtension::Jpg,
      bytes: b"Pretend it's a wallpaper".to_vec(),
    });

    let mut other_theme = TdesktopTheme::new();

    other_theme.set_variable("windowFg".to_string(), [0x21; 4]).unwrap();
    other_theme
      .link_variable("windowBoldFg".to_string(), "windowFg".to_string())
      .unwrap();

    theme += other_theme;

    assert_eq!(
      theme.wallpaper,
      Some(Wallpaper {
        wallpaper_type: WallpaperType::Tiled,
        extension: WallpaperExtension::Jpg,
        bytes: b"Pretend it's a wallpaper".to_vec(),
      })
    );

    assert_eq!(
      theme.variables,
      indexmap! {
        "windowBg".to_string() => Value::Color([0xff; 4]),
        "windowFg".to_string() => Value::Color([0x21; 4]),
        "windowBoldFg".to_string() => Value::Link("windowFg".to_string()),
      }
    );

    let mut theme = TdesktopTheme::new();
    let mut other_theme = TdesktopTheme::new();

    theme.wallpaper = Some(Wallpaper {
      wallpaper_type: WallpaperType::Tiled,
      extension: WallpaperExtension::Png,
      bytes: b"Wallpaper one".to_vec(),
    });

    other_theme.wallpaper = Some(Wallpaper {
      wallpaper_type: WallpaperType::Background,
      extension: WallpaperExtension::Jpg,
      bytes: b"Wallpaper two".to_vec(),
    });

    assert_eq!((&theme + &other_theme).wallpaper, other_theme.wallpaper);
    assert_eq!(
      (theme + other_theme).wallpaper,
      Some(Wallpaper {
        wallpaper_type: WallpaperType::Background,
        extension: WallpaperExtension::Jpg,
        bytes: b"Wallpaper two".to_vec(),
      })
    );

    let mut theme = TdesktopTheme::new();
    let mut other_theme = TdesktopTheme::new();

    theme.wallpaper = Some(Wallpaper {
      wallpaper_type: WallpaperType::Tiled,
      extension: WallpaperExtension::Png,
      bytes: b"Wallpaper one".to_vec(),
    });

    other_theme.wallpaper = Some(Wallpaper {
      wallpaper_type: WallpaperType::Background,
      extension: WallpaperExtension::Jpg,
      bytes: b"Wallpaper two".to_vec(),
    });

    theme += other_theme;

    assert_eq!(
      theme.wallpaper,
      Some(Wallpaper {
        wallpaper_type: WallpaperType::Background,
        extension: WallpaperExtension::Jpg,
        bytes: b"Wallpaper two".to_vec(),
      }),
    );
  }

  #[test]
  fn bitor_works_correctly() {
    let mut theme = TdesktopTheme::new();

    theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();
    theme.set_variable("windowFg".to_string(), [0x00; 4]).unwrap();
    theme.wallpaper = Some(Wallpaper {
      wallpaper_type: WallpaperType::Tiled,
      extension: WallpaperExtension::Jpg,
      bytes: b"wallpaper one".to_vec(),
    });

    let mut other_theme = TdesktopTheme::new();

    other_theme.set_variable("windowFg".to_string(), [0xff; 4]).unwrap();
    other_theme.set_variable("windowBoldFg".to_string(), [0x80; 4]).unwrap();
    other_theme.wallpaper = Some(Wallpaper {
      wallpaper_type: WallpaperType::Background,
      extension: WallpaperExtension::Png,
      bytes: b"wallpaper two".to_vec(),
    });

    let third_theme = &theme | &other_theme;

    assert_eq!(third_theme.wallpaper, theme.wallpaper);
    assert_eq!(
      third_theme.variables,
      indexmap! {
        "windowBg".to_string() => Value::Color([0xff; 4]),
        "windowFg".to_string() => Value::Color([0x00; 4]),
        "windowBoldFg".to_string() => Value::Color([0x80; 4]),
      },
    );

    let third_theme = theme | other_theme;

    assert_eq!(
      third_theme.wallpaper,
      Some(Wallpaper {
        wallpaper_type: WallpaperType::Tiled,
        extension: WallpaperExtension::Jpg,
        bytes: b"wallpaper one".to_vec(),
      }),
    );
    assert_eq!(
      third_theme.variables,
      indexmap! {
        "windowBg".to_string() => Value::Color([0xff; 4]),
        "windowFg".to_string() => Value::Color([0x00; 4]),
        "windowBoldFg".to_string() => Value::Color([0x80; 4]),
      },
    );

    let mut theme = TdesktopTheme::new();

    theme.set_variable("windowBg".to_string(), [0xff; 4]).unwrap();
    theme.set_variable("windowFg".to_string(), [0x00; 4]).unwrap();
    theme.wallpaper = Some(Wallpaper {
      wallpaper_type: WallpaperType::Tiled,
      extension: WallpaperExtension::Jpg,
      bytes: b"wallpaper one".to_vec(),
    });

    let mut other_theme = TdesktopTheme::new();

    other_theme.set_variable("windowFg".to_string(), [0xff; 4]).unwrap();
    other_theme.set_variable("windowBoldFg".to_string(), [0x80; 4]).unwrap();
    other_theme.wallpaper = Some(Wallpaper {
      wallpaper_type: WallpaperType::Background,
      extension: WallpaperExtension::Png,
      bytes: b"wallpaper two".to_vec(),
    });

    theme |= other_theme;

    assert_eq!(
      theme.wallpaper,
      Some(Wallpaper {
        wallpaper_type: WallpaperType::Tiled,
        extension: WallpaperExtension::Jpg,
        bytes: b"wallpaper one".to_vec(),
      })
    );
    assert_eq!(
      theme.variables,
      indexmap! {
        "windowBg".to_string() => Value::Color([0xff; 4]),
        "windowFg".to_string() => Value::Color([0x00; 4]),
        "windowBoldFg".to_string() => Value::Color([0x80; 4]),
      }
    );

    let mut theme = TdesktopTheme::new();
    theme.wallpaper = Some(Wallpaper {
      wallpaper_type: WallpaperType::Background,
      extension: WallpaperExtension::Png,
      bytes: b"wallpaper".to_vec(),
    });

    assert_eq!((&TdesktopTheme::new() | &theme).wallpaper, theme.wallpaper);
    assert_eq!(
      (TdesktopTheme::new() | theme).wallpaper,
      Some(Wallpaper {
        wallpaper_type: WallpaperType::Background,
        extension: WallpaperExtension::Png,
        bytes: b"wallpaper".to_vec(),
      }),
    );

    let mut theme = TdesktopTheme::new();
    let mut other_theme = TdesktopTheme::new();
    other_theme.wallpaper = Some(Wallpaper {
      wallpaper_type: WallpaperType::Background,
      extension: WallpaperExtension::Png,
      bytes: b"wallpaper".to_vec(),
    });

    theme |= other_theme;
    assert_eq!(
      theme.wallpaper,
      Some(Wallpaper {
        wallpaper_type: WallpaperType::Background,
        extension: WallpaperExtension::Png,
        bytes: b"wallpaper".to_vec(),
      }),
    );
  }
}
