#ifndef TABLICA_H_
#define TABLICA_H_

#include <map>
#include <vector>
#include <string>
#include <iostream>
#include <utility>
#include <stdexcept>

#include "main.h"

typedef std::vector<std::string> Zapis;

class Tablica {	
public:
	/* Jedini konstruktor. Parametri:
	 * 	 - const std::vector<Tip> &tipovi:
	 * 			odredjuje tipove atributa tablice
	 * 	 - const Zapis &imena:
	 * 			odjedjuje imena atributa
	 * 	 - const std::string &ime:
	 * 			odredjuje ime tablice (za samu tablicu nije odvec bitno da zna svoje ime, zato default "")
	 * 	 - bool ind:
	 * 			odredjuje je li tablica indeksirana po kljucu (prvom atributu). U slucaju da jest,
	 * 			popunjava se indeks prilikom dodavanja zapisa i provjerava se jedinstvenost kljuca.
	 * 			Dopustamo da tablica nije indeksirana i da nema jedinstven kljuc jer nam objekti
	 * 			tipa Tablica sluze i za pohranu medjurezultata operacije SELECT 
	 **/
    Tablica(const std::vector<Tip> &tipovi, const Zapis &imena, const std::string &ime = "")
		: imeTablice(ime), tipAtributa(tipovi), imeAtributa(imena), trenutni(0)  {
		if (imena.empty())
	    	throw std::runtime_error("Greska pri konstrukciji tablice " + ime +
				"! Potreban barem jedan atribut.");

		if (tipovi.size() != imena.size())
	    	throw std::runtime_error("Greska pri konstrukciji tablice " + ime +
				"! Broj tipova atributa ne odgovara broju imena atributa.");

		for (Zapis::size_type i = 0; i != imena.size(); ++i) {
			if (indeksAtributa.count(imena[i])) // ime nekog atributa se ponovilo
				throw std::runtime_error("Greska pri konstrukciji tablice " + ime +
					"! Imena atributa nisu jedinstvena.");
			
	    	indeksAtributa[imena[i]] = i;
		}
    }

    void dodajZapis(const std::vector<Tip> &, const Zapis&);
    //void brisiZapis(std::string &kljuc);
    
    /* Najkompliciranija funkcija. Uzima popis atributa koje treba selektirati, eventualno ih jos
     * grupira i odabere one ciji je having (relOp) value. Formira privremenu tablicu koju vrati
     * po vrijednosti.
     * 
     * Razmisliti o dodavanju dodatnog parametra -- Tablica & -- u koji bi se spremila privremena
     * tablica.
     * Kako realizirati prijenos podataka o agregatnim funkcijama?
     *  Ostaviti ih u imenu atributa?
     *  Odvojiti atribute na koje djeluje funkcija od ostalih i proslijediti dodatan vektor s popisom fja?
     *    Nije bas dobro jer se gubi poredak agregiranja.
     *  Proslijediti vektor parova: funkcija, ime atributa? Dodamo i identitetu.
     **/
    //Tablica select(const Zapis &atributi, const Zapis & = Zapis(), const std::string & = "", relacijskiOperator = NONE, const std::string & = "");
    void selectPrint(const std::vector<AgregatnaFunkcija> &, const Zapis &, std::ostream & = std::cout,
    				const std::pair<AgregatnaFunkcija, std::string> & = std::make_pair(AF_NONE, ""), RelacijskiOperator = OP_NONE, const std::string & = "", Tip valueType = TIP_NULL) const;
    //Tablica& select() {	return *this; }
	
    Zapis dajZapis() {
		if (naKraju())
	    	throw std::runtime_error("Pokusaj dohvacanja zapisa nakon zadnjeg"
				" u tablici " + imeTablice);

		return zapisi[trenutni++];
    }

    void naPocetak() { trenutni = 0; }

    Zapis prviZapis() const { return zapisi.front(); }
    Zapis zadnjiZapis() const { return zapisi.back(); }

    bool prazna() const { return zapisi.empty(); }
    bool naKraju() const { return trenutni == zapisi.size(); }

    static bool checkType(Tip, Tip);
    static std::string rijecTip(Tip t) {
		switch (t) {
		    case TIP_INTEGER:
				return "INTEGER";
		    case TIP_FLOAT:
				return "FLOAT";
		    case TIP_STRING:
				return "STRING";
		    case TIP_CHAR:
				return "CHAR";
		    case TIP_BOOL:
				return "BOOL";
		    default:
				return "NULL";
		}
    }

    void printAll(std::ostream & = std::cout) const;
    void printSome(const Zapis &, std::ostream & = std::cout) const;
private:
    std::string imeTablice;

	/* Sljedece varijable odredjuju zaglavlje nase tablice. Imamo vektor tipova atributa,
	 * njihova imena i mapu koja imenima pridruzuje indeks u vektoru.
	 **/
    std::vector<Tip> tipAtributa;
    Zapis imeAtributa;
    std::map<std::string, Zapis::size_type> indeksAtributa;
    
    /* Vektor zapisi cuva sve zapise u nasoj tablici. U slucaju da je tablica indeksirana
     * (konstanta indeksirana postavljena na true), odrzavamo indeksPoKljucu i garantiramo
     * jedinstvenost kljuca zapisa.
     **/
    std::vector<Zapis> zapisi;
    std::map<std::string, std::vector<Zapis>::size_type> indeksPoKljucu;

	/* Pri iteraciji kroz tablicu funkcijom dajZapis, trenutni pamti na kojem smo
	 * trenutno zapisu.
	 **/
    std::vector<Zapis>::size_type trenutni;

    /* Pomocne funkcije za usporedbu, zbrajanje i dijeljenje tipova
     **/
    static std::string add(const std::string &, const std::string &, Tip);
    static std::string divide(const std::string &, std::vector<Zapis>::size_type);
    static bool compare(const std::string &, RelacijskiOperator, const std::string &, Tip);
};

typedef std::map<std::string, Tablica> Baza;

#endif /*TABLICA_H_*/
