/*
 * File:   main.cpp
 * Author: ollbert
 *
 * Created on 15. März 2010
 */

extern "C" {
#include "cudf.h"
}

#include <cstring>
#include <cstdlib>
#include <string>
#include <iostream>
#include <fstream>
#include <map>
#include <vector>
using namespace std;

typedef map<string, int> pkgmap;
pkgmap pkgids;
vector<string> pkgnames;

int getid(string name) {
    pkgmap::iterator i = pkgids.find(name);
    int id;
    if (i == pkgids.end()) {
        id = pkgids.size();
        pkgids[name] = id;
        pkgnames.push_back(name);
    } else id = i->second;

    return id;
}

int orcount;

int getorid() {
    return orcount++;
}

int mypkgid(cudf_package_t pkg) {
    string name = cudf_pkg_name(pkg);
    return getid(name);
}

string mykeepstr(int keep) {
    switch (keep) {
        case KEEP_FEATURE:
            return "feature";
        case KEEP_NONE:
            return "none";
        case KEEP_PACKAGE:
            return "package";
        case KEEP_VERSION:
            return "version";
        default:
            cerr << "Illegal keep value." << endl;
            exit(1);
    }
}

string myrelopstr(int relop) {
    switch (relop) {
        case RELOP_EQ:
            return "eq";
        case RELOP_GEQ:
            return "geq";
        case RELOP_GT:
            return "gt";
        case RELOP_LEQ:
            return "leq";
        case RELOP_LT:
            return "lt";
        case RELOP_NEQ:
            return "neq";
        default:
            cerr << "Illegal relational operator: " << relop << "." << endl;
            exit(1);
    }
}

void parse(string sourcename, string targetname, string tablename) {
    pkgids.clear();
    orcount = 0;
    ofstream out(targetname.c_str());

    cudf_doc_t *doc = cudf_parse_from_file(const_cast<char *> (sourcename.c_str()));

    // Durchlaufe die Paketliste
    GList *package_list_pointer = doc->packages;
    while (package_list_pointer != 0) {
        cudf_package_t pkg = (cudf_package_t) package_list_pointer->data;
        int id = mypkgid(pkg);
        int version = cudf_pkg_version(pkg);

        // packageversion
        out << "packageversion(" << id << "," << version << ")." << endl;
        // installed
        if (cudf_pkg_installed(pkg)) {
            out << "installed(" << id << "," << version << ")." << endl;
        }
        // provides
        cudf_vpkglist_t plist = cudf_pkg_provides(pkg);
        while (plist != 0) {
            cudf_vpkg_t *vpkg = (cudf_vpkg_t *) plist->data;
            int vid = getid(vpkg->name);
            if (vpkg->relop == RELOP_EQ) {
                out << "provides(" << id << "," << version << "," << vid << "," << vpkg->version;
                out << ")." << endl;
            } else {
                out << "provides(" << id << "," << version << "," << vid << ")." << endl;
            }
            plist = plist->next;
        }
        // keep
        int keep = cudf_pkg_keep(pkg);
        if (keep != KEEP_NONE) {
            out << "keep(" << id << "," << version << "," << mykeepstr(keep) << ")." << endl;
        }
        // conflicts
        cudf_vpkglist_t clist = cudf_pkg_conflicts(pkg);
        while (clist != 0) {
            cudf_vpkg_t *vpkg = (cudf_vpkg_t *) clist->data;
            int vid = getid(vpkg->name);
            if (vpkg->relop != RELOP_NOP) {
                out << "conflicts(" << id << "," << version << "," << vid << ",";
                out << myrelopstr(vpkg->relop) << "," << vpkg->version << ")." << endl;
            } else {
                out << "conflicts(" << id << "," << version << "," << vid << ")." << endl;
            }
            clist = clist->next;
        }
        // depends
        cudf_vpkgformula_t formula = cudf_pkg_depends(pkg);
        while (formula != 0) {
            cudf_vpkglist_t orlist = (cudf_vpkglist_t) formula->data;
            if (g_list_length(orlist) == 1) {
                cudf_vpkg_t *vpkg = (cudf_vpkg_t *) orlist->data;
                int vid = getid(vpkg->name);
                if (vpkg->relop != RELOP_NOP) {
                    out << "depends(" << id << "," << version << "," << vid << ",";
                    out << myrelopstr(vpkg->relop) << "," << vpkg->version << ")." << endl;
                } else {
                    out << "depends(" << id << "," << version << "," << vid << ")." << endl;
                }
            } else {
                int orid = getorid();
                out << "dependsor(" << id << "," << version << "," << orid << ")." << endl;
                while (orlist != 0) {
                    cudf_vpkg_t *vpkg = (cudf_vpkg_t *) orlist->data;
                    int vid = getid(vpkg->name);
                    if (vpkg->relop != RELOP_NOP) {
                        out << "or(" << orid << "," << vid << ",";
                        out << myrelopstr(vpkg->relop) << "," << vpkg->version << ")." << endl;
                    } else {
                        out << "or(" << orid << "," << vid << ")." << endl;
                    }
                    orlist = orlist->next;
                }
            }
            formula = formula->next;
        }
        // recommends
				cudf_extra_t extra = cudf_pkg_extra(pkg);
				cudf_value_t *recom = (cudf_value_t*)g_hash_table_lookup(extra, "recommends");
				if (recom != 0 && recom->typ == TYPE_VPKGFORMULA) {
				    cudf_vpkgformula_t formula = recom->val.f;
				    while (formula != 0) {
				        cudf_vpkglist_t orlist = (cudf_vpkglist_t) formula->data;
				        if (g_list_length(orlist) == 1) {
				            cudf_vpkg_t *vpkg = (cudf_vpkg_t *) orlist->data;
				            int vid = getid(vpkg->name);
				            if (vpkg->relop != RELOP_NOP) {
				                out << "recommends(" << id << "," << version << "," << vid << ",";
				                out << myrelopstr(vpkg->relop) << "," << vpkg->version << ")." << endl;
				            } else {
				                out << "recommends(" << id << "," << version << "," << vid << ")." << endl;
				            }
				        } else {
				            int orid = getorid();
				            out << "recommendsor(" << id << "," << version << "," << orid << ")." << endl;
				            while (orlist != 0) {
				                cudf_vpkg_t *vpkg = (cudf_vpkg_t *) orlist->data;
				                int vid = getid(vpkg->name);
				                if (vpkg->relop != RELOP_NOP) {
				                    out << "or(" << orid << "," << vid << ",";
				                    out << myrelopstr(vpkg->relop) << "," << vpkg->version << ")." << endl;
				                } else {
				                    out << "or(" << orid << "," << vid << ")." << endl;
				                }
				                orlist = orlist->next;
				            }
				        }
				        formula = formula->next;
				    }
				}
        package_list_pointer = package_list_pointer->next;
    }
    // requests
    if (doc->has_request) {
        const char *types[] = {"install", "upgrade", "remove"};
				cudf_vpkglist_t (*functionpointer[3])(cudf_request_t) = {
					cudf_req_install, cudf_req_upgrade, cudf_req_remove};
        for (int i = 0; i < 3; ++i) {
            cudf_vpkglist_t vlist = functionpointer[i](doc->request);
            while (vlist != 0) {
                cudf_vpkg_t *vpkg = (cudf_vpkg_t *) vlist->data;
                int vid = getid(vpkg->name);
                if (vpkg->relop != RELOP_NOP) {
                    out << types[i] << "(" << vid << ",";
                    out << myrelopstr(vpkg->relop) << "," << vpkg->version << ")." << endl;
                } else {
                    out << types[i] << "(" << vid << ")." << endl;
                }
                vlist = vlist->next;
            }
        }
    }
    ofstream tbl(tablename.c_str());
    tbl << "size: " << pkgids.size() << endl;
    for (pkgmap::iterator i = pkgids.begin(); i != pkgids.end(); ++i) {
        tbl << i->first << " " << i->second << endl;
    }
}

/*
 *
 */
int main(int argc, char **argv) {
    if(argc < 2) {
        cout << "using: " << argv[0] << "cudfin [lpout] [tableout]" << endl;
        exit(1);
    }

		string cudfin, lpout, tableout;

		cudfin = argv[1];
		if (argc < 3) lpout = cudfin + ".lp";
		else lpout = argv[2];
		if (argc < 4) tableout = cudfin + ".tbl";
		else tableout = argv[3];

    cudf_init();

	  parse(cudfin, lpout, tableout);

    return 0;   
}
