/*++
Copyright (c) 2017 Microsoft Corporation

Module Name:

    main.cpp

Abstract:

    Main file for qprofdiff.

Author:

    Christoph M. Wintersteiger (cwinter)

Revision History:
--*/
#include<errno.h>
#include<limits.h>

#include<string>
#include<iostream>
#include<fstream>
#include<map>
#include<vector>
#include<set>
#include<algorithm>

using namespace std;

set<string> options;

// Profile format:
// [quantifier_instances] qname : num_instances : max_generation : max_cost_s
const string prefix = "[quantifier_instances]";
unsigned prefix_len = prefix.length();
typedef struct { unsigned num_instances, max_generation, max_cost; } map_entry;

string trim(string str) {
    size_t linx = str.find_first_not_of(' ');
    size_t rinx = str.find_last_not_of(' ');
    return str.substr(linx, rinx-linx+1);
}

int parse(string const & filename, map<string, map_entry> & data) {
    ifstream fs(filename.c_str());

    if (!fs.is_open()) {
        cout << "Can't open file '" << filename << "'" << endl;
        return ENOENT;
    }

    string qid;
    string tokens[4];
    unsigned cur_token = 0;

    while (!fs.eof()) {
        string line;
        getline(fs, line);

        if (line.substr(0, prefix_len) == prefix) {
            line = trim(line.substr(prefix_len));
            size_t from = 0, ti = 0;
            for (size_t inx = line.find(" : ", from);
                inx != string::npos;
                inx = line.find(" : ", from)) {
                tokens[ti] = trim(line.substr(from, inx-from));
                from = inx+3; //3 is the length of " : "
                ti++;
            }
            if (from != line.length() && ti < 4)
                tokens[ti] = trim(line.substr(from));

            qid = tokens[0];

            if (data.find(qid) == data.end()) {
                map_entry & entry = data[qid];
                entry.num_instances = entry.max_generation = entry.max_cost = 0;
            }

            // Existing entries represent previous occurrences of quantifiers
            // that, at some point, were removed (e.g. backtracked). We sum
            // up instances from all occurrences of the same qid.
            map_entry & entry = data[qid];
            entry.num_instances += atoi(tokens[1].c_str());
            entry.max_generation = max(entry.max_generation, (unsigned)atoi(tokens[2].c_str()));
            entry.max_cost = max(entry.max_cost, (unsigned)atoi(tokens[3].c_str()));
        }
    }

    fs.close();

    return 0;
}

void display_data(map<string, map_entry> & data) {
    for (map<string, map_entry>::iterator it = data.begin();
         it != data.end();
         it++)
        cout << it->first << ": " << it->second.num_instances <<
                             ", " << it->second.max_generation <<
                             ", " << it->second.max_cost << endl;
}


typedef struct {
    int d_num_instances, d_max_generation, d_max_cost;
    bool left_only, right_only;
} diff_entry;

typedef struct { string qid; diff_entry e; } diff_item;

#define DIFF_LT(X) bool diff_item_lt_ ## X (diff_item const & l, diff_item const & r) { \
    int l_lt_r = l.e.d_ ## X < r.e.d_ ## X; \
    int l_eq_r = l.e.d_ ## X == r.e.d_ ## X; \
    return \
        l.e.left_only ? (r.e.left_only ? ((l_eq_r) ? l.qid < r.qid : l_lt_r) : false) : \
        l.e.right_only ? (r.e.right_only ? ((l_eq_r) ? l.qid < r.qid : l_lt_r) : true) : \
        r.e.right_only ? false : \
        r.e.left_only ? true : \
        l_lt_r; \
}

DIFF_LT(num_instances)
DIFF_LT(max_generation)
DIFF_LT(max_cost)

int indicate(diff_entry const & e, bool suppress_unchanged) {
    if (e.left_only) {
        cout << "< ";
        return INT_MIN;
    }
    else if (e.right_only) {
        cout << "> ";
        return INT_MAX;
    }
    else {
        int const & delta =
            (options.find("-si") != options.end()) ? e.d_num_instances :
            (options.find("-sg") != options.end()) ? e.d_max_generation :
            (options.find("-sc") != options.end()) ? e.d_max_cost :
            e.d_num_instances;

        if (delta < 0)
            cout << "+ ";
        else if (delta > 0)
            cout << "- ";
        else if (delta == 0 && !suppress_unchanged)
            cout << "= ";

        return delta;
    }
}

void diff(map<string, map_entry> & left, map<string, map_entry> & right) {
    map<string, diff_entry> diff_data;

    for (map<string, map_entry>::const_iterator lit = left.begin();
        lit != left.end();
        lit++) {
        string const & qid = lit->first;
        map_entry const & lentry = lit->second;

        map<string, map_entry>::const_iterator rit = right.find(qid);
        if (rit != right.end()) {
            map_entry const & rentry = rit->second;
            diff_entry & de = diff_data[qid];

            de.left_only = de.right_only = false;
            de.d_num_instances = lentry.num_instances - rentry.num_instances;
            de.d_max_generation = lentry.max_generation - rentry.max_generation;
            de.d_max_cost = lentry.max_cost - rentry.max_cost;
        }
        else {
            diff_entry & de = diff_data[qid];
            de.left_only = true;
            de.right_only = false;
            de.d_num_instances = lentry.num_instances;
            de.d_max_generation = lentry.max_generation;
            de.d_max_cost = lentry.max_cost;
        }
    }

    for (map<string, map_entry>::const_iterator rit = right.begin();
        rit != right.end();
        rit++) {
        string const & qid = rit->first;
        map_entry const & rentry = rit->second;

        map<string, map_entry>::const_iterator lit = left.find(qid);
        if (lit == left.end()) {
            diff_entry & de = diff_data[qid];
            de.left_only = false;
            de.right_only = true;
            de.d_num_instances = -(int)rentry.num_instances;
            de.d_max_generation = -(int)rentry.max_generation;
            de.d_max_cost = -(int)rentry.max_cost;
        }
    }

    vector<diff_item> flat_data;
    for (map<string, diff_entry>::const_iterator it = diff_data.begin();
        it != diff_data.end();
        it++) {
        flat_data.push_back(diff_item());
        flat_data.back().qid = it->first;
        flat_data.back().e = it->second;
    }

    stable_sort(flat_data.begin(), flat_data.end(),
        options.find("-si") != options.end() ? diff_item_lt_num_instances :
        options.find("-sg") != options.end() ? diff_item_lt_max_generation :
        options.find("-sc") != options.end() ? diff_item_lt_max_cost :
        diff_item_lt_num_instances);

    bool suppress_unchanged = options.find("-n") != options.end();

    for (vector<diff_item>::const_iterator it = flat_data.begin();
        it != flat_data.end();
        it++) {
        diff_item const & d = *it;
        string const & qid = d.qid;
        diff_entry const & e = d.e;

        int delta = indicate(e, suppress_unchanged);

        if (!(delta == 0 && suppress_unchanged))
            cout << qid << " (" <<
                (e.d_num_instances > 0 ? "" : "+") << -e.d_num_instances << " inst., " <<
                (e.d_max_generation > 0 ? "" : "+") << -e.d_max_generation << " max. gen., " <<
                (e.d_max_cost > 0 ? "" : "+") << -e.d_max_cost << " max. cost)" <<
                endl;
    }
}

void display_usage() {
    cout << "Usage: qprofdiff [options] <filename> <filename>" << endl;
    cout << "Options:" << endl;
    cout << " -n      Suppress unchanged items" << endl;
    cout << " -si     Sort by difference in number of instances" << endl;
    cout << " -sg     Sort by difference in max. generation" << endl;
    cout << " -sc     Sort by difference in max. cost" << endl;
}

int main(int argc, char ** argv) {
    char * filename1 = 0;
    char * filename2 = 0;

    for (int i = 1; i < argc; i++) {
        int len = string(argv[i]).length();
        if (len > 1 && argv[i][0] == '-') {
            options.insert(string(argv[i]));
        }
        else if (filename1 == 0)
            filename1 = argv[i];
        else if (filename2 == 0)
            filename2 = argv[i];
        else {
            cout << "Invalid argument: " << argv[i] << endl << endl;
            display_usage();
            return EINVAL;
        }
    }

    if (filename1 == 0 || filename2 == 0) {
        cout << "Two filenames required." << endl << endl;
        display_usage();
        return EINVAL;
    }


    cout << "Comparing " << filename1 << " to " << filename2 << endl;

    map<string, map_entry> data1, data2;

    int r = parse(filename1, data1);
    if (r != 0) return r;
    r = parse(filename2, data2);
    if (r != 0) return r;

    // display_data(data1);
    // display_data(data2);

    diff(data1, data2);

    return 0;
}
