use zeroize::Zeroize;

use crate::blake2b;
use crate::error::Error;
use crate::utils::{load_u64_le, rotr64};

// Version of the algorithm
pub(crate) const ARGON2_VERSION_NUMBER: u32 = 0x13;

// Memory block size in bytes
const ARGON2_BLOCK_SIZE: usize = 1024;
const ARGON2_QWORDS_IN_BLOCK: usize = ARGON2_BLOCK_SIZE / 8;

// Number of pseudo-random values generated by one call to Blake in Argon2i to
// generate reference block positions
const ARGON2_ADDRESSES_IN_BLOCK: u32 = 128;

// Pre-hashing digest length and its extension
const ARGON2_PREHASH_DIGEST_LENGTH: usize = 64;
const ARGON2_PREHASH_SEED_LENGTH: usize = 72;

const ARGON2_MIN_LANES: u32 = 1;
const ARGON2_MAX_LANES: u32 = 0xFFFFFF;

// Number of synchronization points between lanes per pass
const ARGON2_SYNC_POINTS: u32 = 4;

// Minimum and maximum digest size in bytes
const ARGON2_MIN_OUTLEN: usize = 16;
const ARGON2_MAX_OUTLEN: usize = 0xFFFFFFFF;

// Minimum and maximum number of memory blocks (each of BLOCK_SIZE bytes)
const ARGON2_MIN_MEMORY: u32 = 2 * ARGON2_SYNC_POINTS; // 2 blocks per slice

const fn min(a: u64, b: u64) -> u64 {
    [a, b][(a > b) as usize]
}

// Max memory size is half the addressing space, topping at 2^32 blocks (4
// TB)
const ARGON2_MAX_MEMORY_BITS: u32 =
    min(32, std::mem::size_of::<usize>() as u64 * 8 - 10 - 1) as u32;
const ARGON2_MAX_MEMORY: u32 = min(0xFFFFFFFF, 1u64 << ARGON2_MAX_MEMORY_BITS) as u32;

// Minimum and maximum number of passes
const ARGON2_MIN_TIME: u32 = 1;
const ARGON2_MAX_TIME: u32 = 0xFFFFFFFF;

// Minimum and maximum password length in bytes
const ARGON2_MIN_PWD_LENGTH: usize = 0;
const ARGON2_MAX_PWD_LENGTH: usize = 0xFFFFFFFF;

// Minimum and maximum associated data length in bytes
const ARGON2_MIN_AD_LENGTH: usize = 0;
const ARGON2_MAX_AD_LENGTH: usize = 0xFFFFFFFF;

// Minimum and maximum salt length in bytes
const ARGON2_MIN_SALT_LENGTH: usize = 8;
const ARGON2_MAX_SALT_LENGTH: usize = 0xFFFFFFFF;

// Minimum and maximum key length in bytes
const ARGON2_MIN_SECRET: usize = 0;
const ARGON2_MAX_SECRET: usize = 0xFFFFFFFF;

#[derive(Clone, Copy, PartialEq)]
pub(crate) enum Argon2Type {
    Argon2i  = 1,
    Argon2id = 2,
}

#[derive(Clone)]
struct Block {
    v: [u64; ARGON2_QWORDS_IN_BLOCK],
}

#[cfg_attr(feature = "nightly", allow(clippy::derivable_impls))]
impl Default for Block {
    fn default() -> Self {
        Self {
            v: [0u64; ARGON2_QWORDS_IN_BLOCK],
        }
    }
}

#[derive(Default)]
struct BlockRegion {
    memory: Vec<Block>,
}

struct Argon2Instance {
    region: BlockRegion,
    pseudo_rands: Vec<u64>,
    passes: u32,        /* Number of passes */
    memory_blocks: u32, /* Number of blocks in memory */
    segment_length: u32,
    lane_length: u32,
    lanes: u32,
    type_: Argon2Type,
}

impl Default for Argon2Instance {
    fn default() -> Self {
        Self {
            region: Default::default(),
            pseudo_rands: vec![],
            passes: 0,
            memory_blocks: 0,
            segment_length: 0,
            lane_length: 0,
            lanes: 0,
            type_: Argon2Type::Argon2id,
        }
    }
}

impl Argon2Instance {
    fn new(
        memory_blocks: u32,
        segment_length: u32,
        type_: Argon2Type,
        t_cost: u32,
        lanes: u32,
    ) -> Self {
        let mut ret = Self {
            memory_blocks,
            segment_length,
            type_,
            lane_length: segment_length * ARGON2_SYNC_POINTS,
            passes: t_cost,
            lanes,
            ..Default::default()
        };
        ret.initialize();
        ret
    }

    fn initialize(&mut self) {
        // 1. Memory allocation
        self.pseudo_rands.resize(self.segment_length as usize, 0);

        self.region
            .memory
            .resize(self.memory_blocks as usize, Default::default());
    }
}

impl<'a> Argon2Context<'a> {
    #[allow(clippy::too_many_arguments)]
    fn new(
        output: &'a mut [u8],
        password: &'a [u8],
        salt: &'a [u8],
        secret: Option<&'a [u8]>,
        ad: Option<&'a [u8]>,
        t_cost: u32,
        m_cost: u32,
        parallelism: u32,
    ) -> Result<Self, Error> {
        // validate the inputs
        validate!(ARGON2_MIN_OUTLEN, ARGON2_MAX_OUTLEN, output.len(), "output");
        validate!(
            ARGON2_MIN_PWD_LENGTH,
            ARGON2_MAX_PWD_LENGTH,
            password.len(),
            "password"
        );
        validate!(
            ARGON2_MIN_SALT_LENGTH,
            ARGON2_MAX_SALT_LENGTH,
            salt.len(),
            "salt"
        );

        if let Some(secret) = secret {
            validate!(ARGON2_MIN_SECRET, ARGON2_MAX_SECRET, secret.len(), "secret");
        }
        if let Some(ad) = ad {
            validate!(ARGON2_MIN_AD_LENGTH, ARGON2_MAX_AD_LENGTH, ad.len(), "ad");
        }

        validate!(
            ARGON2_MIN_LANES,
            ARGON2_MAX_LANES,
            parallelism,
            "parallelism"
        );
        validate!(ARGON2_MIN_MEMORY, ARGON2_MAX_MEMORY, m_cost, "m_cost");
        validate!(ARGON2_MIN_TIME, ARGON2_MAX_TIME, t_cost, "t_cost");

        Ok(Self {
            output,
            password,
            salt,
            secret,
            ad,
            t_cost,
            m_cost,
            lanes: parallelism,
        })
    }
}

struct Argon2Context<'a> {
    output: &'a mut [u8],
    password: &'a [u8],
    salt: &'a [u8],
    secret: Option<&'a [u8]>,
    ad: Option<&'a [u8]>,
    t_cost: u32, // number of passes
    m_cost: u32, // amount of memory requested (KB)
    lanes: u32,  // number of lanes
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn argon2_hash(
    t_cost: u32,
    m_cost: u32,
    parallelism: u32,
    password: &[u8],
    salt: &[u8],
    secret: Option<&[u8]>,
    ad: Option<&[u8]>,
    output: &mut [u8],
    type_: Argon2Type,
) -> Result<(), Error> {
    let memory_blocks = if m_cost < 2 * ARGON2_SYNC_POINTS * parallelism {
        2 * ARGON2_SYNC_POINTS * parallelism
    } else {
        m_cost
    };

    let segment_length = memory_blocks / (parallelism * ARGON2_SYNC_POINTS);
    /* Ensure that all segments have equal length */
    let memory_blocks = segment_length * (parallelism * ARGON2_SYNC_POINTS);

    let context = Argon2Context::new(
        output,
        password,
        salt,
        secret,
        ad,
        t_cost,
        m_cost,
        parallelism,
    )?;

    let mut instance =
        Argon2Instance::new(memory_blocks, segment_length, type_, t_cost, parallelism);

    // 2. Initial hashing
    // H_0 + 8 extra bytes to produce the first blocks
    // Hashing all inputs
    let blockhash = argon2_initial_hash(&context, type_)?;
    // Zeroing 8 extra bytes

    // 3. Creating first blocks, we always have at least two blocks in a
    // slice
    argon2_fill_first_blocks(blockhash, &mut instance)?;

    for pass in 0..instance.passes {
        argon2_fill_memory_blocks(&mut instance, pass);
    }

    argon2_finalize(context, instance)
}

fn argon2_finalize(context: Argon2Context, instance: Argon2Instance) -> Result<(), Error> {
    let mut blockhash = Block::default();
    copy_block(
        &mut blockhash,
        &instance.region.memory[instance.lane_length.wrapping_sub(1) as usize],
    );

    /* XOR the last blocks */
    for l in 1..instance.lanes {
        let last_block_in_lane = (l * instance.lane_length + (instance.lane_length - 1)) as usize;
        xor_block(&mut blockhash, &instance.region.memory[last_block_in_lane]);
    }

    let mut blockhash_bytes = [0u8; ARGON2_BLOCK_SIZE];
    store_block(&mut blockhash_bytes, &blockhash);
    blake2b::longhash(context.output, &blockhash_bytes)
}

fn argon2_initial_hash(
    context: &Argon2Context,
    type_: Argon2Type,
) -> Result<[u8; ARGON2_PREHASH_SEED_LENGTH], Error> {
    let mut blake2b = blake2b::State::init(ARGON2_PREHASH_DIGEST_LENGTH as u8, None, None, None)?;
    blake2b.update(&context.lanes.to_le_bytes());
    blake2b.update(&(context.output.len() as u32).to_le_bytes());
    blake2b.update(&context.m_cost.to_le_bytes());
    blake2b.update(&context.t_cost.to_le_bytes());
    blake2b.update(&ARGON2_VERSION_NUMBER.to_le_bytes());
    blake2b.update(&(type_ as u32).to_le_bytes());
    blake2b.update(&(context.password.len() as u32).to_le_bytes());
    if !context.password.is_empty() {
        blake2b.update(context.password);
    }
    blake2b.update(&(context.salt.len() as u32).to_le_bytes());
    if !context.salt.is_empty() {
        blake2b.update(context.salt);
    }

    match context.secret {
        Some(secret) => {
            blake2b.update(&(secret.len() as u32).to_le_bytes());
            if !secret.is_empty() {
                blake2b.update(secret);
            }
        }
        None => blake2b.update(&(0u32.to_le_bytes())), // secret, unused
    }
    match context.ad {
        Some(ad) => {
            blake2b.update(&(ad.len() as u32).to_le_bytes());
            if !ad.is_empty() {
                blake2b.update(ad);
            }
        }
        None => blake2b.update(&(0u32.to_le_bytes())), // ad, unused
    }

    let mut blockhash = [0u8; ARGON2_PREHASH_SEED_LENGTH];
    blake2b.finalize(&mut blockhash[..ARGON2_PREHASH_DIGEST_LENGTH])?;
    Ok(blockhash)
}

#[derive(Default)]
struct Argon2Position {
    pass: u32,
    lane: u32,
    slice: u8,
    index: u32,
}

fn argon2_fill_memory_blocks(instance: &mut Argon2Instance, pass: u32) {
    let mut position = Argon2Position {
        pass,
        ..Default::default()
    };
    for s in 0..ARGON2_SYNC_POINTS {
        position.slice = s as u8;
        for l in 0..instance.lanes {
            position.lane = l;
            position.index = 0;
            fill_segment(instance, &mut position);
        }
    }
}

fn fill_segment(instance: &mut Argon2Instance, position: &mut Argon2Position) {
    let data_independent_addressing = !(instance.type_ == Argon2Type::Argon2id
        && (position.pass != 0 || position.slice as u32 >= (ARGON2_SYNC_POINTS / 2)));

    if data_independent_addressing {
        generate_addresses(instance, position);
    }

    let starting_index = if position.pass == 0 && position.slice == 0 {
        2
    } else {
        0
    };

    let mut curr_offset = position.lane * instance.lane_length
        + position.slice as u32 * instance.segment_length
        + starting_index;

    let mut prev_offset = if curr_offset % instance.lane_length == 0 {
        curr_offset + instance.lane_length - 1
    } else {
        curr_offset - 1
    };

    for i in starting_index..instance.segment_length {
        if curr_offset % instance.lane_length == 1 {
            prev_offset = curr_offset - 1;
        }

        let pseudo_rand = if data_independent_addressing {
            instance.pseudo_rands[i as usize]
        } else {
            instance.region.memory[prev_offset as usize].v[0]
        };

        let ref_lane = if position.pass == 0 && position.slice == 0 {
            position.lane as u64
        } else {
            (pseudo_rand >> 32) % instance.lanes as u64
        };

        position.index = i;
        let ref_index = index_alpha(
            instance,
            position,
            (pseudo_rand & 0xFFFFFFFF) as u32,
            ref_lane == position.lane as u64,
        );

        let prev_block = &instance.region.memory[prev_offset as usize];
        let ref_block = &instance.region.memory
            [(instance.lane_length as u64 * ref_lane + ref_index as u64) as usize];
        let mut next_block = instance.region.memory[curr_offset as usize].clone();

        fill_block(prev_block, ref_block, &mut next_block, position.pass != 0);

        instance.region.memory[curr_offset as usize] = next_block;

        curr_offset += 1;
        prev_offset += 1;
    }
}

fn index_alpha(
    instance: &Argon2Instance,
    position: &Argon2Position,
    pseudo_rand: u32,
    same_lane: bool,
) -> u32 {
    /*
     * Pass 0:
     *      This lane : all already finished segments plus already constructed
     * blocks in this segment
     *      Other lanes : all already finished segments
     * Pass 1+:
     *      This lane : (SYNC_POINTS - 1) last segments plus already constructed
     * blocks in this segment
     *      Other lanes : (SYNC_POINTS - 1) last segments
     */
    let reference_area_size = if position.pass == 0 {
        /* First pass */
        if position.slice == 0 {
            /* First slice */
            position.index - 1 /* all but the previous */
        } else if same_lane {
            /* The same lane => add current segment */
            position.slice as u32 * instance.segment_length + position.index - 1
        } else if position.index == 0 {
            position.slice as u32 * instance.segment_length - 1
        } else {
            position.slice as u32 * instance.segment_length
        }
        /* Second pass */
    } else if same_lane {
        instance.lane_length - instance.segment_length + position.index - 1
    } else if position.index == 0 {
        instance.lane_length - instance.segment_length - 1
    } else {
        instance.lane_length - instance.segment_length
    };

    /* 1.2.4. Mapping pseudo_rand to 0..<reference_area_size-1> and produce
     * relative position */
    let mut relative_position = pseudo_rand;
    relative_position =
        ((relative_position as u64).wrapping_mul(relative_position as u64) >> 32) as u32;
    relative_position = reference_area_size
        - 1
        - ((reference_area_size as u64).wrapping_mul(relative_position as u64) >> 32) as u32;

    /* 1.2.5 Computing starting position */
    let start_position = if position.pass != 0 {
        if position.slice as u32 == ARGON2_SYNC_POINTS - 1 {
            0
        } else {
            (position.slice as u32 + 1) * instance.segment_length
        }
    } else {
        0
    };

    /* 1.2.6. Computing absolute position */
    (start_position + relative_position) % instance.lane_length /* absolute position */
}

fn generate_addresses(instance: &mut Argon2Instance, position: &Argon2Position) {
    let mut input_block = Block::default();
    let zero_block = Block::default();
    let mut address_block = Block::default();
    let mut tmp_block = Block::default();

    input_block.v[0] = position.pass as u64;
    input_block.v[1] = position.lane as u64;
    input_block.v[2] = position.slice as u64;
    input_block.v[3] = instance.memory_blocks as u64;
    input_block.v[4] = instance.passes as u64;
    input_block.v[5] = instance.type_ as u64;

    for i in 0..instance.segment_length {
        if i % ARGON2_ADDRESSES_IN_BLOCK == 0 {
            input_block.v[6] += 1;
            tmp_block.v.fill(0);
            address_block.v.fill(0);
            fill_block(&zero_block, &input_block, &mut tmp_block, true);
            fill_block(&zero_block, &tmp_block, &mut address_block, true);
        }

        instance.pseudo_rands[i as usize] =
            address_block.v[(i % ARGON2_ADDRESSES_IN_BLOCK) as usize];
    }
}

#[inline]
fn fill_block(prev_block: &Block, ref_block: &Block, next_block: &mut Block, with_xor: bool) {
    let mut block_r = Block::default();
    let mut block_tmp = Block::default();

    copy_block(&mut block_r, ref_block);
    xor_block(&mut block_r, prev_block);
    copy_block(&mut block_tmp, &block_r);
    if with_xor {
        xor_block(&mut block_tmp, next_block);
    }

    for i in 0..8 {
        blake2_round_nomsg(
            &mut block_r,
            16 * i,
            16 * i + 1,
            16 * i + 2,
            16 * i + 3,
            16 * i + 4,
            16 * i + 5,
            16 * i + 6,
            16 * i + 7,
            16 * i + 8,
            16 * i + 9,
            16 * i + 10,
            16 * i + 11,
            16 * i + 12,
            16 * i + 13,
            16 * i + 14,
            16 * i + 15,
        );
    }

    for i in 0..8 {
        blake2_round_nomsg(
            &mut block_r,
            2 * i,
            2 * i + 1,
            2 * i + 16,
            2 * i + 17,
            2 * i + 32,
            2 * i + 33,
            2 * i + 48,
            2 * i + 49,
            2 * i + 64,
            2 * i + 65,
            2 * i + 80,
            2 * i + 81,
            2 * i + 96,
            2 * i + 97,
            2 * i + 112,
            2 * i + 113,
        );
    }

    copy_block(next_block, &block_tmp);
    xor_block(next_block, &block_r);
}

#[inline]
#[allow(clippy::too_many_arguments)]
fn blake2_round_nomsg(
    block: &mut Block,
    v0: usize,
    v1: usize,
    v2: usize,
    v3: usize,
    v4: usize,
    v5: usize,
    v6: usize,
    v7: usize,
    v8: usize,
    v9: usize,
    v10: usize,
    v11: usize,
    v12: usize,
    v13: usize,
    v14: usize,
    v15: usize,
) {
    let g = |block: &mut Block, a, b, c, d| {
        block.v[a] = fblamka(block.v[a], block.v[b]);
        block.v[d] = rotr64(block.v[d] ^ block.v[a], 32);
        block.v[c] = fblamka(block.v[c], block.v[d]);
        block.v[b] = rotr64(block.v[b] ^ block.v[c], 24);
        block.v[a] = fblamka(block.v[a], block.v[b]);
        block.v[d] = rotr64(block.v[d] ^ block.v[a], 16);
        block.v[c] = fblamka(block.v[c], block.v[d]);
        block.v[b] = rotr64(block.v[b] ^ block.v[c], 63);
    };

    g(block, v0, v4, v8, v12);
    g(block, v1, v5, v9, v13);
    g(block, v2, v6, v10, v14);
    g(block, v3, v7, v11, v15);
    g(block, v0, v5, v10, v15);
    g(block, v1, v6, v11, v12);
    g(block, v2, v7, v8, v13);
    g(block, v3, v4, v9, v14);
}

#[inline]
fn fblamka(x: u64, y: u64) -> u64 {
    let m = 0xFFFFFFFFu64;
    let xy = (x & m) * (y & m);
    x.wrapping_add(y).wrapping_add(2u64.wrapping_mul(xy))
}

fn copy_block(dst: &mut Block, src: &Block) {
    dst.v.copy_from_slice(&src.v)
}

/* XOR @src onto @dst bytewise */
fn xor_block(dst: &mut Block, src: &Block) {
    for i in 0..(dst.v.len()) {
        dst.v[i] ^= src.v[i];
    }
}

fn argon2_fill_first_blocks(
    mut blockhash: [u8; ARGON2_PREHASH_SEED_LENGTH],
    instance: &mut Argon2Instance,
) -> Result<(), Error> {
    let mut blockhash_bytes = [0u8; ARGON2_BLOCK_SIZE];

    for l in 0..instance.lanes {
        blockhash[ARGON2_PREHASH_DIGEST_LENGTH..(ARGON2_PREHASH_DIGEST_LENGTH + 4)]
            .copy_from_slice(&[0, 0, 0, 0]);
        blockhash[(ARGON2_PREHASH_DIGEST_LENGTH + 4)..(ARGON2_PREHASH_DIGEST_LENGTH + 8)]
            .copy_from_slice(&l.to_le_bytes());
        blake2b::longhash(&mut blockhash_bytes, &blockhash)?;

        load_block(
            &mut instance.region.memory[(l * instance.lane_length) as usize],
            &blockhash_bytes,
        );

        blockhash[ARGON2_PREHASH_DIGEST_LENGTH..(ARGON2_PREHASH_DIGEST_LENGTH + 4)]
            .copy_from_slice(&[1, 0, 0, 0]);
        blake2b::longhash(&mut blockhash_bytes, &blockhash)?;
        load_block(
            &mut instance.region.memory[(l * instance.lane_length + 1) as usize],
            &blockhash_bytes,
        );
    }

    blockhash_bytes.zeroize();
    blockhash.zeroize();

    Ok(())
}

fn load_block(block: &mut Block, input: &[u8]) {
    for i in 0..ARGON2_QWORDS_IN_BLOCK {
        let start = i * 8;
        let end = start + 8;
        block.v[i] = load_u64_le(&input[start..end]);
    }
}

fn store_block(output: &mut [u8], block: &Block) {
    for i in 0..ARGON2_QWORDS_IN_BLOCK {
        let start = i * 8;
        let end = start + 8;
        output[start..end].copy_from_slice(&block.v[i].to_le_bytes());
    }
}

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

    #[test]
    fn test_vector_argon2i() {
        let password = [1u8; 32];
        let salt = [2u8; 16];
        let secret = [3u8; 8];
        let ad = [4u8; 12];

        let mut hash = [0u8; 32];

        super::argon2_hash(
            3,
            32,
            4,
            &password,
            &salt,
            Some(&secret),
            Some(&ad),
            &mut hash,
            Argon2Type::Argon2i,
        )
        .expect("argon2_hash failed");

        assert_eq!(
            hash,
            [
                0xc8, 0x14, 0xd9, 0xd1, 0xdc, 0x7f, 0x37, 0xaa, 0x13, 0xf0, 0xd7, 0x7f, 0x24, 0x94,
                0xbd, 0xa1, 0xc8, 0xde, 0x6b, 0x01, 0x6d, 0xd3, 0x88, 0xd2, 0x99, 0x52, 0xa4, 0xc4,
                0x67, 0x2b, 0x6c, 0xe8,
            ]
        );
    }

    extern "C" {
        fn argon2_hash(
            t_cost: u32,
            m_cost: u32,
            parallelism: u32,
            pwd: *const u8,
            pwdlen: usize,
            salt: *const u8,
            saltlen: usize,
            hash: *mut u8,
            hashlen: usize,
            encoded: *mut u8,
            encodedlen: usize,
            type_: i32,
        ) -> i32;
    }

    #[test]
    fn test_vector_argon2id() {
        let password = [1u8; 32];
        let salt = [2u8; 16];
        let secret = [3u8; 8];
        let ad = [4u8; 12];

        let mut hash = [0u8; 32];

        super::argon2_hash(
            3,
            32,
            4,
            &password,
            &salt,
            Some(&secret),
            Some(&ad),
            &mut hash,
            Argon2Type::Argon2id,
        )
        .expect("argon2_hash failed");

        assert_eq!(
            hash,
            [
                0x0d, 0x64, 0x0d, 0xf5, 0x8d, 0x78, 0x76, 0x6c, 0x08, 0xc0, 0x37, 0xa3, 0x4a, 0x8b,
                0x53, 0xc9, 0xd0, 0x1e, 0xf0, 0x45, 0x2d, 0x75, 0xb6, 0x5e, 0xb5, 0x25, 0x20, 0xe9,
                0x6b, 0x01, 0xe6, 0x59,
            ]
        );
    }

    #[test]
    fn test_vector_argon2id_so() {
        let password = [1u8; 32];
        let salt = [2u8; 16];

        let mut hash = [0u8; 32];
        let mut so_hash = [0u8; 32];

        super::argon2_hash(
            3,
            32,
            4,
            &password,
            &salt,
            None,
            None,
            &mut hash,
            Argon2Type::Argon2id,
        )
        .expect("argon2_hash failed");

        unsafe {
            argon2_hash(
                3,
                32,
                4,
                password.as_ptr(),
                password.len(),
                salt.as_ptr(),
                salt.len(),
                so_hash.as_mut_ptr(),
                so_hash.len(),
                std::ptr::null_mut(),
                0,
                Argon2Type::Argon2id as i32,
            );
        }

        assert_eq!(hash, so_hash);

        assert_eq!(
            hash,
            [
                3, 170, 185, 101, 193, 32, 1, 201, 215, 208, 210, 222, 51, 25, 44, 4, 148, 182,
                132, 187, 20, 129, 150, 215, 60, 29, 241, 172, 175, 109, 12, 46
            ]
        );
    }
}
