/**
  * Choose an amount of bits (percentage of size of dataset) 
  * of bits and turn them off or on with a probability, but, if 
  * a bit has to be turned off, then it has to be ramdon bit on.
  * If a bit has to be turned on, then it has to be a random bit off.
  * @param 'S'  :  Solution 
  */
void weightedRandomPlus::tweak(IS::Solution &S) {
    std::bitset<MAX> bits = S.getBits();
    int size = S.getSize();
    int n = (perc * size) / 100;

    double reduc = ((double) reduc_weight) / 100.0;
    for (int k = 0; k < n; k++) {
        double prob = ((double) rand() / (double) RAND_MAX);
        // Select a random bit on, and set it off 
        if (bits.count() > 0 and reduc - prob > EPSILON) { 
            int random_bit_on = (rand() % bits.count()) + 1;
            int i, j;
            for (j = 0, i = 0; j < size && i < random_bit_on; j++) 
                if (bits.test(j)) i++;
            assert(bits.test(j-1));
            bits[j-1] = 0;
        } else { // Select a random bit off, and set it on
            int random_bit_off = (rand() % (size - bits.count())) + 1;
            int i, j;
            for (j = 0, i = 0; j < size && i < random_bit_off; j++) 
                if (! bits.test(j)) i++;
            assert(! bits.test(j-1));
            bits[j-1] = 1;
        }
    }
    S.setBits(bits);
}
/**
  * Apply n Random flips on random bits of solution
  * @param 'S'  :  Solution 
  */
void nRandomFlips::tweak(IS::Solution &S) {
    std::bitset<MAX> bits = S.getBits();
    int size = S.getSize();

    for (int i = 0; i < n; ++i) {
        int j = (rand() % size);
        bits.flip(j);
    }
    S.setBits(bits);
}
/**
  * Just turn off one random bit (but a bit that is on)
  * @param 'S'  :  Solution 
  */
void oneRandomUnset::tweak(IS::Solution &S) {
    std::bitset<MAX> bits = S.getBits();
    int size = S.getSize();
    int n = (rand() % bits.count()) + 1;
    int i, j;
    for (j = 0, i = 0; j < size && i < n; j++) if (bits.test(j)) i++;
    assert(bits.test(j-1));
    bits[j-1] = 0;
    S.setBits(bits);
}
/**
  * Optimize of ILS 
  * @param 'T'      :  Dataset
  * @param 'S'      :  Solution 
  *
  * This function save in S the best found solution 
  */
void ILS::optimize(const IS::Dataset &T, IS::Solution &S) const {
    std::cout << ">>>> Running ILS" << std::endl;
    std::cout << "quality = " << max_quality << std::endl;
    std::cout << "ite_limit = " << ite_limit << std::endl;
    std::cout << "no_change_best = " << no_change_best << std::endl;
    std::cout << ">>>>>>>>>>>>>>>>>>>>" << std::endl;

    IS::Solution best(S);

    double q_max, q_BEST = -1.0;
    int global_iter = 0;

    int max_out_iter = ite_limit;
    int no_change = 0;
    int max_local_iter = getLocalIter(); 

    while(1) {
        global_iter++;
        // Local search
        int local_iter = 0;
        while(1) {
            local_iter++;
            IS::Solution R(S);
            tweaker->tweak(R); 
            double q1 = quality(T, R, 0.5), q2 = quality(T, S, 0.5);
            if (q1 > q2)
                S.setBits(R.getBits());

            q_max = std::max(q1, q2);
            bool stop = q_max > max_quality or local_iter == max_local_iter;
            if (stop) break;
        }

        // Compare and (maybe) perturb
        double qs = quality(T, S, 0.5);
        double qb = quality(T, best, 0.5);

        if (qs > qb) { 
            best.setBits(S.getBits());
            no_change = 0;
        } else {
            no_change++;
        }
        //weightedRandomPlus tweaker = weightedRandomPlus(perc, 50);
        int perc = S.getSize() / getPerturbPerc();
        nRandomFlips tweaker = nRandomFlips(perc);
        tweaker.tweak(S);

        bool stop = q_max > max_quality or global_iter == max_out_iter;
        stop = stop or no_change == no_change_best;
        if (stop) break;
    }
    S.copy(best);
}
/**
  * Choose an amount of bits (percentage of size of dataset) 
  * of bits and turn them off or on with a probability
  * @param 'S'  :  Solution 
  */
void weightedRandom::tweak(IS::Solution &S) {
    std::bitset<MAX> bits = S.getBits();
    int size = S.getSize();
    int n = (perc * size) / 100;
    double reduc = ((double) reduc_weight) / 100.0;

    for (int i = 0; i < n; ++i) {
        int j = (rand() % size);
        double prob = ((double) rand() / (double) RAND_MAX);
        bits[j] = (reduc - prob > EPSILON) ? 0 : 1;
    }
    S.setBits(bits);
}
/**
  * Optimize of HillClimbing
  * @param 'T'      :  Dataset
  * @param 'S'      :  Solution 
  *
  * This function save in S the best found solution 
  */
void HillClimbing::optimize(const IS::Dataset &T, IS::Solution &S) const {
    std::cout << ">>>> Running Hill Climbing" << std::endl;
    std::cout << "quality = " << max_quality << std::endl;
    std::cout << "ite_limit = " << ite_limit << std::endl;
    std::cout << "no_change_best = " << no_change_best << std::endl;
    std::cout << ">>>>>>>>>>>>>>>>>>>>" << std::endl;

    double q_max, q_best = -1;
    int no_change = 0, iter = 0;
    while (1) {
        iter++;
        IS::Solution R(S);
        tweaker->tweak(R); 
        // assert((S.getBits() ^ R.getBits()).count() == 1);

        // Using 0.5 because paper
        double q1 = quality(T, R, 0.5), q2 = quality(T, S, 0.5);
        if (q1 > q2)
            S.setBits(R.getBits());
        
        q_max = std::max(q1, q2);
        if (q_best > q_max) {
            no_change++;  
        } else {
            q_best = q_max;
            no_change = 0;
        }
        // TODO: Pass these values as parameters
        bool stop = q_max > max_quality or no_change == no_change_best;
        stop = stop || iter == ite_limit;
        if (stop) break;
    }
}
/**
  * Optimize of ILS 
  * @param 'T'      :  Dataset
  * @param 'S'      :  Solution 
  *
  * This function save in S the best found solution 
  */
void Tabu::optimize(const IS::Dataset &T, IS::Solution &best) const {
    std::cout << ">>>> Running Tabu" << std::endl;
    std::cout << "quality = " << max_quality << std::endl;
    std::cout << "ite_limit = " << ite_limit << std::endl;
    std::cout << "no_change_best = " << no_change_best << std::endl;
    std::cout << "Number of Tweaks = " << number_of_tweaks << std::endl;
    std::cout << "Length of the list = " << length << std::endl;
    std::cout << ">>>>>>>>>>>>>>>>>>>>" << std::endl;

    IS::Solution S(best);
    int max_length = (length * T.size() / 100);

    std::deque<IS::Solution> tl;   // Tabu list
    tl.push_back(S);
    double q_max, q_BEST = -1;
    int no_change = 0, iter = 0;
    while (1) {
        iter++;
        while (tl.size() > max_length) tl.pop_front();
        IS::Solution R(S);
        tweaker->tweak(R); 

        for (int i = 0; i < number_of_tweaks - 1; i++) {
            IS::Solution W(S); 
            tweaker->tweak(W);

            // if W is not int tabu list
            if (std::find(tl.begin(), tl.end(), W) == tl.end()) {
                double qw = quality(T, W, 0.5), qr = quality(T, R, 0.5);
                if (qw > qr || std::find(tl.begin(), tl.end(), R)
                               != tl.end()) 
                    R.copy(W);
            }

        }
        // R is not in L and r is better
        bool R_is_better = quality(T, R, 0.5) > quality(T, S, 0.5);
        if (std::find(tl.begin(), tl.end(), R) == tl.end() and R_is_better) {
            S.copy(R);
            tl.push_back(R);
        }

        double qs = quality(T, S, 0.5), qb = quality(T, best, 0.5);
        q_max = std::max(qs, qb);
        if (qs > qb) best.copy(S);
        // TODO: Tune iter_total and no_change

        if (q_max > q_BEST) {
            q_BEST = q_max;
            no_change = 0;
        } else no_change++;  

        bool stop = q_max > max_quality || no_change == no_change_best;
        stop = stop || iter == ite_limit;
        if (stop) break;
    }
}
/**
  * Fitness function.
  * @param 'T'      :  Dataset
  * @param 'S'      :  Solution 
  * @param 'alpha'  :  double
  * 
  * This function calculates:
  * alpha * (instances_well_clasified) + (1 - alpha) * (percentage_of_reduction)
  *
  * @return Fitness of S. 
  */
double Metaheuristic::quality(const IS::Dataset &T, 
                              const IS::Solution &S,
                              double alpha) const {

    std::bitset<MAX> bits = S.getBits();
    if (bits.count() == 0) return 0.5;

    IS::Dataset training, result;
    std::vector<double> category;
    int j = 0;
    for (int i = 0; i < T.size(); i++) {
        if (bits.test(i)) training.push_back(T[i]); 
        else result.push_back(T[i]);
    }

    category.assign(result.size(), -1);
    oneNN(training, result, category);

    int count = 0;
    for (int i = 0; i < result.size(); i++)
        if (result[i].getCategory() == category[i])
            count++;

    double clas_rate = (1.0 * count) / (1.0 * result.size());
    double perc_redc = (1.0 * result.size()) / (1.0 * T.size());
    double fitness = alpha * clas_rate + (1 - alpha) * perc_redc;

    // // XXX: Print
      cout << "Hubo un total de " << count << " aciertos" << endl;
      cout << "Result es de tamanio: " << result.size() << endl;
      cout << "La relación es: " << clas_rate << endl;
      cout << "El tamaño de T es: " << T.size() << endl;
      cout << "El tamaño de training es: " << training.size() << endl;
      cout << "El porcentaje de reducción es: " << perc_redc << endl;
      cout << "El fitness es : " << fitness << endl << endl;

    assert(fitness <= 1.0);
    return fitness;
}