#ifndef straighten_hash_table_h__
#define straighten_hash_table_h__

#include <x86intrin.h>

#include "straighten_core.h"

struct pt_hash_table {
	struct packed_tableau* pt_arr;
    int (*compar)(const uint8_t* lhs, const uint8_t* rhs, size_t size);
	uint32_t * data_arr;
	uint32_t size;
	uint32_t and_size;
	uint32_t original_key_length;
	uint32_t key_length;
    uint32_t copy_mode;
};

FORCE_INLINE uint32_t Hash_YoshimitsuTRIAD(const uint8_t *str, size_t wrdlen) {
    const uint32_t PRIME = 709607;
    uint32_t hash32 = 2166136261;
    uint32_t hash32B = 2166136261;
    const uint8_t *p = str;

    // 1111=15; 10111=23
    if (wrdlen & 4*sizeof(uint32_t)) {	
		hash32 = (hash32 ^ (_rotl(*(uint32_t *)(p+0),5) ^ *(uint32_t *)(p+4))) * PRIME;        
		hash32B = (hash32B ^ (_rotl(*(uint32_t *)(p+8),5) ^ *(uint32_t *)(p+12))) * PRIME;        
		p += 8*sizeof(uint16_t);
    }
    // Cases: 0,1,2,3,4,5,6,7,...,15
    if (wrdlen & 2*sizeof(uint32_t)) {
		hash32 = (hash32 ^ *(uint32_t*)(p+0)) * PRIME;
		hash32B = (hash32B ^ *(uint32_t*)(p+4)) * PRIME;
		p += 4*sizeof(uint16_t);
    }
    // Cases: 0,1,2,3,4,5,6,7
    if (wrdlen & sizeof(uint32_t)) {
		hash32 = (hash32 ^ *(uint16_t*)(p+0)) * PRIME;
		hash32B = (hash32B ^ *(uint16_t*)(p+2)) * PRIME;
		p += 2*sizeof(uint16_t);
    }
    if (wrdlen & sizeof(uint16_t)) {
        hash32 = (hash32 ^ *(uint16_t*)p) * PRIME;
        p += sizeof(uint16_t);
    }
    if (wrdlen & 1) 
        hash32 = (hash32 ^ *p) * PRIME;

    hash32 = (hash32 ^ _rotl(hash32B,5) ) * PRIME;
    return hash32 ^ (hash32 >> 16);
}

FORCE_INLINE int pt_compar_anysize_even(const uint8_t* lhs, const uint8_t* rhs, size_t size) {
    const uint8_t* p;
    const uint8_t* q;
    const uint8_t* sentry = lhs + size;
    for ( p = lhs, q = rhs; p < sentry; ++p, ++q ) {
        if (*p != *q) { return 0; }
    }   
    return 1;
}

FORCE_INLINE int pt_compar_anysize_odd(const uint8_t* lhs, const uint8_t* rhs, size_t size) {
    const uint8_t* p;
    const uint8_t* q;
    const uint8_t* sentry = lhs + size - 1;
    for ( p = lhs, q = rhs; p < sentry; ++p, ++q ) {
        if (*p != *q) { return 0; }
    }   
    if((*p & ~0xF) != (*q & ~0xF)) { return 0; }
    return 1;
}

FORCE_INLINE int pt_compar_numboxes_gt32kll32(const uint8_t* lhs, const uint8_t* rhs, size_t size) {
    __m128i l1 = _mm_loadu_si128((__m128i *) lhs);
    __m128i r1 = _mm_loadu_si128((__m128i *) rhs);
    __m128i pcmp = _mm_cmpeq_epi32(l1, r1);  // epi8 is fine too
    int bitmask = _mm_movemask_epi8(pcmp);
    return (bitmask == 0xFFFF);
}

FORCE_INLINE int pt_compar_numboxes_36klgt32(const uint8_t* lhs, const uint8_t* rhs, size_t size) {
    __m128i l1 = _mm_loadu_si128((__m128i *) lhs);
    __m128i r1 = _mm_loadu_si128((__m128i *) rhs);
    __m128i pcmp = _mm_cmpeq_epi32(l1, r1);  // epi8 is fine too
    int bitmask = _mm_movemask_epi8(pcmp);
    return (bitmask == 0xFFFF) & (*((uint16_t*)(lhs + 16)) == *((uint16_t*)(rhs + 16)));
}

FORCE_INLINE int pt_search(const struct pt_hash_table* pt_hash_tb, const struct packed_tableau* key) {
    uint32_t index =  Hash_YoshimitsuTRIAD(key[0].entries, pt_hash_tb[0].key_length);
    
    index = index & pt_hash_tb[0].and_size;
    if(pt_hash_tb[0].size == 0 || (pt_hash_tb[0].pt_arr[index].entries == NULL || pt_hash_tb[0].pt_arr[index].entries[0] == 0)) {
            //printf("Error: Entry not found in dictionary hash. \n");
            return -1;
        }

    while (pt_hash_tb[0].compar(pt_hash_tb[0].pt_arr[index].entries, key[0].entries, pt_hash_tb[0].key_length) != 1) {
        index++;
        index = index & pt_hash_tb[0].and_size;
        //commenting this out is somewhat dangerous! we know that the entry is always in the hash table if there are no bugs, but this means things will hang if for some reason key is malformed
        if(pt_hash_tb[0].pt_arr[index].entries == NULL || pt_hash_tb[0].pt_arr[index].entries[0] == 0) {
            //printf("Error: Entry not found in dictionary hash. \n");
            return -1;
        }
    }

    return pt_hash_tb[0].data_arr[index];
}

struct pt_hash_table* pt_build_hashtable(struct packed_tableau* all_dictionary_tableau, const size_t num_dict_tableau, const uint32_t key_length, struct shape_data_c * s_data, uint32_t copy_data);

#endif //straighten_hash_table_h__