#include <iostream>
#include <string>
#include <map>
#include <set>
#include <utility>
#include <cctype>
#include <stdexcept>

#include "parser.h"
#include "tablica.h"
#include "main.h"

using namespace std;

// Definicija static clana klase Parser
map<string, TokenType> Parser::kljucneRijeci;

Parser::Parser() : ulaz(0), izlaz(0), baza(0), state(S_IDLE) {
	if (kljucneRijeci.empty()) {
		// vrsimo inicijalizaciju
		kljucneRijeci["BY"] = T_BY;
		kljucneRijeci["CREATE"] = T_CREATE;
		kljucneRijeci["DROP"] = T_DROP;
		kljucneRijeci["FROM"] = T_FROM;
		kljucneRijeci["GROUP"] = T_GROUP;
		kljucneRijeci["HAVING"] = T_HAVING;
		kljucneRijeci["INSERT"] =  T_INSERT;
		kljucneRijeci["INTO"] = T_INTO;
		kljucneRijeci["SELECT"] = T_SELECT;
		kljucneRijeci["TABLE"] = T_TABLE;
		kljucneRijeci["VALUES"] = T_VALUES;
		kljucneRijeci["CHAR"] = T_CHAR;
		kljucneRijeci["BOOL"] = T_BOOL;
		kljucneRijeci["FLOAT"] = T_FLOAT;
		kljucneRijeci["INTEGER"] = T_INTEGER;
		kljucneRijeci["STRING"] = T_STRING;
		kljucneRijeci["COUNT"] = T_COUNT;
		kljucneRijeci["MIN"] = T_MIN;
		kljucneRijeci["MAX"] = T_MAX;
		kljucneRijeci["AVG"] = T_AVG;
		kljucneRijeci["SUM"] = T_SUM;
		kljucneRijeci["TRUE"] = T_BOOLVAL;
		kljucneRijeci["FALSE"] = T_BOOLVAL;
	}
}

TokenType Parser::nextToken() {
	int ch;
	
	// Preskacemo bjeline
	while ((ch = ulaz->get()) != EOF && ch <= ' ')
		;
	
	switch (ch) {
		case EOF: return T_EOF;
		case ',': return T_COMMA;
		case '(': return T_LPAREN;
		case ')': return T_RPAREN;
		case ';': return T_SEMICOLON;
		case '*': return T_STAR;
		case '=': return T_EQUAL;
		case '>':
			if ((ch = ulaz->get()) == '=')
				return T_GREATEREQUAL;
			ulaz->unget();
			return T_GREATER;
		case '<':
			if ((ch = ulaz->get()) == '=')
				return T_LESSEQUAL;
			ulaz->unget();
			return T_LESS;
		case '!':
			if ((ch = ulaz->get()) == '=')
				return T_NOTEQUAL;
			else
				throw runtime_error(string("Leksicka analiza: neprepoznat token \"!") + static_cast<char>(ch) + "\"");
		case '\'':
			// Oznacit cu ipak stringove i charove s '...'
			savedToken.clear();
			while ((ch = ulaz->get()) != EOF && ch != '\'') {
				// escaping mehanizam, da bih mogao ucitati '
				// prvo se evaluira ch == '\\', ako je to istina, ucita se znak i usporedi s EOF
				// ako je EOF, izleti iz petlje, inace nista -- ali znak ostaje ucitan
				if (ch == '\\' && (ch = ulaz->get()) == EOF) break;
				savedToken.push_back(ch);
			}
			if (ch == '\'') {
				if (savedToken.empty() || savedToken.size() == 1) return T_CHARVAL;
				else return T_STRINGVAL;
			}
			else
				throw runtime_error("Leksicka analiza: dosao do EOF u potrazi za krajem stringa.");
		default:
			if (isdigit(ch) || ch == '+' || ch == '-' || ch == '.') {
				// Mogli bismo imati broj.
				bool tocka = false, e = false, digit = false, minus = true;
				savedToken.clear();
								
				if (ch == '.') tocka = true;
				else if (isdigit(ch)) digit = true;
				
				do {
					savedToken.push_back(ch);
					ch = ulaz->get();
					if (ch == '.') {
						if (tocka || e) throw runtime_error(string("Leksicka analiza: znak '.' odmah nakon '") + savedToken + "'");
						else tocka = true;
					}
					else if (ch == 'e' || ch == 'E') {
						if (e) throw runtime_error(string("Leksicka analiza: znak '") + static_cast<char>(ch) + "' odmah nakon '" + savedToken + "'");
						if (!digit) throw runtime_error(string("Leksicka analiza: ocekivana bar jedna znamenka prije eksponenta u numerickoj konstanti ('") + savedToken + static_cast<char>(ch) + "')");
						e = true;
						digit = false;
					}
					else if (ch == '-') {
						if (!e || !minus) throw runtime_error("Leksicka analiza: pojavio se '-' na krivom mjestu u numerickoj konstanti.");
						else minus = false;
					}
					else if (isdigit(ch)) {
						digit = true;
						if (e && minus) minus = false;
					}
				} while (ch != EOF && (isdigit(ch) || ch == '.' || ch == 'E' || ch == 'e' || ch == '-'));
				
				ulaz->unget();
				if (!digit) {
					if (e) throw runtime_error(string("Leksicka analiza: nema znamenki u eksponentu ('") + savedToken + "')");
					else throw runtime_error(string("Leksicka analiza: nema znamenki u numerickoj konstanti ('") + savedToken + "')");
				}
				if (tocka || e) return T_FLOATVAL;
				else return T_INTVAL;
			}
			else if (isalpha(ch)) {
				// Ovdje mozemo ocekivati: kljucnu rijec, stringval, charval, identifikator, boolval
				// Malo je nezgodno sto stringovi i charovi ne moraju biti stavljeni u navodnike.
				// Razrjesujemo stvar ovako: kljucnu rijec i boolval lako identificiramo. Sve sto
				// je vece od jednog znaka smatramo stringvalom, ostalo charvalom. Kad cemo analizirati
				// semantiku, gledat cemo je li stringval/charval mozda identifikator.
				savedToken.clear();
				
				do {
					savedToken.push_back(ch);
					ch = ulaz->get();
				} while (ch != EOF && isalnum(ch));
				
				ulaz->unget();
				if (kljucneRijeci.count(toupper(savedToken))) return kljucneRijeci[toupper(savedToken)];
				// Dogovor sam sa sobom: ako nije kljucna rijec, identifikator je.
				else return T_ID;
			}
			else
				throw runtime_error(string("Leksicka analiza: neocekivan znak '") + static_cast<char>(ch) + "'");
	}
	
	return T_NULL;
}



void Parser::parse(Baza &datara, istream &in, ostream &out)
try {
	state = S_IDLE;
	baza = &datara;
	ulaz = &in;
	izlaz = &out;
	
	TokenType token;
	
	while (!ulaz->eof()) {
		token = nextToken();
		switch (token) {
			case T_CREATE:
				create(); break;
			case T_DROP:
				drop(); break;
			case T_INSERT:
				insert(); break;
			case T_SELECT:
				select(); break;
			case T_EOF:
				break;
			default:
				state = S_ERROR;
				greska();
		}
	}
	
	state = S_FINISHED;
	baza = 0;
	ulaz = 0;
	izlaz = 0;
}
catch (runtime_error &e) {
	baza = 0;
	ulaz = 0;
	izlaz = 0;
	state = S_ERROR;
	throw e;
}

void Parser::create() {
	TokenType token;
	string tablename;
	vector<Tip> tipovi;
	Zapis atributi;
	
	state = S_CREATE;

	if ((token = nextToken()) != T_TABLE) { greska("CREATE: ocekivano: TABLE"); return; }
	if ((token = nextToken()) != T_ID) { greska("CREATE: ocekivano: identifikator tablice"); return; }
	// Ako smo ovdje, imamo identifikator nase tablice
	tablename = savedToken;
	if (baza->count(tablename)) { greska("Tablica " + tablename + " vec postoji!"); return; }
	if ((token = nextToken()) != T_LPAREN) { greska("CREATE: ocekivano: ("); return; }
	while (token != T_RPAREN) {
		if ((token = nextToken()) != T_ID) { greska("CREATE: ocekivano: identifikator atributa"); return; }
	 	atributi.push_back(savedToken);
	 	if (!tipPodatka(token = nextToken())) { greska("CREATE: ocekivano: tip podatka"); return; }
	 	tipovi.push_back(tokToTip(token));
	 	if ((token = nextToken()) != T_COMMA && token != T_RPAREN) { greska("CREATE: ocekivano: )"); return; }
	}
	if ((token = nextToken()) != T_SEMICOLON) { greska("CREATE: ocekivano: ;"); return; }
	// U ovom trenutku spremni smo izvrsiti nasu naredbu
	state = S_IDLE;
	try {
		if (!baza->insert(make_pair(tablename, Tablica(tipovi, atributi, tablename))).second)
			greska("Tablica " + tablename + " nije dodana u bazu.");
		else
			*izlaz << "Tablica " + tablename + " uspjesno dodana u bazu." << endl;
	}
	catch (runtime_error &e) {
		greska(e.what());
	}
}

void Parser::drop() {
	TokenType token;
	string tablename;
	
	state = S_DROP;
	
	if ((token = nextToken()) != T_TABLE) { greska("DROP: ocekivano: TABLE"); return; }
	if ((token = nextToken()) != T_ID) { greska("DROP: ocekivano: identifikator tablice"); return; }
	tablename = savedToken;
	if ((token = nextToken()) != T_SEMICOLON) { greska("DROP: ocekivano: ;"); return; }
	state = S_IDLE;
	if (!baza->erase(tablename)) greska("DROP: Ne postoji tablica " + tablename);
	else *izlaz << "Tablica " << tablename << " uspjesno obrisana." << endl;
}

void Parser::insert() {
	TokenType token;
	string tablename;
	vector<Tip> tipovi;
	Zapis zapis;
	state = S_INSERT;

	if ((token = nextToken()) != T_INTO) { greska("INSERT: ocekivano: INTO"); return; }
	if ((token = nextToken()) != T_TABLE) { greska("INSERT: ocekivano: TABLE"); return; }
	if ((token = nextToken()) != T_ID) { greska("INSERT: ocekivano: identifikator tablice"); return; }
	tablename = savedToken;
	if (!baza->count(tablename)) {
		greska("INSERT: Ne postoji tablica " + tablename);
		return;
	}
	else {
		Tablica &tablica = baza->find(tablename)->second;
		if ((token = nextToken()) != T_VALUES) { greska("INSERT: ocekivano: value"); return; }
		if ((token = nextToken()) != T_LPAREN) { greska("INSERT: ocekivano: ("); return; }
		bool prvi = true; // prva vrijednost je kljuc, ne smije biti NULL
		while (token != T_RPAREN) {
			if (!vrijednost(token = nextToken()) && token != T_COMMA  && token != T_RPAREN /*dopustam NULL unos*/) { greska("INSERT: ocekivano: value"); return; }
			if (token == T_COMMA || token == T_RPAREN) {
				if (prvi) { greska("INSERT: prva vrijednost -- vrijednost kljuca -- ne smije ostati neupisana!"); return; }
				tipovi.push_back(TIP_NULL);
				zapis.push_back("");
			}
			else {
				tipovi.push_back(tokToTip(token));
				zapis.push_back(savedToken);
				token = nextToken();
			}
			if (prvi) prvi = false;
			if (token != T_COMMA && token != T_RPAREN) { greska("INSERT: ocekivano: , ili )"); return; }
		}
		if ((token = nextToken()) != T_SEMICOLON) { greska("INSERT: ocekivano: ;"); return; }
		state = S_IDLE;
		try {
			tablica.dodajZapis(tipovi, zapis);
			*izlaz << "Zapis uspjesno dodan u tablicu " << tablename << "." << endl;
		}
		catch (runtime_error &e) {
			greska(e.what());
		}
	}
}

void Parser::select() {
	TokenType token = T_SELECT;
	bool primamStar = true, imamStar = false, imamFunkciju = false;
	string tablename;
	vector<AgregatnaFunkcija> funkcije;
	Zapis polja;
	set<string> groupby, nonaggregated;
	state = S_SELECT;
	
	while (token != T_FROM) {
		token = nextToken();
		if (!imamStar && token == T_ID) {
			primamStar = false;
			funkcije.push_back(AF_NONE);
			polja.push_back(savedToken);
			nonaggregated.insert(savedToken);
		}
		else if (!imamStar && funkcija(token)) {
			primamStar = false;
			imamFunkciju = true;
			funkcije.push_back(tokToAgregF(token));
			if ((token = nextToken()) != T_LPAREN) { greska("SELECT: ocekivano: ("); return; }
			if ((token = nextToken()) != T_ID) { greska("SELECT: ocekivano: identifikator atributa"); return; }
			polja.push_back(savedToken);
			if ((token = nextToken()) != T_RPAREN) { greska("SELECT: ocekivano: )"); return; }
		}
		else if (token == T_STAR && primamStar) {
			primamStar = false;
			imamStar = true;
		}
		else {
			greska(string("SELECT: ocekivano: ") + (primamStar ? "* ili " : "") + "identifikator atributa ili agregatna funkcija");
			return;
		}
		if ((token = nextToken()) != T_COMMA && token != T_FROM) { greska("SELECT: ocekivano: , ili FROM"); return; }
	}
	
	if ((token = nextToken()) != T_ID) { greska("SELECT: ocekivano: identifikator tablice"); return; }
	tablename = savedToken;
	if (!baza->count(tablename)) {
		greska("SELECT: Ne postoji tablica " + tablename);
		return;
	}
	else {
		Tablica &tablica = baza->find(tablename)->second;
		token = nextToken();
		if (token == T_SEMICOLON) {
			state = S_IDLE;
			if (imamStar)
				tablica.printAll(*izlaz);
			else if (imamFunkciju) {
				// morao bih imati samo agregirana polja
				if (!nonaggregated.empty()) { greska("SELECT: potreban je GROUP BY"); return; }
				try {
					tablica.selectPrint(funkcije, polja, *izlaz);
				}
				catch (runtime_error &e) {
					greska(e.what());
				}
			}
			else {
				try {
					tablica.printSome(polja, *izlaz);
					//tablica.selectPrint(funkcije, polja, *izlaz);
				}
				catch (runtime_error &e) {
					greska(e.what());
				}
			}
		}
		else if (token == T_GROUP) {
			if ((token = nextToken()) != T_BY) { greska("SELECT: ocekivano: BY"); return; }
			while (token != T_HAVING && token != T_SEMICOLON) {
				if ((token = nextToken()) != T_ID) { greska("SELECT: ocekivano: identifikator atributa"); return; }
				groupby.insert(savedToken);
				if ((token = nextToken()) != T_COMMA && token != T_HAVING && token != T_SEMICOLON) { greska("SELECT: ocekivano: , ili HAVING ili ;"); return; }
			}
			if (token == T_SEMICOLON) {
				state = S_IDLE;
				if (groupby != nonaggregated) { greska("SELECT: grupirajuci atributi ne odgovaraju selektiranim neagregiranim atributima"); return; }
				else
					try {
						tablica.selectPrint(funkcije, polja, *izlaz);
					}
					catch (runtime_error &e) {
						greska(e.what());
					}
			}
			else { // token == T_HAVING
				token = nextToken();
				string having, value;
				AgregatnaFunkcija f = AF_NONE;
				string fName;
				if (funkcija(token)) {
					f = tokToAgregF(token);
					fName = savedToken;
					if ((token = nextToken()) != T_LPAREN) { greska("SELECT: ocekivano: ("); return; }
					if ((token = nextToken()) != T_ID) { greska("SELECT: ocekivano: identifikator atributa"); return; }
					having = savedToken;
					if ((token = nextToken()) != T_RPAREN) { greska("SELECT: ocekivano: )"); return; }
				}
				else if (token == T_ID)
					having = savedToken;
				else { greska("SELECT: ocekivano: agregatna funkcija ili identifikator atributa"); return; }
				if (!reloperator(token = nextToken())) { greska("SELECT: ocekivano: <, <=, =, >, >= or !="); return; }
				RelacijskiOperator relOp = tokToRelOp(token);
				if (!vrijednost(token = nextToken())) { greska("SELECT: ocekivano: vrijednost"); return; }
				value = savedToken;
				Tip valueType = tokToTip(token);
				// valja mi provjeriti da se f(having) pojavljuje medju selektiranim atributima
				bool pojavljuje = false;
				for (Zapis::size_type i = 0; i != polja.size(); ++i)
					if (funkcije[i] == f && polja[i] == having) {
						pojavljuje = true;
						break;
					}
				if (!pojavljuje) {
					greska("SELECT: " + fName + (f == AF_NONE ? "" : "(") + having + (f == AF_NONE ? "" : ")") + " iz"
						" HAVING dijela se ne pojavljuje medju selektiranim atributima");
					return;
				} 
				if ((token = nextToken()) != T_SEMICOLON) { greska("SELECT: ocekivano: ;"); return; }
				state = S_IDLE;
				// mogao bih vec vidjeti sto mora biti valueType
				try {
					tablica.selectPrint(funkcije, polja, *izlaz, make_pair(f, having), relOp, value, valueType);
				}
				catch (runtime_error &e) {
					greska(e.what());
				}
			}
		}
		else {
			greska("SELECT: ocekivano: ; ili GROUP");
			return;
		}
	}
}
