use regex::Regex;
use std::cmp::max;
use std::error::Error;

use std::fmt;
use std::fs::File as FsFile;
use std::io;
use std::io::{BufRead, BufReader};
use std::path::Path;
use std::path::PathBuf;

use command_runner::CommandRunner;
use resources::Resource;
use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner};

pub struct WordpressTranslation<'a, C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> {
  path: D,
  version: &'a str,
  locale: C,
  command_runner: &'a R,
}

impl<'a, C: AsRef<str>, R: CommandRunner> WordpressTranslation<'a, C, PathBuf, R> {
  pub fn new<D: AsRef<Path>>(path: D, version: &'a str, locale: C, command_runner: &'a R) -> Self {
    WordpressTranslation {
      path: [path.as_ref(), "wp-content/languages".as_ref()]
        .iter()
        .collect(),
      version,
      locale,
      command_runner,
    }
  }
}

impl<'a, C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> WordpressTranslation<'a, C, D, R> {
  fn get_pairs(&self) -> Vec<(String, PathBuf)> {
    let version_x = self
      .version
      .trim_end_matches(|c: char| c.is_digit(10))
      .to_owned()
      + "x";
    let locale = self.locale.as_ref();
    let path_locale = if locale == "de_DE" {
      "de".to_owned()
    } else {
      locale.to_lowercase().replace('_', "-")
    };
    let mut res = vec![];
    for &(in_slug, out_slug) in [
      ("", ""),
      ("cc/", "continents-cities-"),
      ("admin/", "admin-"),
      ("admin/network/", "admin-network-"),
    ]
    .iter()
    {
      for format in ["po", "mo"].iter() {
        res.push((
          format!("https://translate.wordpress.org/projects/wp/{}/{}{}/default/export-translations?format={}", version_x, in_slug, path_locale, format),
          [self.path.as_ref(), format!("{}{}.{}", out_slug, self.locale.as_ref(), format).as_ref()].iter().collect()
        ))
      }
    }
    res
  }
}

impl<'a, C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> Symbol
  for WordpressTranslation<'a, C, D, R>
{
  fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
    let mut newest = String::new();
    let match_date = Regex::new("(?m)^\"PO-Revision-Date: (.+)\\+0000\\\\n\"$").unwrap();
    for (_, target) in self.get_pairs() {
      match FsFile::open(target.clone()) {
        Err(e) => {
          // Check if file exists
          return if e.kind() == io::ErrorKind::NotFound {
            Ok(false)
          } else {
            Err(Box::new(e))
          };
        }
        Ok(file) => {
          if target.ends_with(".po") {
            let reader = BufReader::new(file);
            for content in reader.lines() {
              if let Some(match_result) = match_date.captures(&content?) {
                newest = max(newest, match_result[1].to_string());
                break;
              }
            }
          }
        }
      }
    }
    let upstream = self.command_runner.get_output(
      "curl",
      args![format!(
        "https://api.wordpress.org/core/version-check/1.7/?version={}&locale={}",
        self.version,
        self.locale.as_ref()
      )],
    )?;
    Ok(String::from_utf8(upstream)?.contains(&format!(
      r###"language":"{}","version":"{}","updated":"{}"###,
      self.locale.as_ref(),
      self.version,
      newest
    )))
  }

  fn execute(&self) -> Result<(), Box<dyn Error>> {
    for (source, target) in self.get_pairs() {
      self
        .command_runner
        .run_successfully("curl", args!["--compressed", "-o", target, source,])?;
    }
    Ok(())
  }

  fn get_prerequisites(&self) -> Vec<Resource> {
    vec![Resource::new("dir", self.path.as_ref().to_str().unwrap())]
  }

  fn as_action<'b>(&'b self, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b> {
    Box::new(SymbolAction::new(runner, self))
  }

  fn into_action<'b>(self: Box<Self>, runner: &'b dyn SymbolRunner) -> Box<dyn Action + 'b>
  where
    Self: 'b,
  {
    Box::new(OwnedSymbolAction::new(runner, *self))
  }
}

impl<'a, C: AsRef<str>, D: AsRef<Path>, R: CommandRunner> fmt::Display
  for WordpressTranslation<'a, C, D, R>
{
  fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
    write!(f, "WordpressTranslation {}", self.path.as_ref().display())
  }
}