// Copyright (c) 2017 Martijn Rijkeboer <mrr@sru-systems.com>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use super::context::Context;
use super::config::Config;
use super::core;
use super::encoding;
use super::memory::Memory;
use super::result::Result;
use super::thread_mode::ThreadMode;
use super::variant::Variant;
use super::version::Version;

/// Returns the length of the encoded string.
///
/// # Remarks
///
/// The length is **one** less that the original C version, since no null
/// terminator is used.
///
/// # Examples
///
/// ```rust
/// use argon2::{self, Variant};
///
/// let variant = Variant::Argon2i;
/// let mem = 4096;
/// let time = 10;
/// let parallelism = 10;
/// let salt_len = 8;
/// let hash_len = 32;
/// let enc_len = argon2::encoded_len(variant, mem, time, parallelism, salt_len, hash_len);
/// assert_eq!(enc_len, 86);
/// ```
#[cfg_attr(rustfmt, rustfmt_skip)]
pub fn encoded_len(
    variant: Variant,
    mem_cost: u32,
    time_cost: u32,
    parallelism: u32,
    salt_len: u32,
    hash_len: u32
) -> u32 {
    ("$$v=$m=,t=,p=$$".len() as u32)  +
    (variant.as_lowercase_str().len() as u32) +
    encoding::num_len(Version::default().as_u32()) +
    encoding::num_len(mem_cost) +
    encoding::num_len(time_cost) +
    encoding::num_len(parallelism) +
    encoding::base64_len(salt_len) +
    encoding::base64_len(hash_len)
}

/// Hashes the password and returns the encoded hash.
///
/// # Examples
///
/// Create an encoded hash with the default configuration:
///
/// ```
/// use argon2::{self, Config};
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let config = Config::default();
/// let encoded = argon2::hash_encoded(pwd, salt, &config).unwrap();
/// ```
///
///
/// Create an Argon2d encoded hash with 4 lanes and parallel execution:
///
/// ```
/// use argon2::{self, Config, ThreadMode, Variant};
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let mut config = Config::default();
/// config.variant = Variant::Argon2d;
/// config.lanes = 4;
/// config.thread_mode = ThreadMode::Parallel;
/// let encoded = argon2::hash_encoded(pwd, salt, &config).unwrap();
/// ```
pub fn hash_encoded(pwd: &[u8], salt: &[u8], config: &Config) -> Result<String> {
    let context = Context::new(config.clone(), pwd, salt)?;
    let hash = run(&context);
    let encoded = encoding::encode_string(&context, &hash);
    Ok(encoded)
}

/// Hashes the password using default settings and returns the encoded hash.
///
/// # Examples
///
/// ```
/// use argon2;
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let encoded = argon2::hash_encoded_defaults(pwd, salt).unwrap();
/// ```
///
/// The above rewritten using `hash_encoded`:
///
/// ```
/// use argon2::{self, Config};
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let config = Config::default();
/// let encoded = argon2::hash_encoded(pwd, salt, &config).unwrap();
/// ```
#[deprecated(since = "0.2.0", note = "please use `hash_encoded` instead")]
pub fn hash_encoded_defaults(pwd: &[u8], salt: &[u8]) -> Result<String> {
    hash_encoded(pwd, salt, &Config::default())
}

/// Hashes the password and returns the encoded hash (pre 0.2.0 `hash_encoded`).
///
/// # Examples
///
///
/// ```
/// use argon2::{self, Variant, Version};
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let mem_cost = 4096;
/// let time_cost = 10;
/// let lanes = 1;
/// let threads = 1;
/// let secret = b"secret value";
/// let ad = b"associated data";
/// let hash_len = 32;
/// let encoded = argon2::hash_encoded_old(Variant::Argon2i,
///                                        Version::Version13,
///                                        mem_cost,
///                                        time_cost,
///                                        lanes,
///                                        threads,
///                                        pwd,
///                                        salt,
///                                        secret,
///                                        ad,
///                                        hash_len).unwrap();
/// ```
///
/// The above rewritten using the new `hash_encoded`:
///
/// ```
/// use argon2::{self, Config, ThreadMode, Variant, Version};
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let config = Config {
///     variant: Variant::Argon2i,
///     version: Version::Version13,
///     mem_cost: 4096,
///     time_cost: 10,
///     lanes: 1,
///     thread_mode: ThreadMode::Sequential,
///     secret: b"secret value",
///     ad: b"associated data",
///     hash_length: 32,
/// };
/// let encoded = argon2::hash_encoded(pwd, salt, &config).unwrap();
/// ```
#[deprecated(since = "0.2.0", note = "please use new `hash_encoded` instead")]
pub fn hash_encoded_old(
    variant: Variant,
    version: Version,
    mem_cost: u32,
    time_cost: u32,
    lanes: u32,
    threads: u32,
    pwd: &[u8],
    salt: &[u8],
    secret: &[u8],
    ad: &[u8],
    hash_len: u32,
) -> Result<String> {
    let config = Config {
        variant: variant,
        version: version,
        mem_cost: mem_cost,
        time_cost: time_cost,
        lanes: lanes,
        thread_mode: ThreadMode::from_threads(threads),
        secret: secret,
        ad: ad,
        hash_length: hash_len,
    };
    hash_encoded(pwd, salt, &config)
}

/// Hashes the password and returns the encoded hash (standard).
///
/// # Examples
///
///
/// ```
/// use argon2::{self, Variant, Version};
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let mem_cost = 4096;
/// let time_cost = 10;
/// let parallelism = 1;
/// let hash_len = 32;
/// let encoded = argon2::hash_encoded_std(Variant::Argon2i,
///                                        Version::Version13,
///                                        mem_cost,
///                                        time_cost,
///                                        parallelism,
///                                        pwd,
///                                        salt,
///                                        hash_len).unwrap();
/// ```
///
/// The above rewritten using `hash_encoded`:
///
/// ```
/// use argon2::{self, Config, ThreadMode, Variant, Version};
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let config = Config {
///     variant: Variant::Argon2i,
///     version: Version::Version13,
///     mem_cost: 4096,
///     time_cost: 10,
///     lanes: 1,
///     thread_mode: ThreadMode::Sequential,
///     secret: &[],
///     ad: &[],
///     hash_length: 32,
/// };
/// let encoded = argon2::hash_encoded(pwd, salt, &config).unwrap();
/// ```
#[deprecated(since = "0.2.0", note = "please use `hash_encoded` instead")]
pub fn hash_encoded_std(
    variant: Variant,
    version: Version,
    mem_cost: u32,
    time_cost: u32,
    parallelism: u32,
    pwd: &[u8],
    salt: &[u8],
    hash_len: u32,
) -> Result<String> {
    let config = Config {
        variant: variant,
        version: version,
        mem_cost: mem_cost,
        time_cost: time_cost,
        lanes: parallelism,
        thread_mode: ThreadMode::from_threads(parallelism),
        secret: &[],
        ad: &[],
        hash_length: hash_len,
    };
    hash_encoded(pwd, salt, &config)
}

/// Hashes the password and returns the hash as a vector.
///
/// # Examples
///
/// Create a hash with the default configuration:
///
/// ```
/// use argon2::{self, Config};
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let config = Config::default();
/// let vec = argon2::hash_raw(pwd, salt, &config).unwrap();
/// ```
///
///
/// Create an Argon2d hash with 4 lanes and parallel execution:
///
/// ```
/// use argon2::{self, Config, ThreadMode, Variant};
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let mut config = Config::default();
/// config.variant = Variant::Argon2d;
/// config.lanes = 4;
/// config.thread_mode = ThreadMode::Parallel;
/// let vec = argon2::hash_raw(pwd, salt, &config).unwrap();
/// ```
pub fn hash_raw(pwd: &[u8], salt: &[u8], config: &Config) -> Result<Vec<u8>> {
    let context = Context::new(config.clone(), pwd, salt)?;
    let hash = run(&context);
    Ok(hash)
}

/// Hashes the password using default settings and returns the hash as a vector.
///
/// # Examples
///
/// ```
/// use argon2;
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let vec = argon2::hash_raw_defaults(pwd, salt).unwrap();
/// ```
///
/// The above rewritten using `hash_raw`:
///
/// ```
/// use argon2::{self, Config};
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let config = Config::default();
/// let vec = argon2::hash_raw(pwd, salt, &config).unwrap();
/// ```
#[deprecated(since = "0.2.0", note = "please use `hash_raw` instead")]
pub fn hash_raw_defaults(pwd: &[u8], salt: &[u8]) -> Result<Vec<u8>> {
    hash_raw(pwd, salt, &Config::default())
}

/// Hashes the password and returns the hash as a vector (pre 0.2.0 `hash_raw`).
///
/// # Examples
///
///
/// ```
/// use argon2::{self, Variant, Version};
///
/// let mem_cost = 4096;
/// let time_cost = 10;
/// let lanes = 1;
/// let threads = 1;
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let secret = b"secret value";
/// let ad = b"associated data";
/// let hash_len = 32;
/// let vec = argon2::hash_raw_old(Variant::Argon2i,
///                                Version::Version13,
///                                mem_cost,
///                                time_cost,
///                                lanes,
///                                threads,
///                                pwd,
///                                salt,
///                                secret,
///                                ad,
///                                hash_len).unwrap();
/// ```
///
/// The above rewritten using the new `hash_raw`:
///
/// ```
/// use argon2::{self, Config, ThreadMode, Variant, Version};
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let config = Config {
///     variant: Variant::Argon2i,
///     version: Version::Version13,
///     mem_cost: 4096,
///     time_cost: 10,
///     lanes: 1,
///     thread_mode: ThreadMode::Sequential,
///     secret: b"secret value",
///     ad: b"associated data",
///     hash_length: 32,
/// };
/// let vec = argon2::hash_raw(pwd, salt, &config);
/// ```
#[deprecated(since = "0.2.0", note = "please use new `hash_raw` instead")]
pub fn hash_raw_old(
    variant: Variant,
    version: Version,
    mem_cost: u32,
    time_cost: u32,
    lanes: u32,
    threads: u32,
    pwd: &[u8],
    salt: &[u8],
    secret: &[u8],
    ad: &[u8],
    hash_len: u32,
) -> Result<Vec<u8>> {
    let config = Config {
        variant: variant,
        version: version,
        mem_cost: mem_cost,
        time_cost: time_cost,
        lanes: lanes,
        thread_mode: ThreadMode::from_threads(threads),
        secret: secret,
        ad: ad,
        hash_length: hash_len,
    };
    hash_raw(pwd, salt, &config)
}

/// Hashes the password and returns the hash as a vector (standard).
///
/// # Examples
///
///
/// ```
/// use argon2::{self, Variant, Version};
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let mem_cost = 4096;
/// let time_cost = 10;
/// let parallelism = 1;
/// let hash_len = 32;
/// let vec = argon2::hash_raw_std(Variant::Argon2i,
///                                Version::Version13,
///                                mem_cost,
///                                time_cost,
///                                parallelism,
///                                pwd,
///                                salt,
///                                hash_len).unwrap();
/// ```
///
/// The above rewritten using `hash_raw`:
///
/// ```
/// use argon2::{self, Config, ThreadMode, Variant, Version};
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let config = Config {
///     variant: Variant::Argon2i,
///     version: Version::Version13,
///     mem_cost: 4096,
///     time_cost: 10,
///     lanes: 1,
///     thread_mode: ThreadMode::Sequential,
///     secret: &[],
///     ad: &[],
///     hash_length: 32,
/// };
/// let vec = argon2::hash_raw(pwd, salt, &config);
/// ```
#[deprecated(since = "0.2.0", note = "please use `hash_raw` instead")]
pub fn hash_raw_std(
    variant: Variant,
    version: Version,
    mem_cost: u32,
    time_cost: u32,
    parallelism: u32,
    pwd: &[u8],
    salt: &[u8],
    hash_len: u32,
) -> Result<Vec<u8>> {
    let config = Config {
        variant: variant,
        version: version,
        mem_cost: mem_cost,
        time_cost: time_cost,
        lanes: parallelism,
        thread_mode: ThreadMode::from_threads(parallelism),
        secret: &[],
        ad: &[],
        hash_length: hash_len,
    };
    hash_raw(pwd, salt, &config)
}

/// Verifies the password with the encoded hash.
///
/// # Examples
///
/// ```
/// use argon2;
///
/// let enc = "$argon2i$v=19$m=4096,t=3,p=1$c29tZXNhbHQ\
///            $iWh06vD8Fy27wf9npn6FXWiCX4K6pW6Ue1Bnzz07Z8A";
/// let pwd = b"password";
/// let res = argon2::verify_encoded(enc, pwd).unwrap();
/// assert!(res);
/// ```
pub fn verify_encoded(encoded: &str, pwd: &[u8]) -> Result<bool> {
    verify_encoded_ext(encoded, pwd, &[], &[])
}

/// Verifies the password with the encoded hash, secret and associated data.
///
/// # Examples
///
/// ```
/// use argon2;
///
/// let enc = "$argon2i$v=19$m=4096,t=3,p=1$c29tZXNhbHQ\
///            $OlcSvlN20Lz43sK3jhCJ9K04oejhiY0AmI+ck6nuETo";
/// let pwd = b"password";
/// let secret = b"secret";
/// let ad = b"ad";
/// let res = argon2::verify_encoded_ext(enc, pwd, secret, ad).unwrap();
/// assert!(res);
/// ```
pub fn verify_encoded_ext(encoded: &str, pwd: &[u8], secret: &[u8], ad: &[u8]) -> Result<bool> {
    let decoded = encoding::decode_string(encoded)?;
    let config = Config {
        variant: decoded.variant,
        version: decoded.version,
        mem_cost: decoded.mem_cost,
        time_cost: decoded.time_cost,
        lanes: decoded.parallelism,
        thread_mode: ThreadMode::from_threads(decoded.parallelism),
        secret: secret,
        ad: ad,
        hash_length: decoded.hash.len() as u32,
    };
    verify_raw(pwd, &decoded.salt, &decoded.hash, &config)
}

/// Verifies the password with the supplied configuration.
///
/// # Examples
///
///
/// ```
/// use argon2::{self, Config};
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let hash = &[137, 104, 116, 234, 240, 252, 23, 45, 187, 193, 255, 103, 166,
///              126, 133, 93, 104, 130, 95, 130, 186, 165, 110, 148, 123, 80,
///              103, 207, 61, 59, 103, 192];
/// let config = Config::default();
/// let res = argon2::verify_raw(pwd, salt, hash, &config).unwrap();
/// assert!(res);
/// ```
pub fn verify_raw(pwd: &[u8], salt: &[u8], hash: &[u8], config: &Config) -> Result<bool> {
    let config = Config {
        hash_length: hash.len() as u32,
        ..config.clone()
    };
    let context = Context::new(config, pwd, salt)?;
    Ok(run(&context) == hash)
}

/// Verifies the password with the supplied settings (pre 0.2.0 `verify_raw`).
///
/// # Examples
///
/// ```
/// use argon2::{self, Variant, Version};
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let hash = &[137, 104, 116, 234, 240, 252, 23, 45, 187, 193, 255, 103, 166,
///              126, 133, 93, 104, 130, 95, 130, 186, 165, 110, 148, 123, 80,
///              103, 207, 61, 59, 103, 192];
/// let mem_cost = 4096;
/// let time_cost = 3;
/// let lanes = 1;
/// let threads = 1;
/// let secret = &[];
/// let ad = &[];
/// let res = argon2::verify_raw_old(Variant::Argon2i,
///                                  Version::Version13,
///                                  mem_cost,
///                                  time_cost,
///                                  lanes,
///                                  threads,
///                                  pwd,
///                                  salt,
///                                  secret,
///                                  ad,
///                                  hash).unwrap();
/// assert!(res);
/// ```
///
/// The above rewritten using the new `verify_raw`:
///
/// ```
/// use argon2::{self, Config, ThreadMode, Variant, Version};
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let hash = &[137, 104, 116, 234, 240, 252, 23, 45, 187, 193, 255, 103, 166,
///              126, 133, 93, 104, 130, 95, 130, 186, 165, 110, 148, 123, 80,
///              103, 207, 61, 59, 103, 192];
/// let config = Config {
///     variant: Variant::Argon2i,
///     version: Version::Version13,
///     mem_cost: 4096,
///     time_cost: 3,
///     lanes: 1,
///     thread_mode: ThreadMode::Sequential,
///     secret: &[],
///     ad: &[],
///     hash_length: hash.len() as u32,
/// };
/// let res = argon2::verify_raw(pwd, salt, hash, &config).unwrap();
/// assert!(res);
/// ```
#[deprecated(since = "0.2.0", note = "please use new `verify_raw` instead")]
pub fn verify_raw_old(
    variant: Variant,
    version: Version,
    mem_cost: u32,
    time_cost: u32,
    lanes: u32,
    threads: u32,
    pwd: &[u8],
    salt: &[u8],
    secret: &[u8],
    ad: &[u8],
    hash: &[u8],
) -> Result<bool> {
    let config = Config {
        variant: variant,
        version: version,
        mem_cost: mem_cost,
        time_cost: time_cost,
        lanes: lanes,
        thread_mode: ThreadMode::from_threads(threads),
        secret: secret,
        ad: ad,
        hash_length: hash.len() as u32,
    };
    verify_raw(pwd, salt, hash, &config)
}

/// Verifies the password with the supplied settings (standard).
///
/// # Examples
///
///
/// ```
/// use argon2::{self, Variant, Version};
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let hash = &[137, 104, 116, 234, 240, 252, 23, 45, 187, 193, 255, 103, 166,
///              126, 133, 93, 104, 130, 95, 130, 186, 165, 110, 148, 123, 80,
///              103, 207, 61, 59, 103, 192];
/// let mem_cost = 4096;
/// let time_cost = 3;
/// let parallelism = 1;
/// let res = argon2::verify_raw_std(Variant::Argon2i,
///                                  Version::Version13,
///                                  mem_cost,
///                                  time_cost,
///                                  parallelism,
///                                  pwd,
///                                  salt,
///                                  hash).unwrap();
/// assert!(res);
/// ```
///
/// The above rewritten using `verify_raw`:
///
/// ```
/// use argon2::{self, Config, ThreadMode, Variant, Version};
///
/// let pwd = b"password";
/// let salt = b"somesalt";
/// let hash = &[137, 104, 116, 234, 240, 252, 23, 45, 187, 193, 255, 103, 166,
///              126, 133, 93, 104, 130, 95, 130, 186, 165, 110, 148, 123, 80,
///              103, 207, 61, 59, 103, 192];
/// let config = Config {
///     variant: Variant::Argon2i,
///     version: Version::Version13,
///     mem_cost: 4096,
///     time_cost: 3,
///     lanes: 1,
///     thread_mode: ThreadMode::Sequential,
///     secret: &[],
///     ad: &[],
///     hash_length: hash.len() as u32,
/// };
/// let res = argon2::verify_raw(pwd, salt, hash, &config).unwrap();
/// assert!(res);
/// ```
#[deprecated(since = "0.2.0", note = "please use `verify_raw` instead")]
pub fn verify_raw_std(
    variant: Variant,
    version: Version,
    mem_cost: u32,
    time_cost: u32,
    parallelism: u32,
    pwd: &[u8],
    salt: &[u8],
    hash: &[u8],
) -> Result<bool> {
    let config = Config {
        variant: variant,
        version: version,
        mem_cost: mem_cost,
        time_cost: time_cost,
        lanes: parallelism,
        thread_mode: ThreadMode::from_threads(parallelism),
        secret: &[],
        ad: &[],
        hash_length: hash.len() as u32,
    };
    verify_raw(pwd, salt, hash, &config)
}

fn run(context: &Context) -> Vec<u8> {
    let mut memory = Memory::new(context.config.lanes, context.lane_length);
    core::initialize(context, &mut memory);
    core::fill_memory_blocks(context, &mut memory);
    core::finalize(context, &memory)
}
