#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <assert.h>
#include "common.h"

static NODE_TYPE *LRU_Cache;

static INT TotalCacheSize;
static INT Threshold;
static INT OccupiedCacheSize;

static INT MissCount;
static double MissedBytes;
static double MissedCost;
static double MissedPackets;


#define CURRENT_REQUEST  RequestsTable[i]

#define REMOVE_THIS_ELEMENT_FROM_CACHE(element) \
{ \
    remove_node_from_sorted_list(LRU_Cache, element); \
    OccupiedCacheSize -= element->size; \
    ObjectsHashTable[element->hash_index].position_in_cache = NULL; \
}

#define REMOVE_THE_LAST_ELEMENT_FROM_CACHE(last_element) \
{ \
    last_element = remove_last_node_from_sorted_list(LRU_Cache); \
    OccupiedCacheSize -= last_element->size; \
    ObjectsHashTable[last_element->hash_index].position_in_cache = NULL; \
}

#define ADD_THIS_ELEMENT_INTO_CACHE(element,hash_index,newSize,newLastModify) \
{ \
    add_node_into_sorted_list(LRU_Cache, element); \
    assert(SAME_OBJECT(element->object, ObjectsHashTable[hash_index].object)); \
    ObjectsHashTable[hash_index].position_in_cache = element; \
    OccupiedCacheSize += newSize; \
 \
    ObjectsHashTable[hash_index].size = newSize; \
    ObjectsHashTable[hash_index].last_modify = newLastModify; \
}


static void init_global_variables_and_cache_memory(INT size, INT threshold)
{
    TotalCacheSize    = size;
    Threshold         = threshold;
    OccupiedCacheSize = 0;
    MissCount     = 0;
    MissedBytes   = 0.0;
    MissedCost    = 0.0;
    MissedPackets = 0.0;

    LRU_Cache = NULL;
    init_sorted_list(&LRU_Cache);
}

static void print_LRU_cache_result(FILE **fp)
{
    double rate, percentage;

    fprintf((*fp), "\n");

    fprintf((*fp), "LRU Cache ==============================================");
    fprintf((*fp), "================\n"); \
    percentage = TotalCacheSize/UniqueBytes * 100;
    fprintf((*fp), "          CacheSize = %-9lld           ", TotalCacheSize);
    fprintf((*fp), "[ %6.3f%% of Uniqu_Obj_Size ]\n", percentage);

    fprintf((*fp), "          Threshold = %lld\n", Threshold);

    rate = (double)(TotalRequests - MissCount)/ (double)TotalRequests;
    percentage = rate/InfCacheHitRate * 100;
    fprintf((*fp), "           Hit Rate = %6.3f%% (%f)  ", rate*100, rate);
    fprintf((*fp), "[ %6.3f%% of Infinite Cache ]\n", percentage);

    rate = (TotalBytes - MissedBytes) / TotalBytes;
    percentage = rate/InfCacheByteHitRate * 100;
    fprintf((*fp), "      Byte Hit Rate = %6.3f%% (%f)  ", rate*100, rate);
    fprintf((*fp), "[ %6.3f%% of Infinite Cache ]\n", percentage);

    rate = (TotalCost - MissedCost) / TotalCost;
    percentage = rate/InfCacheReducedLatency * 100;
    fprintf((*fp), "    Reduced Latency = %6.3f%% (%f)  ", rate*100, rate);
    fprintf((*fp), "[ %6.3f%% of Infinite Cache ]\n", percentage);

    rate = (TotalPackets - MissedPackets) / TotalPackets;
    percentage = rate/InfCacheReducedPackets * 100;
    fprintf((*fp), "    Reduced Packets = %6.3f%% (%f)  ", rate*100, rate);
    fprintf((*fp), "[ %6.3f%% of Infinite Cache ]\n", percentage);

    fprintf((*fp), "\n");
    fflush((*fp));
}

static NODE_TYPE *create_a_new_caching_element(REQUEST request, INT hash_index)
{
    NODE_TYPE *p;
    p = create_a_new_node();

    p->hash_index  = hash_index;
    p->primary_key = 0;
    SET_OBJECT(p->object, request.object);
    p->size = request.size;
    p->time = request.time;

    return(p);
}

static void make_free_space_enough_for_this_element(NODE_TYPE *element)
{
    NODE_TYPE *p;
    while (TotalCacheSize - OccupiedCacheSize < element->size) {
        REMOVE_THE_LAST_ELEMENT_FROM_CACHE(p);
        free(p);
    }
}

static void print_cache_status()
{
    printf("===============================================================\n");
    printf("       TotalCacheSize = %lld\n", TotalCacheSize);
    printf("    OccupiedCacheSize = %lld", OccupiedCacheSize);
    print_sorted_list(LRU_Cache);
}

static void free_cache_memory()
{
    INT num1, num2;

    num1 = release_sorted_list(&LRU_Cache);
    num2 = reset_the_field_of_position_in_cache();
    if ( num1 != num2 ) {
        ERROR_LOG("\nfree_cache_memory: LRU_cache, the number of the entries ");
        ERROR_LOG("in the cache memory is not equal to the number of the ");
        ERROR_LOG("entries set in the hash table! Exit!\n\n");
        exit(-1);
    }
}

void cache_LRU(INT size, INT threshold)
{
    INT i, miss, index;
    NODE_TYPE *p;

    if ( size<=0 || threshold<=0 ) {
        ERROR_LOG("\ncache_LRU: size<=0 or threshold<=0 ! Exit!\n\n");
        exit(-1);
    }
    init_global_variables_and_cache_memory(size, threshold);

    for (i=0; i<TotalRequests; i++) {
        miss = 0;
        index = find_object_in_hash_table(CURRENT_REQUEST.object);
        assert(index >= 0);

        if (ObjectsHashTable[index].position_in_cache == NULL) {
            miss = 1;
            if (CURRENT_REQUEST.size <= Threshold) {
                p = create_a_new_caching_element(CURRENT_REQUEST, index);
                make_free_space_enough_for_this_element(p);
                ADD_THIS_ELEMENT_INTO_CACHE(p, index, CURRENT_REQUEST.size, \
                                            CURRENT_REQUEST.last_modify);
            }
        }
        else {
            IF_OBJECT_CHANGED(ObjectsHashTable[index], CURRENT_REQUEST) {
                miss = 1;
            }
            ObjectsHashTable[index].size = CURRENT_REQUEST.size;
            ObjectsHashTable[index].last_modify = CURRENT_REQUEST.last_modify;

            p = ObjectsHashTable[index].position_in_cache;
            REMOVE_THIS_ELEMENT_FROM_CACHE(p);
            p->size = CURRENT_REQUEST.size;
            p->time = CURRENT_REQUEST.time;
            if (CURRENT_REQUEST.size <= Threshold) {
                make_free_space_enough_for_this_element(p);
                ADD_THIS_ELEMENT_INTO_CACHE(p, index, CURRENT_REQUEST.size, \
                                            CURRENT_REQUEST.last_modify);
            }
            else {
                miss = 1;
                free(p);
            }
        }
    
        if (miss) {
            MissCount++;
            MissedBytes   += CURRENT_REQUEST.size;
            MissedCost    += ( (double)CURRENT_REQUEST.cost / 1e6 );
            MissedPackets += GET_PACKET(CURRENT_REQUEST.size);
        }
        if (Echo) {
            if ( (i>=From) && (i<=To) && ((i-From)%Step==0) ) {
                print_cache_status();
                printf("So far, %lld requests have been processed.\n\n", i+1);
            }
        }
    }
    print_LRU_cache_result(&fpout);
    free_cache_memory();
}

