// La estabilidad con un ejemplo chico y con el archivo de términos.
// Uso: stability archivo_de_terminos
#include <algorithm>
#include <cstddef>
#include <cstdio>
#include <iostream>
#include <random>
#include <string>
#include <utility>
#include <vector>

#include "lab/terms_io.h"

namespace {

struct Exam {
    int score;
    std::string name;
};

// La partición de la clase, sobre registros que se comparan solo por nota.
std::size_t partition_by_score(std::vector<Exam>& items, std::size_t lo, std::size_t hi) {
    int pivot = items[lo].score;
    std::size_t boundary = lo;
    for (std::size_t scan = lo + 1; scan < hi; scan++) {
        if (items[scan].score < pivot) {
            boundary++;
            std::swap(items[boundary], items[scan]);
        }
    }
    std::swap(items[lo], items[boundary]);
    return boundary;
}

void print_exams(const char* label, const std::vector<Exam>& items) {
    std::cout << label;
    for (const Exam& exam : items) {
        std::cout << " " << exam.score << exam.name;
    }
    std::cout << "\n";
}

// Ordena n registros con std::sort por una clave de 0 a 2, y cuenta vecinos empatados invertidos.
int inverted_ties(int n) {
    std::mt19937 rng(1);
    std::vector<Exam> items;
    for (int i = 0; i < n; i++) {
        items.push_back(Exam{static_cast<int>(rng() % 3), std::to_string(100 + i)});
    }
    std::sort(items.begin(), items.end(), [](const Exam& a, const Exam& b) { return a.score < b.score; });
    int inverted = 0;
    for (std::size_t i = 1; i < items.size(); i++) {
        if (items[i - 1].score == items[i].score && items[i - 1].name > items[i].name) {
            inverted++;
        }
    }
    return inverted;
}

std::size_t same_as(const std::vector<Term>& a, const std::vector<Term>& b) {
    std::size_t same = 0;
    for (std::size_t i = 0; i < a.size(); i++) {
        if (a[i].text == b[i].text) {
            same++;
        }
    }
    return same;
}

}  // namespace

int main(int argc, char** argv) {
    if (argc != 2) {
        std::cerr << "uso: stability archivo_de_terminos\n";
        return 2;
    }
    std::vector<Exam> exams = {{2, "a"}, {3, "b"}, {3, "c"}, {1, "d"}};
    print_exams("antes:   ", exams);
    partition_by_score(exams, 0, exams.size());
    print_exams("partido: ", exams);
    std::printf("std::sort con 16 elementos, empates invertidos: %d\n", inverted_ties(16));
    std::printf("std::sort con 17 elementos, empates invertidos: %d\n", inverted_ties(17));

    std::vector<Term> file_order;
    if (!load_terms(argv[1], file_order)) {
        std::cerr << "no se pudo leer " << argv[1] << "\n";
        return 1;
    }
    auto by_text = [](const Term& a, const Term& b) { return a.text < b.text; };
    auto by_weight = [](const Term& a, const Term& b) { return a.weight > b.weight; };
    std::vector<Term> sorted_terms = file_order;
    std::sort(sorted_terms.begin(), sorted_terms.end(), by_text);

    std::vector<Term> with_stable = sorted_terms;
    std::stable_sort(with_stable.begin(), with_stable.end(), by_weight);
    std::vector<Term> with_sort = sorted_terms;
    std::sort(with_sort.begin(), with_sort.end(), by_weight);

    std::printf("líneas: %zu\n", file_order.size());
    std::printf("std::stable_sort, iguales al archivo: %zu\n", same_as(with_stable, file_order));
    std::printf("std::sort,        iguales al archivo: %zu\n", same_as(with_sort, file_order));
    return 0;
}
