use chrono::UTC;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::collections::hash_map::DefaultHasher;

#[derive(Serialize, Deserialize)]
pub struct Token {
    pub access_token: String,
    pub expires_in: u16,
    pub token_type: String,
    pub scope: String,
    pub session_id: String,
    pub refresh_token: String,
    pub user_id: String,
    #[serde(default = "timestamp")]
    pub timestamp: i64,
}

impl Token {
    pub fn is_expired(&self) -> bool {
        let now = UTC::now().timestamp();

        now > self.timestamp + self.expires_in as i64
    }
}

fn timestamp() -> i64 {
    UTC::now().timestamp()
}

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Config {
    #[serde(default)]
    pub game_storage: String,
    #[serde(default)]
    pub movie_storage: String,
    #[serde(default = "default_map")]
    pub content: HashMap<String, u64>,
    #[serde(default = "default_map")]
    pub data: HashMap<String, u64>,
    #[serde(default = "default_map")]
    pub extras: HashMap<String, u64>,
    #[serde(default = "default_list")]
    pub os_filters: Vec<String>,
    #[serde(default = "default_list")]
    pub language_filters: Vec<String>,
    #[serde(default = "default_list")]
    pub resolution_filters: Vec<String>,
    #[serde(default)]
    pub skip_movies: bool,
    #[serde(default)]
    pub skip_games: bool,
}

fn default_map() -> HashMap<String, u64> {
    HashMap::new()
}

fn default_list() -> Vec<String> {
    Vec::new()
}

impl Config {
    pub fn new() -> Config {
        Config {
            game_storage: String::from("."),
            movie_storage: String::from("."),
            content: HashMap::new(),
            data: HashMap::new(),
            extras: HashMap::new(),
            os_filters: Vec::new(),
            language_filters: Vec::new(),
            resolution_filters: Vec::new(),
            skip_movies: false,
            skip_games: false,
        }
    }
}

#[derive(Hash)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Content {
    pub title: String,
    #[serde(skip_deserializing)]
    #[serde(rename(deserialize = "cdKey"))]
    pub cd_keys: BTreeMap<String, String>,
    #[serde(skip_deserializing)]
    pub is_movie: bool,
    #[serde(skip_deserializing)]
    pub data: Vec<Data>,
    pub extras: Vec<Extra>,
}

impl fmt::Display for Content {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({})", self.title)
    }
}

#[derive(Hash)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Data {
    pub manual_url: String,
    pub version: String,
    pub os: String,
    pub language: String,
}

impl fmt::Display for Data {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f,
               "({}, {}, {}, {})",
               self.manual_url,
               self.version,
               self.os,
               self.language)
    }
}

#[derive(Hash)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Extra {
    pub manual_url: String,
    pub name: String,
}

impl fmt::Display for Extra {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.manual_url, self.name)
    }
}

pub fn get_hash<T>(obj: &T) -> u64
    where T: Hash
{
    let mut s = DefaultHasher::new();
    obj.hash(&mut s);
    s.finish()
}
