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

static NODE_TYPE *Common_Room;
static NODE_TYPE *VIP_Room;

static INT TotalCacheSize;
static INT CommonRoomSize;
static INT VIPRoomSize;
static INT OccupiedCacheSize; /* not used in this partitioning design */
static INT OccupiedCommonRoomSize;
static INT OccupiedVIPRoomSize;

static INT Threshold;
static INT UpgradingThreshold;
static INT WindowSize;

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


#define COMMON_ROOM      1
#define VIP_ROOM         2
#define CURRENT_REQUEST  RequestsTable[i]

#define RESET_THIS_ENTRY_OF_HASH_TABLE(index) \
{ \
    ObjectsHashTable[index].in_which_cache = INVALID_TAG; \
    ObjectsHashTable[index].position_in_cache = NULL; \
}

#define REMOVE_THIS_ELEMENT_FROM_COMMON_ROOM(element) \
{ \
    remove_node_from_sorted_list(Common_Room, element); \
    OccupiedCommonRoomSize -= element->size; \
    RESET_THIS_ENTRY_OF_HASH_TABLE(element->hash_index); \
}
#define REMOVE_THIS_ELEMENT_FROM_VIP_ROOM(element) \
{ \
    remove_node_from_sorted_list(VIP_Room, element); \
    OccupiedVIPRoomSize -= element->size; \
    RESET_THIS_ENTRY_OF_HASH_TABLE(element->hash_index); \
}

#define REMOVE_THE_LAST_ELEMENT_FROM_COMMON_ROOM(last_element) \
{ \
    last_element = remove_last_node_from_sorted_list(Common_Room); \
    OccupiedCommonRoomSize -= last_element->size; \
    RESET_THIS_ENTRY_OF_HASH_TABLE(last_element->hash_index); \
}
#define REMOVE_THE_LAST_ELEMENT_FROM_VIP_ROOM(last_element) \
{ \
    last_element = remove_last_node_from_sorted_list(VIP_Room); \
    OccupiedVIPRoomSize -= last_element->size; \
    RESET_THIS_ENTRY_OF_HASH_TABLE(last_element->hash_index); \
}

#define ADD_THIS_ELEMENT_INTO_COMMON_ROOM(element, hash_index, newSize, \
                                          newLastModify) \
{ \
    add_node_into_sorted_list(Common_Room, element); \
    assert(SAME_OBJECT(element->object, ObjectsHashTable[hash_index].object)); \
    ObjectsHashTable[hash_index].in_which_cache = COMMON_ROOM; \
    ObjectsHashTable[hash_index].position_in_cache = element; \
    OccupiedCommonRoomSize += newSize; \
 \
    ObjectsHashTable[hash_index].size = newSize; \
    ObjectsHashTable[hash_index].last_modify = newLastModify; \
}
#define ADD_THIS_ELEMENT_INTO_VIP_ROOM(element, hash_index, newSize, \
                                       newLastModify) \
{ \
    add_node_into_sorted_list(VIP_Room, element); \
    assert(SAME_OBJECT(element->object, ObjectsHashTable[hash_index].object)); \
    ObjectsHashTable[hash_index].in_which_cache = VIP_ROOM; \
    ObjectsHashTable[hash_index].position_in_cache = element; \
    OccupiedVIPRoomSize += newSize; \
 \
    ObjectsHashTable[hash_index].size = newSize; \
    ObjectsHashTable[hash_index].last_modify = newLastModify; \
}


static void init_global_variables_and_cache_memory(INT size, float part_ratio,
                        INT threshold, INT upgrading_threshold, INT window_size)
{
    TotalCacheSize     = size;
    CommonRoomSize     = TotalCacheSize * part_ratio;
    VIPRoomSize        = TotalCacheSize - CommonRoomSize;
    Threshold          = threshold;
    UpgradingThreshold = upgrading_threshold;
    WindowSize         = window_size;

    OccupiedCacheSize       = 0;
    OccupiedCommonRoomSize  = 0;
    OccupiedVIPRoomSize     = 0;
    MissCount     = 0;
    MissedBytes   = 0.0;
    MissedCost    = 0.0;
    MissedPackets = 0.0;

    Common_Room = NULL;
    VIP_Room    = NULL;
    init_sorted_list(&Common_Room);
    init_sorted_list(&VIP_Room);
}

static void print_PARTITIONING_cache_result(FILE **fp, INT throw_away)
{
    double rate, percentage;

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

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

    percentage = (float) CommonRoomSize/TotalCacheSize * 100;
    fprintf((*fp), "        CommonRoomSize = %-9lld        ", CommonRoomSize);
    fprintf((*fp), "[ %6.3f%% of TotalCacheSize ]\n", percentage);

    percentage = (float) VIPRoomSize/TotalCacheSize * 100;
    fprintf((*fp), "           VIPRoomSize = %-9lld        ", VIPRoomSize);
    fprintf((*fp), "[ %6.3f%% of TotalCacheSize ]\n", percentage);

    fprintf((*fp), "             Threshold = %lld\n", Threshold);
    fprintf((*fp), "            WindowSize = %lld\n", WindowSize);
    fprintf((*fp), "    UpgradingThreshold = %lld\n", UpgradingThreshold);
    fprintf((*fp), "            Throw_Away = %s\n", throw_away?"YES":"NO");

    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 = 1;
    SET_OBJECT(p->object, request.object);
    p->size = request.size;
    p->time = request.time;

    return(p);
}

static void make_free_space_enough_in_common_room_for_this_element
                                                            (NODE_TYPE *element)
{
    NODE_TYPE *p;
    while (CommonRoomSize - OccupiedCommonRoomSize < element->size) {
        REMOVE_THE_LAST_ELEMENT_FROM_COMMON_ROOM(p);
        free(p);
    }
}
static void make_free_space_enough_in_vip_room_for_this_element
                            (NODE_TYPE *element, INT hash_index, INT throw_away)
{
    NODE_TYPE *p;
    while (VIPRoomSize - OccupiedVIPRoomSize < element->size) {
        REMOVE_THE_LAST_ELEMENT_FROM_VIP_ROOM(p);
        if (throw_away) {
            free(p);
        }
        else { /* downgrading */
            /* reset key???  p->primary_key = 1; */
            make_free_space_enough_in_common_room_for_this_element(p);
            ADD_THIS_ELEMENT_INTO_COMMON_ROOM(p, p->hash_index, \
                                  ObjectsHashTable[p->hash_index].size, \
                                  ObjectsHashTable[p->hash_index].last_modify );
        }
    }
}

static void print_cache_status(INT throw_away)
{
    printf("===============================================================\n");
    printf("            Threshold = %lld\n", Threshold);
    printf("   UpgradingThreshold = %lld\n", UpgradingThreshold);
    printf("           WindowSize = %lld\n", WindowSize);
    printf("           Throw_Away = %s\n", throw_away?"YES":"NO");

    printf("\n   ======== Common Room ========\n");
    printf("         CommonRoomSize = %lld\n", CommonRoomSize);
    printf(" OccupiedCommonRoomSize = %lld", OccupiedCommonRoomSize);
    print_sorted_list(Common_Room);

    printf("   ========== VIP Room =========\n");
    printf("            VIPRoomSize = %lld\n", VIPRoomSize);
    printf("    OccupiedVIPRoomSize = %lld", OccupiedVIPRoomSize);
    print_sorted_list(VIP_Room);
}

static void free_cache_memory()
{
    INT num1, num2;

    num1 = release_sorted_list(&Common_Room) + release_sorted_list(&VIP_Room);
    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_PARTITIONING(INT size, float partition_ratio, INT threshold,
                       INT upgrading_threshold, INT window_size, INT throw_away)
{
    INT i, miss, index, old_which_cache;
    NODE_TYPE *p;

    if ( size<=0 || partition_ratio<0 || partition_ratio>1 ||
         threshold<=0 || upgrading_threshold<=0 || window_size<0 ) {
        ERROR_LOG("\ncache_PARTITIONING:Illegal initial parameters! Exit!\n\n");
        exit(-1);
    }
    init_global_variables_and_cache_memory(size, partition_ratio, threshold,
                                           upgrading_threshold, window_size);

    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_in_common_room_for_this_element(p);
               ADD_THIS_ELEMENT_INTO_COMMON_ROOM(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;
            if (ObjectsHashTable[index].in_which_cache==COMMON_ROOM) {
                REMOVE_THIS_ELEMENT_FROM_COMMON_ROOM(p);
                old_which_cache = COMMON_ROOM;
            }
            else if (ObjectsHashTable[index].in_which_cache==VIP_ROOM) {
                REMOVE_THIS_ELEMENT_FROM_VIP_ROOM(p);
                old_which_cache = VIP_ROOM;
            }
            else {
                ERROR_LOG("\ncache_PARTITIONING: found one element which is ");
                ERROR_LOG("neither in Common_Room, nor in VIP_Room! Exit!\n\n");
                exit(-1);
            }
            p->size = CURRENT_REQUEST.size;
            p->time = CURRENT_REQUEST.time;
            p->primary_key ++;

            if (CURRENT_REQUEST.size > Threshold) {
                miss = 1;
                free(p);
            }
            else {
                if (p->primary_key >= UpgradingThreshold) {
                    make_free_space_enough_in_vip_room_for_this_element
                                                         (p, index, throw_away);
                    ADD_THIS_ELEMENT_INTO_VIP_ROOM(p, index, \
                                                   CURRENT_REQUEST.size, \
                                                   CURRENT_REQUEST.last_modify);
                }
                else {
                    make_free_space_enough_in_common_room_for_this_element(p);
                    ADD_THIS_ELEMENT_INTO_COMMON_ROOM(p, index, \
                                                   CURRENT_REQUEST.size, \
                                                   CURRENT_REQUEST.last_modify);

                }
            }
        }
    
        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(throw_away);
                printf("So far, %lld requests have been processed.\n\n", i+1);
            }
        }
    }
    free_cache_memory();
    print_PARTITIONING_cache_result(&fpout, throw_away);
}

