#include <stdio.h>
#include <string.h>
#include <iostream>
#include <string>
#include <vector>
#include <unordered_set>
#include <algorithm>

#include "Employee.hpp"
#include "Worker.hpp"
#include "Contractee.hpp"
#include "Manager.hpp"
#include "Company.hpp"

#include "help_message.h"

using namespace std;

/* globális példaértékű Company példány amin a "shell" operálhat
 */
Company mycompany("Beadandó Inc.");

// Ambiens Osztály

/* vissza adja a felhasználói input 'i'-edik mezejét,
 *  ahol a mezők white space szeparált string-ek ként vannak definiálva
 */
string parse_field(const string &line, int i) {
	/* ezt a függvényt könyebb C string-ekkel implementálni,
	 *  de az input std::string-ként jut ide kényelmesen,
	 *  ezért "átváltjuk"
	 */
	const char * line_p = line.c_str();	// mező eleje
	const char * end_p  = line_p;		// mező vége
	for (int h = 0; h < i; h++) {	// iterate
		line_p = end_p;
		while (isspace(*line_p)) {	// find begin
			++line_p;
		}
		end_p = line_p;
		while (!isspace(*end_p)		// find end
		&& *end_p != '\0') {
			++end_p;
		}
	}

	string r = string(line_p, (end_p) - line_p);

	return r;
}

/* felhasználói parancs értelmezése
 */
void command(const string &line) {
	if (line == "q") {		// "Quit"
		throw int();
	} else if (line[0] == 'h') {	// "Help"
		puts(help_message);
		return;
	} else if (line.substr(0, 2) == "c ") { // "Create"
		// ?! példa
		if (line.size() < 5) {
			return;
		}

		string name = parse_field(line, 3);
		//puts(name.c_str());

		const char type = line[2];
		Employee * e;
		switch (type) {
			case 'w': {
				e = (Employee *)(new Worker(name));
			} break;
			case 'c': {
				e = (Employee *)(new Contractee(name));
			} break;
			case 'm': {
				e = (Employee *)(new Manager(name, &mycompany));
			} break;
			default: {
				fputs("No employee created.", stderr);
				return;
			} break;
		}
		mycompany.employees.push_back(e);
		puts("Employee created.");
		return;
	} else if (line.substr(0, 2) == "i ") { // "Info"
		string id = parse_field(line, 2);

		Employee * e = mycompany.by_id(id);
		if (e) {
			e->details();
			puts("");
		}

		return;
	} else if (line.substr(0, 2) == "m ") { // "Modify"
		string id = parse_field(line, 2);
		string modification = parse_field(line, 3);

		Employee * e = mycompany.by_id(id);
		if (e) {
			puts(e->modify(modification) ? "Modification performed." : "Modification error.");
		}

		return;
	} else if (line.substr(0, 2) == "d ") {	// "Delete"
		string id = parse_field(line, 2);
		for (size_t i = 0; i < mycompany.employees.size(); i++) {
			if (std::to_string(mycompany.employees[i]->id_m) == id) {
				mycompany.employees.erase(mycompany.employees.begin() + i);
				break;
			}
		}

		return;
	} else if (line[0] == 'l') {	// "List"
		mycompany.print();
		return;
	/* ------
	 * a biztonsági mentés beégetve pusztán azért,
	 *  hogy kényelmesebb legyen tesztelni
	 */
	} else if (line[0] == 'w') {	// "Write"
		mycompany.bak("./data/bak.csv");
		return;
	} else if (line[0] == 'r') {	// "Read"
		mycompany.load("./data/bak.csv");
		return;
	/* ---
	 */
	} else {
		puts(help_message);
		return;
	}
}

void filter(unordered_set<Employee*> &s, const char * const f) {
	if (not *f) { return; }

	switch (f[0]) {
		case '.': {	// mező érték alapú szűrés
			for (const auto &i : mycompany.employees) {
				if (i->is_match(f)) {
					s.insert(i);
				}
			}
		} break;
		default: {	// feltehetőleg név
			auto t = mycompany.by_name(f);
			s.insert(t.begin(), t.end());
		} break;
	}
}

void search(string line) {
	/* vissza térési lista;
	 *  `set`, hogy ne kézzel kelljen duplikált bejegyzéseket ellenőrizni
	 */
	unordered_set<Employee*> r;

	/* space szeparált mezőnként szűrünk;
	 *  a találatokat hozzáfűzzük az eddigiekhez;
	 *  a szűrők között "vagy" kapcsolat áll fen
	 */
	char * dup = strdup(line.c_str()+1);
	const char * delim = " ";
	char * data = strtok(dup, delim);
	int i = 0;
	do {
		filter(r, data);
		++i;
	} while((data = strtok(NULL, delim), data));

	for (const auto &e : r) {
		e->details();
		puts("");
	}

	free(dup);
}

/* top-level user input kezelő
 */
void input() {
	// Beolvasás
	string line;
	fputs("$ ", stdout);
	std::getline(std::cin, line);

	if (std::cin.eof()) {
		puts("");
		throw int();
	}

	// Értelmezés
	switch (line[0]) {
		case '/': {	// keresés
			search(line);
		} break;
		default: {	// parancs
			command(line);		
		};
	}
}

bool init() {
	/* (lehet, hogy) keverjük a C/C++ I/O-t ezért szinkronizáljuk az
	 *  std iostream objektumokat az stdio függvényekkel;
	 *  számomra a C stílusú output és a C++ stílusú input esik kézre,
	 *  valós projektben persze csak az egyiket használnám
	 */
	std::ios_base::sync_with_stdio(true);
	return true; // megszokásból
}

signed main() {
	init();
	//#include "dummy_constructors.inc"
	//mycompany.bak("./data/employees.csv");
	
	if(mycompany.load("./data/employees.csv")) {
		fputs("Fatális hiba: adatok betöltése sikertelen", stderr);
		return 1;
	}

	mycompany.print();

	/* a kilépést kizárólag azért implementálom kivételel,
	 * mert szerettem volna kivételkezelést használni a programban,
     * de máshol sem lett volna kevésbe értelmetlen
	 */
	try {
		for (;;) {
			input();
		}
	} catch (...) {
		;
	}

	return 0;
}