#include "sha3.h"
#include "obtc.h"




void CSHA3_256_Write(CSHA3_256 *p, const unsigned char* data, size_t len) {
    sha3_update(&p->context, data, len);
    //return *this;
}

void CSHA3_256_Finalize(CSHA3_256 *p, unsigned char hash[OUTPUT_SIZE]) {
    sha3_final(hash, &p->context);
}

/*void CSHA3_256_Reset(Obtc_t *Obtc, CSHA3_256 *p) {
    sha3_init(Obtc,&p->context, OUTPUT_SIZE);
    //return *this;
}*/

void CSHA3_256_init(Obtc_t *Obtc, CSHA3_256 *p) {

	sha3_init(Obtc, &p->context, OUTPUT_SIZE);

	p->Write = CSHA3_256_Write;
	p->Finalize = CSHA3_256_Finalize;
	//p->Reset = CSHA3_256_Reset;
}

void CSHA3_256_CSHA3_256(Obtc_t *Obtc,CSHA3_256 *p) {
	sha3_init(Obtc,&p->context, OUTPUT_SIZE);

}

	
void CHeavyHash_Write(CHeavyHash *p, const unsigned char* data, size_t len) {
		p->hasher.Write(&p->hasher,data, len);
		//sha3_update(&CSHA3_256_p.context, data, len);
		//CSHA3_256_Write(&CSHA3_256_p, data, OUTPUT_SIZE);
	}

void CHeavyHash_Finalize(Obtc_t *Obtc, CHeavyHash *p, unsigned char hash[OUTPUT_SIZE]) {
    uint256 hash_first;
	uint8_t a[32];
	
    p->hasher.Finalize(&p->hasher,&Obtc->g_hash_first.bb.data[0]);
    memcpy(a,&Obtc->g_hash_first.bb.data[0],32);

    uint256 product = MultiplyUsing4bitPrecision(p->matrix, Obtc->g_hash_first);

    uint256 hash_xored;
    for (size_t i = 0; i < OUTPUT_SIZE; ++i) {
        //hash_xored.begin()[i] = hash_first.begin()[i] ^ product.begin()[i];
        hash_xored.bb.data[i] = Obtc->g_hash_first.bb.data[i] ^ product.bb.data[i];
        
       
    }
	
    uint8_t temp[200]={
        0x16,0x19,0x32,0x7d,0x10,0xb9,0xda,0x35,0x54,0x9a,0xe0,0x31,0x2f,0x9f,0xc6,0x15,0x92,0xbb,0x39,0x9d,
        0xb5,0x29,0x0c,0x0a,0x47,0xc3,0x9f,0x67,0x51,0x12,0xc2,0x2e,0xc7,0x76,0xc5,0x04,0x84,0x81,0xb9,0x57,
        0xb9,0x92,0xf2,0xd3,0x7b,0x34,0xca,0x58,0xea,0x8f,0xdb,0x80,0xba,0xc4,0x6d,0x39,0x7e,0x8f,0x1d,0xb1,
        0x77,0x65,0xcc,0x07,0x87,0xe9,0x61,0xb0,0x36,0xbc,0x94,0x16,0x77,0x4c,0x86,0x83,0x54,0x34,0xf2,0xb0,
        0x4e,0xf7,0x4b,0x3a,0x99,0xcd,0xb0,0x44,0x2e,0xc6,0x5b,0xd3,0x56,0x24,0x93,0xe4,0x6c,0x6b,0x7d,0x01,
        0xa7,0x69,0xcc,0x3d,0xd3,0x1f,0x4c,0xc3,0x54,0xc1,0x8c,0x3f,0xf4,0x31,0xc0,0x5d,0xd0,0xa9,0xa2,0x26,
        0xa0,0xbc,0xaa,0x9f,0x79,0x2a,0x3d,0x0c,0x80,0x39,0xf9,0xa6,0x0d,0xcf,0x6a,0x48,0x5e,0x21,0x90,0x40,
        0x25,0x0f,0xc4,0x62,0xc1,0x00,0xff,0x2a,0x93,0x89,0x35,0xba,0x72,0xc7,0xd8,0x2e,0x14,0xf3,0x40,0x69,
        0xe7,0x20,0xe0,0xdf,0x44,0xee,0xce,0xde,0x11,0xa7,0x5f,0x4c,0x80,0x05,0x64,0x98,0x7a,0x14,0xff,0x48,
        0x16,0xc7,0xf8,0xee,0x79,0x62,0x9b,0x0e,0x2f,0x9f,0x42,0x16,0x3a,0xd7,0x4c,0x52,0xb2,0x24,0x85,0x09,
    };
    for(int i = 0 ;i< 200 ;i++)Obtc->const_data[i] = temp[i];

   // CSHA3_256().Write(hash_xored.begin(), OUTPUT_SIZE).Finalize(hash);
   
	CSHA3_256_CSHA3_256(Obtc, &p->hasher);
	CSHA3_256_Write(&p->hasher, &hash_xored.bb.data[0], OUTPUT_SIZE);
	CSHA3_256_Finalize(&p->hasher, hash) ;
}

void CHeavyHash_Reset(CHeavyHash *p, uint64_t matrix_[64*64]) {
	for (int i = 0; i < 64*64; ++i)
		p->matrix[i] = matrix_[i];
}

void CHeavyHash_init(Obtc_t *Obtc, CHeavyHash *p, uint64_t matrix_[64*64]){

	p->Write = CHeavyHash_Write;
	p->Finalize = CHeavyHash_Finalize;
	p->Reset = CHeavyHash_Reset;

	p->hasher.Write = CSHA3_256_Write;
	p->hasher.Finalize = CSHA3_256_Finalize;
	//p->hasher.Reset = CSHA3_256_Reset;

	sha3_init(Obtc, &p->hasher.context, OUTPUT_SIZE);

	for (int i = 0; i < 64*64; ++i)
		p->matrix[i] = matrix_[i];

}




void MultiplyMatrices(uint64_t matrix[64*64], uint64_t vector[64], uint64_t product[64]){
    for (int i = 0; i < 64; ++i) {
        for (int j = 0; j < 64; ++j) {
            product[i] += matrix[64*i + j]*vector[j];
        }
    }
}

uint256 MultiplyUsing4bitPrecision(uint64_t matrix[64*64], const uint256 hash) {
    // conversion to matrix with 4 bit values
    uint64_t vector[64] = {0};
    ConvertTo4BitPrecisionVector(hash, vector);

    // perform matrix multiplication
    uint64_t product[64] = {0};
    MultiplyMatrices(matrix, vector, product);
    for (int i = 0; i < 64; ++i) {
        product[i] >>= 10;
    }
    return Convert4bitVectorToUint(product);
}

void ConvertTo4BitPrecisionVector(uint256 bit_sequence, uint64_t vector[64]) {
    int index = 0;
	int i;
	
    for (i = 0; i < WIDTH; i++) {
		
        vector[index] = bit_sequence.bb.data[i] >> 4;
        vector[index+1] = bit_sequence.bb.data[i] & 0xF;
        index += 2;
    }
}

uint256 Convert4bitVectorToUint(const uint64_t x[64]) {
    uint256 bit_sequence;
    int index = 0;
	int i;
	
    for (i = 0; i < WIDTH; i++) {
        bit_sequence.bb.data[i] =  ( x[index] << 4) |  x[index+1];
        index += 2;
    }

    return bit_sequence;
}
