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

#include <sstream>
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include <map>
#include <utility>
#include <cctype>
#include <cassert>

using namespace std;

/* checkType provjerava odgovara li dobiveni tip tipu koji mi treba
 * npr. svaki tip je dobar ako trebam STRING...
 **/
inline bool Tablica::checkType(Tip imam, Tip trebam) {
    switch (trebam) {
		case TIP_INTEGER:
	    	return imam == TIP_NULL || imam == TIP_INTEGER; 
		case TIP_FLOAT:
			return imam == TIP_NULL || imam == TIP_INTEGER || imam == TIP_FLOAT;
		case TIP_STRING:
		    return true;
		case TIP_CHAR:
		    return imam == TIP_NULL || imam == TIP_CHAR;
		case TIP_BOOL:
			return imam == TIP_NULL || imam == TIP_BOOL;
		default: // TIP_NULL
		    return imam == TIP_NULL;
    }
}

/* Funkcija koja dodaje novi zapis u tablicu.
 **/
void Tablica::dodajZapis(const vector<Tip> &tipovi, const Zapis &zapis) {
    // Provjera odgovara li zapis tipu tablice:
    //  1. po dimenziji
    if (zapis.size() != tipAtributa.size())
		throw runtime_error("Dodavanje zapisa u tablicu "
		    + imeTablice + ": broj elemenata zapisa ne odgovara broju atributa tablice.");
    
    //  2. po tipovima sadrzaja pojedinih celija 
    for (Zapis::size_type i = 0; i != zapis.size(); ++i) {
    	// prihvacam ako je tipAtributa CHAR i zapis[i] duljine 1 cak i ako ne prodje checkType
		if (!checkType(tipovi[i], tipAtributa[i]) && (tipAtributa[i] != TIP_CHAR || zapis[i].size() != 1))
		    throw runtime_error("Dodavanje zapisa u tablicu "
		    	+ imeTablice + ": dobiven <" + zapis[i] + "> tipa " + rijecTip(tipovi[i]) + ", a ocekivan " + rijecTip(tipAtributa[i]) + ".");
    }

	// Osiguravamo jedinstvenost kljuca
    if (indeksPoKljucu.count(zapis[0]))
		throw runtime_error("Dodavanje zapisa u tablicu " + imeTablice +
		    ": zapis s kljucem <" + zapis[0] + "> vec postoji!");

	// Konacno dodajemo zapis u tablicu i azuriramo indeks.
    zapisi.push_back(zapis);
	indeksPoKljucu[zapis[0]] = zapisi.size() - 1;
}

/* Funkcija koja ispisuje tablicu na zadani ostream os.
 **/
void Tablica::printAll(ostream &os) const {
	// Prvo moramo saznati maksimalnu sirinu svakog stupca zbog lijepog formatiranja
	vector<string::size_type> maxSirina;
	
	// inicijaliziramo sa sirinom imena stupca
	for (Zapis::const_iterator it = imeAtributa.begin(); it != imeAtributa.end(); ++it)
		maxSirina.push_back(it->size());
	
	// proiteriramo kroz zapise i modificiramo maxSirinu
	for (vector<Zapis>::const_iterator it = zapisi.begin(); it != zapisi.end(); ++it)
		for (Zapis::size_type i = 0; i != it->size(); ++i)
			if ((*it)[i].size() > maxSirina[i])
				maxSirina[i] = (*it)[i].size();

    char fillch = os.fill(); // Pamtimo trenutni fill char kako bismo ga mogli vratiti na kraju.

    // Ispis zaglavlja tablice. Iz maxSirina saznajemo dimenziju svakog stupca.
    // Izgleda kao black magic, ali u sustini je stvar jasna. Igramo se manipulatorima.
    os << '+';
    for (vector<string::size_type>::size_type i = 0; i != maxSirina.size(); ++i)
		os << setw(maxSirina[i] + 3) << setfill('-') << '+'; // +3 zbog '-' iznad 2 razmaka oko imena atributa i 1 mjesta za +
    os << endl << "| ";
    for (Zapis::size_type i = 0; i != imeAtributa.size(); ++i)
		os << setw(maxSirina[i]) << setfill(' ') << imeAtributa[i]
	    	<< (i + 1 == imeAtributa.size() ? " |" : " | ");
    os << endl << '+';
    for (vector<string::size_type>::size_type i = 0; i != maxSirina.size(); ++i)
		os << setw(maxSirina[i] + 3) << setfill('-') << '+';
    os << endl;

    // Ispis same tablice.
    for (vector<Zapis>::const_iterator it = zapisi.begin(); it != zapisi.end(); ++it) {
		os << "| ";
		for (Zapis::size_type i = 0; i != it->size(); ++i)
	    	os << setw(maxSirina[i]) << setfill(' ') << (*it)[i]
				<< (i + 1 == it->size() ? " |" : " | ");
		os << endl;
    }
	os << '+';
	for (vector<string::size_type>::size_type i = 0; i != maxSirina.size(); ++i)
    	os << setw(maxSirina[i] + 3) << setfill('-') << '+';
	os << endl;

	// Vracamo os u stanje u kojem smo ga dobili.
    os.fill(fillch);
}

void Tablica::printSome(const Zapis &polja, ostream &os) const {
	// Saznajemo koji su tocno indeksi atributa koje printamo
	// Usput inicijaliziramo sirinu stupaca
	vector<Zapis::size_type> atribut;
	vector<string::size_type> maxSirina;

	for (Zapis::const_iterator it = polja.begin(); it != polja.end(); ++it) {
		if (!indeksAtributa.count(*it))
			throw runtime_error("Atribut " + *it + " ne postoji u tablici " + imeTablice + ".");
		atribut.push_back(indeksAtributa.find(*it)->second);
		maxSirina.push_back(it->size());
	}
	
	// proiteriramo kroz zapise i modificiramo maxSirinu
	for (vector<Zapis>::const_iterator it = zapisi.begin(); it != zapisi.end(); ++it)
		for (vector<Zapis::size_type>::size_type i = 0; i != atribut.size(); ++i)
			if ((*it)[atribut[i]].size() > maxSirina[i])
				maxSirina[i] = (*it)[atribut[i]].size();

    char fillch = os.fill(); // Pamtimo trenutni fill char kako bismo ga mogli vratiti na kraju.

    // Ispis zaglavlja tablice. Iz maxSirina saznajemo dimenziju svakog stupca.
    // Izgleda kao black magic, ali u sustini je stvar jasna. Igramo se manipulatorima.
    os << '+';
    for (vector<string::size_type>::size_type i = 0; i != maxSirina.size(); ++i)
		os << setw(maxSirina[i] + 3) << setfill('-') << '+'; // +3 zbog '-' iznad 2 razmaka oko imena atributa i 1 mjesta za +
    os << endl << "| ";
    for (Zapis::size_type i = 0; i != polja.size(); ++i)
		os << setw(maxSirina[i]) << setfill(' ') << polja[i]
	    	<< (i + 1 == polja.size() ? " |" : " | ");
    os << endl << '+';
    for (vector<string::size_type>::size_type i = 0; i != maxSirina.size(); ++i)
		os << setw(maxSirina[i] + 3) << setfill('-') << '+';
    os << endl;

    // Ispis same tablice.
    for (vector<Zapis>::const_iterator it = zapisi.begin(); it != zapisi.end(); ++it) {
		os << "| ";
		for (vector<Zapis::size_type>::size_type i = 0; i != atribut.size(); ++i)
	    	os << setw(maxSirina[i]) << setfill(' ') << (*it)[atribut[i]]
				<< (i + 1 == atribut.size() ? " |" : " | ");
		os << endl;
    }
	os << '+';
	for (vector<string::size_type>::size_type i = 0; i != maxSirina.size(); ++i)
    	os << setw(maxSirina[i] + 3) << setfill('-') << '+';
	os << endl;

	// Vracamo os u stanje u kojem smo ga dobili.
    os.fill(fillch);
}

/* Eh, taj selectPrint...
 **/
void Tablica::selectPrint(const vector<AgregatnaFunkcija> &funkcije, const Zapis &atributi, ostream &os, const pair<AgregatnaFunkcija, string> &having, RelacijskiOperator relOp, const string &value, Tip valueType) const {
	// ubaci neki error handling
	assert(funkcije.size() == atributi.size());
	
	// Znam da grupiranje ide po neagregirajucim atributima (onima s funkcijom AF_NONE) 
	vector<Zapis::size_type> nonagreg;
	vector<pair<AgregatnaFunkcija, Zapis::size_type> > agreg;
	
	// havingInd mi sluzi za pamcenje indeksa polja po kojem radim usporedbu.
	// Pamtim indeks u polju rasporedPolja. havingType je tip having polja (ovisi o agregatnoj funkciji).
	vector<pair<bool, Zapis::size_type> >::size_type havingInd = 0;
	Tip havingType;
	
	// rasporedPolja cu koristiti kod ispisa da znam koji mi je raspored polja.
	// true znaci agregirano, false neagregirano polje
	vector<pair<bool, Zapis::size_type> > rasporedPolja;
	
	Zapis::size_type tmpInd;

	// Prvo odvajamo agregirajuce i neagregirajuce indekse
	for (Zapis::size_type i = 0; i != atributi.size(); ++i) {
		if (!indeksAtributa.count(atributi[i]))
			throw runtime_error("Atribut " + atributi[i] + " ne postoji u tablici " + imeTablice + ".");
		// Pazi! Ovdje ne mozes napraviti indeksAtributa[atributi[i]] jer ti je funkcija const.
		// Zato koristim find koji vraca iterator na element mape indeksAtributa. ->second je
		// trazeni indeksAtributa.
		tmpInd = indeksAtributa.find(atributi[i])->second;
		if (funkcije[i] == AF_NONE) {
			nonagreg.push_back(tmpInd);
			rasporedPolja.push_back(make_pair(false, nonagreg.size() - 1));
		}
		else {
			agreg.push_back(make_pair(funkcije[i], tmpInd));
			rasporedPolja.push_back(make_pair(true, agreg.size() - 1));
			//provjera da su agregatne funkcije pozvane na odgovarajucim tipovima atributa
			if ((funkcije[i] == AF_AVG || funkcije[i] == AF_SUM) && tipAtributa[tmpInd] != TIP_INTEGER && tipAtributa[tmpInd] != TIP_FLOAT)
				throw runtime_error(string("Funkcija ") + (funkcije[i] == AF_AVG ? "AVG" : "SUM") + " pozvana na atributu tipa " + rijecTip(tipAtributa[tmpInd]));					
		}
		if (funkcije[i] == having.first && atributi[i] == having.second) {
			havingInd = i;
			if (having.first == AF_AVG) havingType = TIP_FLOAT;
			else if (having.first == AF_COUNT) havingType = TIP_INTEGER;
			else havingType = tipAtributa[tmpInd];
			// Provjera da havingType odgovara valueTypeu, kak cemo inace usporediti?
			const char *poruka = "Pokusaj usporedbe neusporedivih tipova";
			switch (havingType) {
				case TIP_STRING: case TIP_CHAR:
					if (valueType != TIP_STRING && valueType != TIP_CHAR) throw runtime_error(poruka);
					break;
				case TIP_INTEGER: case TIP_FLOAT:
					if (valueType != TIP_INTEGER && valueType != TIP_FLOAT) throw runtime_error(poruka);
					if (valueType == TIP_FLOAT) havingType = TIP_FLOAT;
					break;
				default: // TIP_BOOL
					if (valueType != TIP_BOOL) throw runtime_error(poruka);
			}		
		}
	}
	
	// Ovdje vektoru stringova, sto ce biti elementi po kojima grupiramo, pridruzujem
	// vektor parova (string, count) -- za svaki agregirani atribut po jedan par.
	map<vector<string>, vector<pair<string, vector<Zapis>::size_type> > > agregat;
	Zapis slobodnaPolja;
	
	for (vector<Zapis>::const_iterator it = zapisi.begin(); it != zapisi.end(); ++it) {
		slobodnaPolja.clear();
		for (vector<Zapis::size_type>::iterator jt = nonagreg.begin(); jt != nonagreg.end(); ++jt)
			slobodnaPolja.push_back((*it)[*jt]);
		// slobodnaPolja sadrze vrijednosti slobodnih polja za trenutni zapis
		// moramo updateati agregat
		
		if (agregat[slobodnaPolja].empty()) {
			// inicijalizirajmo agregat[slobodnaPolja]
			for (vector<pair<AgregatnaFunkcija, Zapis::size_type> >::size_type i = 0; i != agreg.size(); ++i)
				agregat[slobodnaPolja].push_back(make_pair((*it)[agreg[i].second], 1));
		}
		else {
			// update agregat[slobodnaPolja]
			for (vector<pair<AgregatnaFunkcija, Zapis::size_type> >::size_type i = 0; i != agreg.size(); ++i) {
				switch (agreg[i].first) {
					case AF_AVG:
						if (!(*it)[agreg[i].second].empty())
							agregat[slobodnaPolja][i].first = add((*it)[agreg[i].second], agregat[slobodnaPolja][i].first, tipAtributa[agreg[i].second]);
							//break; namjerno ispusten
					case AF_COUNT:
						if (!(*it)[agreg[i].second].empty())
							++agregat[slobodnaPolja][i].second;
						break;
					case AF_MIN:
						if (!(*it)[agreg[i].second].empty() && compare((*it)[agreg[i].second], OP_LESS, agregat[slobodnaPolja][i].first, tipAtributa[agreg[i].second]))
							agregat[slobodnaPolja][i].first = (*it)[agreg[i].second];
						break;
					case AF_MAX:
						if (!(*it)[agreg[i].second].empty() && compare((*it)[agreg[i].second], OP_GREATER, agregat[slobodnaPolja][i].first, tipAtributa[agreg[i].second]))
							agregat[slobodnaPolja][i].first = (*it)[agreg[i].second];
						break;
					case AF_SUM:
						if (!(*it)[agreg[i].second].empty())
							agregat[slobodnaPolja][i].first = add((*it)[agreg[i].second], agregat[slobodnaPolja][i].first, tipAtributa[agreg[i].second]);
					default:
						;
				}
			}
		}
	}
	
	// Proiterirao sam kroz zapise i popunio svoju mapu agregat. Sad moram napraviti ispis.
	// Prvo iteriramo kroz nasu mapu i odredjujemo maxSirinu. Ukoliko je ostalo negdje za napraviti
	// dijeljenje jer smo racunali AVG, napravimo i to.
	
	vector<string::size_type> maxSirina;
	
	// Imena funkcija, koristimo kod ispisa.
	const char *imeAgrFunkcijeTmp[] = { "", "COUNT(", "MIN(", "MAX(", "AVG(", "SUM(" };
	vector<string> imeFunkcije(imeAgrFunkcijeTmp, imeAgrFunkcijeTmp + 6);
	
	for (Zapis::size_type i = 0; i != atributi.size(); ++i)
		// Moramo racunati da cemo ponegdje na ime atributa nakeljiti ime funkcije, zato ?: operator
		maxSirina.push_back(atributi[i].size() + (funkcije[i] == AF_NONE ? 0 : imeFunkcije[funkcije[i]].size() + 1));
	
	map<vector<string>, vector<pair<string, vector<Zapis>::size_type> > >::iterator iter = agregat.begin();
	
	while (iter != agregat.end()) {
		for (vector<pair<bool, Zapis::size_type> >::size_type i = 0; i != rasporedPolja.size(); ++i) {
			// rasporedPolja[i].first == true znaci da je to polje agregirano
			if (rasporedPolja[i].first) {
				if (agreg[rasporedPolja[i].second].first == AF_AVG)
					iter->second[rasporedPolja[i].second].first = divide(iter->second[rasporedPolja[i].second].first, iter->second[rasporedPolja[i].second].second);
				else if (agreg[rasporedPolja[i].second].first == AF_COUNT) {
					// Prebacujemo podatak o countu koji smo pamtili kao unsigned u string
					ostringstream bljuc;
					bljuc << iter->second[rasporedPolja[i].second].second;
					iter->second[rasporedPolja[i].second].first = bljuc.str();
				}
				if (maxSirina[i] < iter->second[rasporedPolja[i].second].first.size())
					maxSirina[i] = iter->second[rasporedPolja[i].second].first.size();
			}
			else {
				if (maxSirina[i] < iter->first[rasporedPolja[i].second].size())
					maxSirina[i] = iter->first[rasporedPolja[i].second].size();
			}
		}
		++iter;
	}
	
	// Spremni smo za ispis na os
	
	char fillch = os.fill(); // Pamtimo trenutni fill char kako bismo ga mogli vratiti na kraju.

    // Ispis zaglavlja tablice. Iz maxSirina saznajemo dimenziju svakog stupca.
    // Izgleda kao black magic, ali u sustini je stvar jasna. Igramo se manipulatorima.
    os << '+';
    for (vector<string::size_type>::size_type i = 0; i != maxSirina.size(); ++i)
		os << setw(maxSirina[i] + 3) << setfill('-') << '+';
    os << endl << "| ";
    for (Zapis::size_type i = 0; i != atributi.size(); ++i)
		os << setw(maxSirina[i]) << setfill(' ')
			<< (funkcije[i] == AF_NONE ? "" : imeFunkcije[funkcije[i]]) + atributi[i] + (funkcije[i] == AF_NONE ? "" : ")")
    		<< (i + 1 == atributi.size() ? " |" : " | ");
    os << endl << '+';
    for (vector<string::size_type>::size_type i = 0; i != maxSirina.size(); ++i)
		os << setw(maxSirina[i] + 3) << setfill('-') << '+';
    os << endl;

    // Ispis same tablice.
    for (iter = agregat.begin(); iter != agregat.end(); ++iter) {
    	// Ispisujemo zapis samo u slucaju da having polje zadovoljava having uvjet
    	if (relOp != OP_NONE &&
    			((rasporedPolja[havingInd].first && !compare(iter->second[rasporedPolja[havingInd].second].first, relOp, value, havingType)) ||
    			(!rasporedPolja[havingInd].first && !compare(iter->first[rasporedPolja[havingInd].second], relOp, value, havingType))))
    		continue;

		os << "| ";
		for (vector<pair<bool, Zapis::size_type> >::size_type i = 0; i != rasporedPolja.size(); ++i) {
			if (rasporedPolja[i].first)
	    		os << setw(maxSirina[i]) << setfill(' ') << iter->second[rasporedPolja[i].second].first;
			else
	    		os << setw(maxSirina[i]) << setfill(' ') << iter->first[rasporedPolja[i].second];
			os << (i + 1 == rasporedPolja.size() ? " |" : " | ");
		}
		os << endl;
    }
	os << '+';
	for (vector<string::size_type>::size_type i = 0; i != maxSirina.size(); ++i)
    	os << setw(maxSirina[i] + 3) << setfill('-') << '+';
	os << endl;

	// Vracamo os u stanje u kojem smo ga dobili.
    os.fill(fillch);
}

string Tablica::add(const string &a, const string &b, Tip type) {
	if (a.empty()) return b;
	if (b.empty()) return a;
	
	if (type != TIP_INTEGER && type != TIP_FLOAT)
		throw runtime_error("Pokusaj zbrajanja podataka koji nisu tipa INTEGER ili FLOAT.");
	
	istringstream sa(a);
	istringstream sb(b);
	ostringstream result;
	int myIntA, myIntB;
	long double myFloatA, myFloatB; // Koristim interno long double za racunanje
	
	if (type == TIP_INTEGER) {
		sa >> myIntA;
		sb >> myIntB;
		result << myIntA + myIntB;
	}
	else if (type == TIP_FLOAT) {
		sa >> myFloatA;
		sb >> myFloatB;
		result << myFloatA + myFloatB;
	}
	
	return result.str();
}

string Tablica::divide(const string &a, vector<Zapis>::size_type b) {
	if (a.empty()) return a;
	if (b == 0) return "";
	
	istringstream sa(a);
	ostringstream result;
	long double myFloatA;
	
	sa >> myFloatA;
	result << myFloatA / b;
	
	return result.str();
}

bool Tablica::compare(const string &a, RelacijskiOperator relOp, const string &b, Tip type) {
	// uvjet koji pomaze u funkciji selectPrint kod ispisa s obzirom na having uvjet
	if ((a.empty() || b.empty()) && type != TIP_STRING && type != TIP_CHAR)
		return false;

	istringstream sa(a);
	istringstream sb(b);
	string upCaseA = toupper(a);
	string upCaseB = toupper(b);
	long double myFloatA; long double myFloatB;
	int myIntA; int myIntB;
	bool result = false;
	
	switch (type) {
		case TIP_INTEGER:
			sa >> myIntA; sb >> myIntB;
			if (relOp == OP_LESS) result = myIntA < myIntB;
			else if (relOp == OP_LESSEQUAL) result = myIntA <= myIntB;
			else if (relOp == OP_GREATER) result = myIntA > myIntB;
			else if (relOp == OP_GREATEREQUAL) result = myIntA >= myIntB;
			else if (relOp == OP_NOTEQUAL) result = myIntA != myIntB;
			else result = myIntA == myIntB;
			break;
		case TIP_FLOAT:
			sa >> myFloatA; sb >> myFloatB;
			if (relOp == OP_LESS) result = myFloatA < myFloatB;
			else if (relOp == OP_LESSEQUAL) result = myFloatA <= myFloatB;
			else if (relOp == OP_GREATER) result = myFloatA > myFloatB;
			else if (relOp == OP_GREATEREQUAL) result = myFloatA >= myFloatB;
			else if (relOp == OP_NOTEQUAL) result = myFloatA != myFloatB;
			else result = myFloatA == myFloatB;
			break;
		case TIP_BOOL:
			if (relOp == OP_LESS) result = upCaseA < upCaseB;
			else if (relOp == OP_LESSEQUAL) result = upCaseA <= upCaseB;
			else if (relOp == OP_GREATER) result = upCaseA > upCaseB;
			else if (relOp == OP_GREATEREQUAL) result = upCaseA >= upCaseB;
			else if (relOp == OP_NOTEQUAL) result = upCaseA != upCaseB;
			else result = upCaseA == upCaseB;
			break;
		default:
			if (relOp == OP_LESS) result = a < b;
			else if (relOp == OP_LESSEQUAL) result = a <= b;
			else if (relOp == OP_GREATER) result = a > b;
			else if (relOp == OP_GREATEREQUAL) result = a >= b;
			else if (relOp == OP_NOTEQUAL) result = a != b;
			else result = a == b;
	}
	
	return result;
}
