#include "okv.h"
#include <openssl/rand.h>
#include <omp.h>

#include <math.h>

//opti! adjust size
#define MAX_DB_SIZE 1000010

vatRow db[MAX_DB_SIZE];
int dbSize = 0;

uint128_t seed;

EVP_CIPHER_CTX *ctx[MAX_THREADS];


void initializeServer(int numThreads){
    
    for(int i = 0; i < numThreads; i++){
        //set fixed key
        if(!(ctx[i] = EVP_CIPHER_CTX_new())) 
            printf("errors occured in creating context\n");
        unsigned char *aeskey = (unsigned char*) "0123456789123456";
        if(1 != EVP_EncryptInit_ex(ctx[i], EVP_aes_128_ecb(), NULL, aeskey, NULL))
            printf("errors occured in init\n");
        EVP_CIPHER_CTX_set_padding(ctx[i], 0);
    }
    
    memset(&seed, 0, 16);
}

//creates a new DB
void createDb(int isLeader, int dataSize){
    vatRow entry;

    if (isLeader == 1) {
        entry.newKeyA = getRandomBlock();

        if(!(entry.rowKeyA = EVP_CIPHER_CTX_new())) 
            printf("errors occured in creating context\n");
        if(1 != EVP_EncryptInit_ex(entry.rowKeyA, EVP_aes_128_ecb(), NULL, (uint8_t*)&entry.newKeyA, NULL))
            printf("errors occured in init\n");
        EVP_CIPHER_CTX_set_padding(entry.rowKeyA, 0);
        
    } else {
        entry.newKeyB = getRandomBlock();

        if(!(entry.rowKeyB = EVP_CIPHER_CTX_new())) 
            printf("errors occured in creating context\n");
        if(1 != EVP_EncryptInit_ex(entry.rowKeyB, EVP_aes_128_ecb(), NULL, (uint8_t*)&entry.newKeyB, NULL))
            printf("errors occured in init\n");
        EVP_CIPHER_CTX_set_padding(entry.rowKeyB, 0);
    }
    
    entry.dataSize = dataSize;
    entry.data = malloc(dataSize);
    entry.mask = malloc(dataSize);
    memset(entry.mask, 0 , dataSize);
    memset(entry.data, 0 , dataSize);
    db[dbSize] = entry;
    int i = dbSize;//to make code below work without changing stuff
    dbSize = dbSize + 1;
    
    
    //now do the encryption/rerandomization for this entry so it can be retrieved normally
    uint8_t* maskTemp = (uint8_t*) malloc(dataSize+16);
    uint8_t* seedTemp = (uint8_t*) malloc(dataSize+16);
    //get rerandomization mask
    for(int j = 0; j < (db[i].dataSize+16)/16; j++){
        memcpy(&seedTemp[16*j], &seed, 16);
        seedTemp[16*j] = seedTemp[16*j] ^ j;
    }
    int len;
    
    if (isLeader == 1) {
       if(1 != EVP_EncryptUpdate(db[i].rowKeyA, maskTemp, &len, seedTemp, ((dataSize-1)|15)+1))
        printf("errors occured in rerandomization of entry %d\n", i); 
    } else {
        if(1 != EVP_EncryptUpdate(db[i].rowKeyB, maskTemp, &len, seedTemp, ((dataSize-1)|15)+1))
        printf("errors occured in rerandomization of entry %d\n", i); 
    }
    

    //xor data into db and rerandomize db entry
    for(int j = 0; j < dataSize; j++){
        db[i].data[j] = db[i].data[j] ^ maskTemp[j];
        db[i].mask[j] = maskTemp[j];
    }
    free(maskTemp);
    free(seedTemp);
}

void xorIn(int i, uint8_t *data){
    for(int j = 0; j < db[i].dataSize; j++){
        db[i].data[j] = db[i].data[j] ^ data[j];
    }
}

void resetDb() {
    memset(db, 0, MAX_DB_SIZE);
    dbSize = 0;
}

//read an entry
void readData(int index, uint8_t *data){
    memcpy(data, db[index].data, db[index].dataSize);
}

//gets the seed
void readSeed(uint8_t *seedIn){
    memcpy(seedIn, &seed, 16);
}

//returns a uint128_t representation of the input
uint128_t getUint128_t(int i){
    return (uint128_t) i;
}

//decrypt and recover a row
void decryptRow(int localIndex, uint8_t *out, uint8_t *dataA, uint8_t *dataB, uint8_t *seedA, uint8_t *seedB){
    int len;
    
    uint8_t *maskA = (uint8_t*) malloc(db[localIndex].dataSize+16);
    uint8_t *maskB = (uint8_t*) malloc(db[localIndex].dataSize+16);
    uint8_t *seedTempA = (uint8_t*) malloc(db[localIndex].dataSize+16);
    uint8_t *seedTempB = (uint8_t*) malloc(db[localIndex].dataSize+16);
    
    //get the masks
    for(int j = 0; j < (db[localIndex].dataSize+16)/16; j++){
            memcpy(&seedTempA[16*j], seedA, 16);
            seedTempA[16*j] = seedTempA[16*j] ^ j;
    }
    if(1 != EVP_EncryptUpdate(db[localIndex].rowKeyA, maskA, &len, seedTempA, ((db[localIndex].dataSize-1)|15)+1))
        printf("errors occured in rerandomization of entry %d\n", localIndex);
    for(int j = 0; j < (db[localIndex].dataSize+16)/16; j++){
            memcpy(&seedTempB[16*j], seedB, 16);
            seedTempB[16*j] = seedTempB[16*j] ^ j;
    }
    if(1 != EVP_EncryptUpdate(db[localIndex].rowKeyB, maskB, &len, seedTempB, ((db[localIndex].dataSize-1)|15)+1))
        printf("errors occured in rerandomization of entry %d\n", localIndex);
    

    for(int i = 0; i < db[localIndex].dataSize; i++){
        out[i] = dataA[i] ^ dataB[i] ^ maskA[i] ^ maskB[i];
    }
    
    free(maskA);
    free(maskB);
    free(seedTempA);
    free(seedTempB);

}

//gets the block that was used to calculate the cipher
void getCipher(int isLeader, int i, uint8_t *array) {
    if (isLeader == 1) {
        memcpy(array, &db[i].newKeyA, 16);
    } else {
        memcpy(array, &db[i].newKeyB, 16);
    }
}

//puts in the block that was used to calculate the cipher on the other server and calculates the missing one
void putCipher(int isLeader, int i, uint8_t *array) {
    if (isLeader == 0) {
        memcpy(&db[i].newKeyA, array, 16);

        if(!(db[i].rowKeyA = EVP_CIPHER_CTX_new())) 
            printf("errors occured in creating context\n");
        if(1 != EVP_EncryptInit_ex(db[i].rowKeyA, EVP_aes_128_ecb(), NULL, (uint8_t*)&db[i].newKeyA, NULL))
            printf("errors occured in init\n");
        EVP_CIPHER_CTX_set_padding(db[i].rowKeyA, 0);
    } else {
        memcpy(&db[i].newKeyB, array, 16);
        
        if(!(db[i].rowKeyB = EVP_CIPHER_CTX_new())) 
            printf("errors occured in creating context\n");
        if(1 != EVP_EncryptInit_ex(db[i].rowKeyB, EVP_aes_128_ecb(), NULL, (uint8_t*)&db[i].newKeyB, NULL))
            printf("errors occured in init\n");
        EVP_CIPHER_CTX_set_padding(db[i].rowKeyB, 0);
    }
}