#include <iostream>
#include <fstream>
#include <stack>
#include <boost/spirit/core.hpp>

#include "TextQuery.h"
#include "Query.h"

using namespace std;
using namespace boost::spirit;

namespace
{
    stack<Query> queryStack;
    void    do_string(char const* str, char const* end)
    {
        string  s(str, end);
        queryStack.push(Query(s));
    }

    void    do_add(char const*, char const*)
    {
        Query rhs=queryStack.top();
        queryStack.pop();
        Query lhs=queryStack.top();
        queryStack.pop();
        Query result=lhs & rhs;
        queryStack.push(result);

    }
    void    do_or(char const*, char const*)
    {
        Query rhs=queryStack.top();
        queryStack.pop();
        Query lhs=queryStack.top();
        queryStack.pop();
        Query result=lhs | rhs;
        queryStack.push(result);

    }

    void    do_not(char const*, char const*)
    {
        Query rhs=queryStack.top();
        queryStack.pop();
        Query result=~rhs;
        queryStack.push(result);

    }
}

struct calculator : public grammar<calculator>
{
    template <typename ScannerT>
    struct definition
    {
        definition(calculator const& /*self*/)
        {
            expression
                =   factor
                    >> *(   ('&' >> factor)[&do_add]
                        |   ('|' >> factor)[&do_or]
                        )
                ;


            factor
                =   lexeme_d[(+alpha_p)[&do_string]]
                |   '(' >> expression >> ')'
                |   ('~' >> factor)[&do_not]
                ;
        }

        rule<ScannerT> expression, term, factor;

        rule<ScannerT> const&
        start() const { return expression; }
    };
};

inline string make_plural(size_t ctr, const string& word, const string& ending)
{
    return (ctr==1)?word:word+ending;
}
void print_results(const set<TextQuery::line_no>& locs, const string& sought, const TextQuery &file)
{
    //if the word was found, then print count and all occurrences
    typedef set<TextQuery::line_no> line_nums;
    line_nums::size_type size=locs.size();
    cout<</*"\n"<<sought<<*/" occurs "
        <<size<<" "
        <<make_plural(size, "time", "s")<<endl;
    //print each line in which the word appeared
    line_nums::const_iterator it=locs.begin();
    for(;it!=locs.end(); ++it)
    {
        cout<<"\t(line "
            //don't confound user with text lines starting at ()
            <<(*it)+1<<" ) "
            <<file.text_line(*it)<<endl;
    }
}


//program takes single argument specifying the file to query
int main(int argc, char **argv)
{
    //open the file from which user will query words
    if(argc<2)
    {
        cerr<<"no input file!"<<endl;
        return EXIT_FAILURE;
    }
    ifstream infile(argv[1]);
    if(infile.fail())
    {
        cerr<<"no input file!"<<endl;
        return EXIT_FAILURE;
    }
    TextQuery tq;
    tq.read_file(infile);       //builds query map
    //iterate with the user: prompt for a word to find and print results
    //loop indefinitely: the loop exit is inside the while
    calculator calc;
    while(true)
    {
        cout<<"enter word to look for, or q to quit: ";
        string s;
        getline(cin,s);
        //stop if hit eof on input or a 'q' is entered
        if(!cin || s=="q" || s=="Q") break;
        //get the set of line numbers on which this word appears

        parse_info<> info = parse(s.c_str(), calc, space_p);

        if (info.full)
        {
            Query query=queryStack.top();

            set<TextQuery::line_no> locs=query.eval(tq);

            query.display(cout);
            print_results(locs, s, tq);

        }
        else
        {
            cout << "-------------------------\n";
            cout << "Parsing failed\n";
            cout << "stopped at: \": " << info.stop << "\"\n";
            cout << "-------------------------\n";
        }
    }
    return 0;
}