#include "parser.h"

string parser::get_token() {
    string type = tokens[0].type;
    return type;
}

void parser::check_datalog() {
    match("SCHEMES");
    match("COLON");
    if(get_token() == "FACTS") {
        error();
    }
    check_schemelist(get_token());
    match("FACTS");
    match("COLON");
    check_factlist(get_token());
    match("RULES");
    match("COLON");
    check_rulelist(get_token());
    match("QUERIES");
    match("COLON");
    check_querylist(get_token());
    out();
}

string parser::out() {
    stringstream s;
    s << "Success!" << endl;
    s << "Schemes(" << schemelist.size() << "):" << endl;
    for(unsigned int i = 0; i < schemelist.size(); i++) {
        s << "  " << schemelist[i].toString();
    }
    s << "Facts(" << factlist.size() << "):" << endl;
    for(unsigned int i = 0; i < factlist.size(); i++) {
        s << "  " << factlist[i].toString(false);
    }
    s << "Rules(" << rulelist.size() << "):" << endl;
    for(unsigned int i = 0; i < rulelist.size(); i++) {
        s << "  " << rulelist[i].toString();
    }
    s << "Queries(" << querylist.size() << "):" << endl;
    double a = 0;
    for(unsigned int i = 0; i < querylist.size(); i++) {
        s << "  " << querylist[i].toString(a);
    }
    s << "Domain(" << domain.size() << "):" << endl;
    for (auto it=domain.cbegin(); it != domain.cend(); ++it) {
        s << "  '" << *it << "'" << endl;
    }
    return s.str();
}

void parser::check_schemelist(string type) {
    if(type == "FACTS") {
        return;
    }
    else {
        check_scheme(type);
        check_schemelist(get_token());
    }
}

void parser::check_scheme(string type) {
    schemelist.push_back(check_predicate(type));
}

void parser::check_factlist(string type) {
    if(type == "RULES") {
        return;
    }
    else {
        check_fact(type);
        check_factlist(get_token());
    }
}

void parser::check_fact(string type) {
    factlist.push_back(check_predicate(type));
    match("PERIOD");
}

void parser::check_rulelist(string type) {
    if(type == "QUERIES") {
        return;
    }
    else {
        check_rule(type);
        check_rulelist(get_token());
    }
}

void parser::check_rule(string type) {
    rule r;
    r.head = check_predicate(type);
    match("COLON_DASH");
    check_predicate_list(get_token(), r);
    match("PERIOD");
    rulelist.push_back(r);
}

void parser::check_querylist(string type) {
    check_query(type);
    if(tokens.empty()) {
        return;
    }
    else {
        check_querylist(get_token());
    }
}

void parser::check_query(string type) {
    querylist.push_back(check_predicate(type));
    match("Q_MARK");
}

void parser::check_predicate_list(string type, rule& r) {
    r.pred_rule.push_back(check_predicate(type));
    if(get_token() == "COMMA") {
        match("COMMA");
        check_predicate_list(get_token(), r);
    }
    else {
        return;
    }
}

predicate parser::check_predicate(string type) {
    predicate pred;
    pred.id  = tokens[0].character;
    match("ID");
    match("LEFT_PAREN");
    if(get_token() == "RIGHT_PAREN") {
        error();
    }
    check_parameterlist(get_token(), pred);
    match("RIGHT_PAREN");
    return pred;
}

void parser::check_parameterlist(string type, predicate& pred) {
    if(type == "RIGHT_PAREN") {
        return;
    }
    else {
        check_parameter(type, pred);
        if(get_token() == "COMMA") {
            match("COMMA");
            if(get_token() == "RIGHT_PAREN") {
                error();
            }
            check_parameterlist(get_token(), pred);
        }
        else {
            return;
        }
    }
}

void parser::check_parameter(string type, predicate& pred) {
    parameter para;
    if(type == "STRING") {
        domain.insert(tokens[0].character);
        para.param = tokens[0].character;
        para.type = tokens[0].type;
        pred.pred_list.push_back(para);
        match("STRING");
        return;
    }
    else if(type == "ID") {
        para.param = tokens[0].character;
        para.type = tokens[0].type;
        pred.pred_list.push_back(para);
        match("ID");
        return;
    }
    else {
        error();
    }
}

void parser::match(string type) {
    if(get_token() == type) {
        if(tokens.empty()) {
            error();
        }
        else {
            tokens.erase(tokens.begin());
        }
    }
    else {
        error();
    }
}

void parser::error() {
    stringstream oss;
    oss << tokens[0] << endl;
    throw oss.str();
}
