#include <algorithm>

#include "Data/FrequencyQueue/IO_stub.h"
#include "hfrequencyqueue_backend.h"


bool directsort(RandomElement a, RandomElement b){
    return (a.probability < b.probability);
};

bool reversesort(RandomElement a, RandomElement b){
    return (a.probability > b.probability);
};


FrequencyQueue::FrequencyQueue(unsigned int seed){

    cumulative_probability = 0;
    generator = minstd_rand0(seed);
    internal_vector = vector<RandomElement>();
}

FrequencyQueue::FrequencyQueue(const FrequencyQueue& other){

    minstd_rand0 tmp_gen = other.generator;
    unsigned int seed  = tmp_gen();
    generator = minstd_rand0(seed);
    cumulative_probability = other.cumulative_probability;
    internal_vector = other.internal_vector;
    new_stable_ref_all();
}

FrequencyQueue& FrequencyQueue::operator= (FrequencyQueue& other){
    free_all_pointers();

    minstd_rand0 tmp_gen = other.generator;
    unsigned int seed  = tmp_gen();
    generator = minstd_rand0(seed);
    cumulative_probability = other.cumulative_probability;
    internal_vector = other.internal_vector;
    new_stable_ref_all();
    
    return *this;
}

unsigned int FrequencyQueue::length(){

    return internal_vector.size();
}

unsigned int FrequencyQueue::probability_unit(){

    return cumulative_probability;
}

void FrequencyQueue::push_back(RandomElement el){

    cumulative_probability += el.probability;
    internal_vector.push_back(el);
}

RandomElement FrequencyQueue::pop_back(){

    RandomElement el = internal_vector.back();
    internal_vector.pop_back();
    cumulative_probability -= el.probability;
    return el;
}

RandomElement FrequencyQueue::pop_back_max_prob(){
    last_max();
    return pop_back();
}

RandomElement FrequencyQueue::pop_back_min_prob(){
    last_min();
    return pop_back();
}

RandomElement* FrequencyQueue::get_random(){

    auto it = get_random_iterator();
    return &(*it);
}

RandomElement FrequencyQueue::get_random_pop(){

    auto it = get_random_iterator();
    put_last_queue_element(it);
    return pop_back();
}

void FrequencyQueue::reset_iterator(){
    
    internal_it = internal_vector.begin();
}

RandomElement* FrequencyQueue::get_next(){

    RandomElement* result_ptr = &(*internal_it);
    if (internal_it != internal_vector.end()) internal_it++;
    return result_ptr; 
}

unsigned int FrequencyQueue::get_random_number(){

    return generator();
}

FrequencyQueue::~FrequencyQueue(){

    free_all_pointers();
}


void FrequencyQueue::sort_last_max(){
    
    sort(internal_vector.begin(),internal_vector.end(), directsort);
}

void FrequencyQueue::sort_last_min(){

    sort(internal_vector.begin(),internal_vector.end(), reversesort);
}

void FrequencyQueue::last_max(){

    auto it = max_element(internal_vector.begin(), internal_vector.end(), directsort);
    put_last_queue_element(it);
};

void FrequencyQueue::last_min(){

    auto it = min_element(internal_vector.begin(), internal_vector.end(), directsort);
    put_last_queue_element(it);
};

vector<RandomElement>::iterator FrequencyQueue::get_random_iterator(){

    shuffle_els();
    if (cumulative_probability != 0){
        
        unsigned int random_number = generator() % cumulative_probability;
        unsigned int accumulator = 0;
        
        for (auto it = internal_vector.begin() ; it != internal_vector.end(); ++it){

            if (((*it).probability + accumulator) > random_number) return it;
            accumulator += (*it).probability;
        }
    }

    return internal_vector.end();
}

void FrequencyQueue::put_last_queue_element(vector<RandomElement>::iterator it){
    RandomElement tmp = (*it);
    (*it) = internal_vector.back();
    internal_vector.back() = tmp;
}

void FrequencyQueue::shuffle_els(){

    shuffle(internal_vector.begin(), internal_vector.end(), generator);
}

void FrequencyQueue::new_stable_ref_all(){
;
    for (auto it = internal_vector.begin() ; it != internal_vector.end(); ++it){
        (*it).element = makeNewStableRef((*it).element);
    }
}

void FrequencyQueue::free_all_pointers(){

    for (auto it = this->internal_vector.begin() ; it != this->internal_vector.end(); ++it){
        freeStablePtr((*it).element);
    }    
}

FrequencyQueue* new_FrequencyQueue_priv_(unsigned int seed){
    return new FrequencyQueue(seed);
}

FrequencyQueue* clone_FrequencyQueue_priv_(FrequencyQueue* queue){
    return new FrequencyQueue(*queue);
}

unsigned int length_priv_(FrequencyQueue* queue){
    return queue->length();
}

unsigned int probability_unit_priv_(FrequencyQueue* queue){
    return queue->probability_unit();
}

void push_back_priv_(FrequencyQueue* queue, RandomElement* element){
    queue->push_back(*element);
}

RandomElement* pop_back_priv_(FrequencyQueue* queue){
    return new RandomElement(queue->pop_back());
}

RandomElement* pop_back_max_prob_priv_(FrequencyQueue* queue){
    return new RandomElement(queue->pop_back_max_prob());
}

RandomElement* pop_back_min_prob_priv_(FrequencyQueue* queue){    
    return new RandomElement(queue->pop_back_min_prob());
}

RandomElement* get_random_priv_(FrequencyQueue* queue){
    return queue->get_random();
}

RandomElement* get_random_pop_priv_(FrequencyQueue* queue){
    return new RandomElement(queue->get_random_pop());
}

void reset_iterator_priv_(FrequencyQueue* queue){
    queue->reset_iterator();
}

RandomElement* get_next_priv_(FrequencyQueue* queue){
    return queue->get_next();
}

unsigned int get_random_number_priv_(FrequencyQueue* queue){
    return queue->get_random_number();
}

void free_FrequencyQueue_priv_(FrequencyQueue* queue){
    delete queue;
}

void free_RandomElement_priv_(RandomElement* el){
    delete el;
};
