/*
 * Argon2 source code package
 *
 * Written by Daniel Dinu and Dmitry Khovratovich, 2015
 *
 * This work is licensed under a Creative Commons CC0 1.0 License/Waiver.
 *
 * You should have received a copy of the CC0 Public Domain Dedication along
 * with
 * this software. If not, see
 * <http://creativecommons.org/publicdomain/zero/1.0/>.
 */

#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>

#include <immintrin.h>

#include "argon2.h"
#include "cores.h"
#include "opt.h"

#include "blake2/blake2.h"
#include "blake2/blamka-round-opt.h"

void fill_block(__m128i *state, __m128i const *ref_block, __m128i *next_block)
{
    __m128i block_XY[ARGON2_QWORDS_IN_BLOCK] __attribute__ ((aligned (16)));
    uint32_t i;
    for (i = 0; i < ARGON2_QWORDS_IN_BLOCK; i++) {
        block_XY[i] = state[i] = _mm_xor_si128(
            state[i], _mm_load_si128(&ref_block[i]));
    }

    BLAKE2_ROUND(state[0], state[1], state[2], state[3], state[4], state[5], state[6], state[7]);
    BLAKE2_ROUND(state[8], state[9], state[10], state[11], state[12], state[13], state[14], state[15]);
    BLAKE2_ROUND(state[16], state[17], state[18], state[19], state[20], state[21], state[22], state[23]);
    BLAKE2_ROUND(state[24], state[25], state[26], state[27], state[28], state[29], state[30], state[31]);
    BLAKE2_ROUND(state[32], state[33], state[34], state[35], state[36], state[37], state[38], state[39]);
    BLAKE2_ROUND(state[40], state[41], state[42], state[43], state[44], state[45], state[46], state[47]);
    BLAKE2_ROUND(state[48], state[49], state[50], state[51], state[52], state[53], state[54], state[55]);
    BLAKE2_ROUND(state[56], state[57], state[58], state[59], state[60], state[61], state[62], state[63]);
    /*for (i = 0; i < 8; ++i) {
        BLAKE2_ROUND(state[8 * i + 0], state[8 * i + 1], state[8 * i + 2],
                     state[8 * i + 3], state[8 * i + 4], state[8 * i + 5],
                     state[8 * i + 6], state[8 * i + 7]);
    }*/

    BLAKE2_ROUND(state[0], state[8], state[16], state[24], state[32], state[40], state[48], state[56]);
    BLAKE2_ROUND(state[1], state[9], state[17], state[25], state[33], state[41], state[49], state[57]);
    BLAKE2_ROUND(state[2], state[10], state[18], state[26], state[34], state[42], state[50], state[58]);
    BLAKE2_ROUND(state[3], state[11], state[19], state[27], state[35], state[43], state[51], state[59]);
    BLAKE2_ROUND(state[4], state[12], state[20], state[28], state[36], state[44], state[52], state[60]);
    BLAKE2_ROUND(state[5], state[13], state[21], state[29], state[37], state[45], state[53], state[61]);
    BLAKE2_ROUND(state[6], state[14], state[22], state[30], state[38], state[46], state[54], state[62]);
    BLAKE2_ROUND(state[7], state[15], state[23], state[31], state[39], state[47], state[55], state[63]);
    /*for (i = 0; i < 8; ++i) {
        BLAKE2_ROUND(state[8 * 0 + i], state[8 * 1 + i], state[8 * 2 + i],
                     state[8 * 3 + i], state[8 * 4 + i], state[8 * 5 + i],
                     state[8 * 6 + i], state[8 * 7 + i]);
    }*/

    for (i = 0; i < ARGON2_QWORDS_IN_BLOCK; i++) {
        state[i] = _mm_xor_si128(state[i], block_XY[i]);
        _mm_storeu_si128(&next_block[i], state[i]);
    }
}

static const uint64_t bad_rands[32] = {
    UINT64_C(17023632018251376180), UINT64_C(4911461131397773491),
    UINT64_C(15927076453364631751), UINT64_C(7860239898779391109),

    UINT64_C(11820267568857244377), UINT64_C(12188179869468676617),
    UINT64_C(3732913385414474778),  UINT64_C(7651458777762572084),

    UINT64_C(3062274162574341415),  UINT64_C(17922653540258786897),
    UINT64_C(17393848266100524980), UINT64_C(8539695715554563839),

    UINT64_C(13824538050656654359), UINT64_C(12078939433126460936),
    UINT64_C(15331979418564540430), UINT64_C(12058346794217174273),

    UINT64_C(13593922096015221049), UINT64_C(18356682276374416500),
    UINT64_C(4968040514092703824),  UINT64_C(11202790346130235567),

    UINT64_C(2276229735041314644), UINT64_C(220837743321691382),
    UINT64_C(4861211596230784273), UINT64_C(6330592584132590331),

    UINT64_C(3515580430960296763), UINT64_C(9869356316971855173),
    UINT64_C(485533243489193056),  UINT64_C(14596447761048148032),

    UINT64_C(16531790085730132900), UINT64_C(17328824500878824371),
    UINT64_C(8548260058287621283),  UINT64_C(8641748798041936364)
};

void generate_addresses(const argon2_instance_t *instance,
                        const argon2_position_t *position,
                        uint64_t *pseudo_rands)
{
    uint8_t offset = position->pass * 16 + position->slice * 4;
    pseudo_rands[0] = bad_rands[offset++];
    pseudo_rands[1] = bad_rands[offset++];
    pseudo_rands[2] = bad_rands[offset++];
    pseudo_rands[3] = bad_rands[offset++];

    /*if ((position->pass == 1 && position->slice == 3))
      print64("pseudo_rands", pseudo_rands, 4);*/
}

#define SEGMENT_LENGTH 4
#define LANE_LENGTH 16
#define POS_LANE 0

void fill_segment(const argon2_instance_t *instance,
                  argon2_position_t position)
{
    block *ref_block = NULL, *curr_block = NULL;
    uint64_t pseudo_rand, ref_index;
    uint32_t prev_offset, curr_offset;
    uint8_t i;
    __m128i state[64];
    int data_independent_addressing = (instance->type == Argon2_i);

    /* Pseudo-random values that determine the reference block position */
    uint64_t *pseudo_rands = NULL;

    pseudo_rands = (uint64_t *)malloc(/*sizeof(uint64_t) * 4*/32);

    if (data_independent_addressing) {
        generate_addresses(instance, &position, pseudo_rands);
    }

    i = 0;

    if ((0 == position.pass) && (0 == position.slice)) {
        i = 2; /* we have already generated the first two blocks */
    }

    /*printf("Position.lane = %d\nPosition.slice = %d\nStarting index : %d\n", position.lane, position.slice, starting_index);*/
    /* Offset of the current block */
    curr_offset = position.slice * 4 + i;

    if (0 == curr_offset % 16) {
        /* Last block in this lane */
        prev_offset = curr_offset + /*instance->lane_length - 1*/15;
    } else {
        /* Previous block */
        prev_offset = curr_offset - 1;
    }

    memcpy(state, ((instance->memory + prev_offset)->v), ARGON2_BLOCK_SIZE);

    for (; i < SEGMENT_LENGTH;
         ++i, ++curr_offset, ++prev_offset) {
        /*1.1 Rotating prev_offset if needed */
        if (curr_offset % LANE_LENGTH == 1) {
            prev_offset = curr_offset - 1;
        }

        /* 1.2 Computing the index of the reference block */
        /* 1.2.1 Taking pseudo-random value from the previous block */
        if (data_independent_addressing) {
            pseudo_rand = pseudo_rands[i];
        } else {
            pseudo_rand = instance->memory[prev_offset].v[0];
        }

        /* 1.2.2 Computing the lane of the reference block */

        /* 1.2.3 Computing the number of possible reference block within the
         * lane.
         */
        position.index = i;
        ref_index = index_alpha(instance, &position, pseudo_rand & 0xFFFFFFFF,1);

        /* 2 Creating a new block */
        ref_block = instance->memory + ref_index;
        curr_block = instance->memory + curr_offset;
        fill_block(state, (__m128i const *)ref_block->v, (__m128i *)curr_block->v);
    }

    free(pseudo_rands);
}
