/** Piotr Stepaniak - E1I4

Słowniczek

		ODBC -	Interfejs pozwalający programom łączyć się z systemami zarządzającymi bazami danych. 
				Jest to API niezależne od języka programowania, systemu operacyjnego i bazy danych. (Wikipedia)
		
		SQLite - Baza danych, której cechą charakterystyczną jest brak rozbudowanej architektury klient-serwer. 
				Dane są przechowywane w plikach binarnych, a operacje SQL-owe są wykonywane przy użyciu dołączonej 
				biblioteki dll lub sterownika ODBC. Bezpieczeństwo przechowywanych danych zależy od bezpieczeństwa 
				zapewnianego przez system plików systemu operacyjnego. 
				(http://www.sqlite.org/)

Wymagania wstępne

		Aby skorzystać z poniższego przykładu należy:

		1) Zainstalować sterowniki ODBC dla bazy danych SQLite (http://www.ch-werner.de/sqliteodbc/)
		2) Zaopatrzyć się w wersję 4.x otl-a (http://otl.sourceforge.net/otl3_down.htm).
		3) Stworzyć przykładowe połączenie ODBC o parametrach opisanych w stałej DEFAULT_CONNECTION, 
		zgodnie z konwencją (login/password@user_dsn_name), gdzie user_dsn_name to nazwa połączenia 
		ODBC.

ODBC w Windows XP

		Aby stworzyć własne połączenie ODBC w systemie Windows XP, należy:

		a) Wybrać 'Panel sterowania', a następnie 'Narzędzia administracjne'
		b) Wybrać 'Źródła danych (ODBC)'
		c) W zakładce 'User DSN' naisnąć przycisk 'Add'
		d) Z wybranej listy wybrać sterownik SQLite
		e) W następnym formularzu określić nazwę połączenia, a także plik zawierający bazę danych.

Baza danych SQLite
		
		Aby stworzyć bazę danych SQLite można posłużyć się konsolowym programem dostępnym na stronie projektu.

		Wystarczy wówczas wpisać:

			sqlite3 nazwa_bazy_danych

		aby stworzyć plik zawierający bazę danych o podanej nazwie. Po wpisaniu powyższej komendy uruchomi się
		konsola sql-owa. Komenda .help wyświetla dostępne dodatkowe polecenia.

		Innym przydatnym narzędziem jest sqlite analyzer również dostępny na stronie projektu. Udostępnia on 
		między innymi graficzną reprezentację tabel, a także edytor zapytań sql.

Opis przykładu
		
		Poniższy przykład pokazuje jak przy użyciu sqlite'a i otl'a stworzyć proste mapowanie:

			relacja (w sensie relacyjnych baz danych) - klasa ( w sensie programowania obiektowego)

		Powyższa technika ułatwia projektowanie oprogramowania (wzorzec entity - control) i bywa często stosowana
		w programowaniu obiektowym (patrz np. Hibernate).
		

*/

#include <iostream>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <string>
#include <vector>
#include <iterator>

#define OTL_ODBC // korzystaj z odbc 
#define OTL_STL  // udostępnienie funkcjonalności STL-owej 

//#define ODBCVER 0x0250 // Za pomocą tej komendy można określić wersję sterownika otl_odbc

#define DEFAULT_CONNECTION "scott/tiger@test3" // dane połączenia (user/password@odbc_name)

#include "otlv4.h"

using namespace std;


/** 
Klasa służąca do przechowywania daty rozpoczęcia pracy przez danego pracownika
*/
class Date {

	public:
		Date() {
			year = 1; month = 1; day = 1;
		};
		
		Date(const Date &d) {
			year= d.year; month = d.month; day = d.day;
		}

		Date(int y, int m, int d) : year(y), month(m), day(d) {};
	
		int getYear() const {
			return year;
		}

		int getMonth() const{
			return month;
		}

		int getDay() const {
			return day;
		}

	private:
		int year;
		int month;
		int day;
};

/**
Klasa reprezentująca pracownika
*/
class Employee {

	public:
		Employee (int i, const char* fname, const char* lname, int dId, int s, Date sDate) : 
			id(i), firstname(fname), lastname(lname), departmentId(dId), salary(s), startDate(sDate) {
		};

		Employee() {};

		Employee(const Employee &emp) {
			id = emp.getId();
			firstname = emp.getFirstname();
			lastname = emp.getLastname();
			departmentId = emp.getDepartmentId();
			salary = emp.getSalary();
			startDate = emp.getDate();
		}

		~Employee() {};

		void setId(int i) {
			id = i;
		}

		void setFirstname(string fname) {
			firstname = fname;
		}

		void setLastname(string lname) {
			lastname = lname;
		}

		void setDepartmentId(int dId) {
			departmentId = dId;
		}

		void setSalary(int s) {
			salary = s;
		}

		void setStartDate(Date sDate) {
			startDate = sDate;
		}

		int getId() const {
			return id;
		}

		const char* getFirstname() const {
			return firstname.c_str();
		}

		const char* getLastname() const {
			return lastname.c_str();
		}

		int getSalary() const {
			return salary;
		}

		int getDepartmentId() const {
			return departmentId;
		}

		Date getDate() const {
			return startDate;
		}

		Employee& operator=(const Employee &emp) {
			id = emp.getId();
			firstname = emp.getFirstname();
			lastname = emp.getLastname();
			departmentId = emp.getDepartmentId();
			salary = emp.getSalary();
			startDate = emp.getDate();
			return *this;
		}

		
		/**
		Przeciążenie operatora << dla strumienia typu otl_stream.
		Używany przy pobieraniu kolekcji pracowników z bazy (getEmployees).
		*/
		friend otl_stream& operator>>(otl_stream& s, Employee& emp) {
			
			char firstname[31],lastname[31];
			int id,departmentId, salary;
			otl_datetime date;
			bool create = false;
			
			s>>id;
			s>>firstname;
			s>>lastname;
			s>>departmentId;
			s>>salary;
			s>>date;
				
			emp.setId(id);
			emp.setFirstname(firstname);
			emp.setLastname(lastname);
			emp.setDepartmentId(departmentId);
			emp.setSalary(salary);
			emp.setStartDate(Date(date.year,date.month,date.month));

			return s;
		}

		/**
		Przeciążenie operatora << dla strumienia typu otl_stream.
		Używany w operacjach insert.
		*/
		friend otl_stream& operator<< (otl_stream& s, const Employee& emp) {
			Date date = emp.getDate();
			otl_datetime datetime(date.getYear(),date.getMonth(),date.getDay(),0,0,0);  

			s<<emp.getId()
			 <<emp.getFirstname()
			 <<emp.getLastname()
			 <<emp.getDepartmentId()
			 <<emp.getSalary()
			 <<datetime;

			return s;
		}


		friend ostream& operator<< (ostream& out,const Employee &emp) {
			out << "Id: " << emp.getId() << ", " 
				<< "Imie: " << emp.getFirstname() << ", " 
				<< "Nazwisko: " <<emp.getLastname() << ", "
				<< "Id dzialu: " <<emp.getDepartmentId() << ", "
				<< "Pensja: " <<emp.getSalary()  <<", " 
				<< "Data zatrudnienia: " << emp.getDate().getYear() << "-" << emp.getDate().getMonth() << "-" <<emp.getDate().getDay() <<endl;

			return out;
		}

	private:
			int id;
			string firstname;
			string lastname;
			int	departmentId;
			int salary;
			Date startDate;
};


/**
Klasa zapewniająca połączenie z bazą danych
*/
class EmployeeBusinessObject {

	public:

		EmployeeBusinessObject (const char* connString) {
			otl_connect::otl_initialize();  // inicjalizacja otl-a
			dbConn.rlogon(connString);      // ustanowienie połączenia zgodnie z podanym connString
			initDB();						// inicjalizacja bazy danych
		}

		~EmployeeBusinessObject() {
			dbConn.logoff();				// zamknięcie połączenia z bazą danych
		}

		// dodanie pracownika do relacji EmployeesTest
		void addEmployee(Employee *emp) {

			try {

				 otl_stream
					o(50, // wielkość buffora po której przekroczeniu nastąpi jego opróżnienie
					"insert into EmployeesTest values(:id<int>,:firstname<char[31]>,:lastname<char[31]>,:departmentId<int>,:salary<int>,:startDate<timestamp>)", 
					  dbConn  // obiekt połączenia
					 );

					//zapisanie danych pracownika do strumienia 
					o << *emp;
					// Alternatywna wersja kodu:	
					/*
					Date date = emp->getDate();
					otl_datetime datetime(date.getYear(),date.getMonth(),date.getDay(),0,0,0);  

					o<<emp->getId()
					 <<emp->getFirstname()
					 <<emp->getLastname()
					 <<emp->getDepartmentId()
					 <<emp->getSalary()
					 <<datetime;*/
					 
			} catch(otl_exception& p){ 
				cerr<<p.msg<<endl;		// wyświetla wiadomość o błędzie
				cerr<<p.stm_text<<endl; // wyświetla SQL, który spowodował błąd
				cerr<<p.sqlstate<<endl; // wyświetla wiadomość SQLSTATE 
				cerr<<p.var_info<<endl; // wyświetla zmienną, która spowodowała błąd
			} catch (std::exception &ex) { // obsługa wyjątków standardowych 
				cerr << "An std:: exception occured" <<endl; 
				cerr << ex.what() <<endl;
			} catch (...) {                // obsługa pozostałych wyjątkówz
				cerr << "Unknown error occured" <<endl;
			}

		}

		// dodanie pracowników zapisanych w kolekcji do relacji EmployeesTest
		void addEmployees(vector<Employee> &emps) {
			try {

				 otl_stream
					o(50, // rozmiar bufora
					"insert into EmployeesTest values(:id<int>,:firstname<char[31]>,:lastname<char[31]>,:departmentId<int>,:salary<int>,:startDate<timestamp>)", 
					  dbConn  // obiekt połączenia
					 );

				 // przekopiowanie zawartości kolekcji do strumienia o
				 // wykorzystywane są przy tym przeciążone operatory << klasy Employee
				 copy(emps.begin(), 
					 emps.end(), 
					 otl_output_iterator<Employee>(o)
				 );	 
					 
			} catch(otl_exception& p){ 
				cerr<<p.msg<<endl;		// wyświetla wiadomość o błędzie
				cerr<<p.stm_text<<endl; // wyświetla SQL, który spowodował błąd
				cerr<<p.sqlstate<<endl; // wyświetla wiadomość SQLSTATE 
				cerr<<p.var_info<<endl; // wyświetla zmienną, która spowodowała błąd
			} catch (std::exception &ex) { // obsługa wyjątków standardowych 
				cerr << "An std:: exception occured" <<endl; 
				cerr << ex.what() <<endl;
			} catch (...) {                // obsługa pozostałych wyjątkówz
				cerr << "Unknown error occured" <<endl;
			}

		}

		// uaktualnienie danych o podanym pracowniku
		void updateEmployee(Employee *emp) {

			try {

				otl_stream 
				   o(1, // rozmiar bufora
					 "UPDATE EmployeesTest "
					 "   SET firstname=:firstname<char[31]>,lastname=:lastname<char[31]>,departmentId=:departmentId<int>,salary=:salary<int>,startDate=:startDate<timestamp> "
					 " WHERE id=:id<int>", 
					 dbConn // obiekt połączenia
					);

					// odwzorowanie obiektu Date na otl_datetime
					Date date = emp->getDate();
					otl_datetime datetime(date.getYear(),date.getMonth(),date.getDay(),0,0,0);  

					// zapisanie danych do strumienia
				   o<<emp->getFirstname()
					<<emp->getLastname()
					<<emp->getDepartmentId()
					<<emp->getSalary()
					<<datetime
					<<emp->getId();
					

			} catch(otl_exception& p){ 
				cerr<<p.msg<<endl;		// wyświetla wiadomość o błędzie
				cerr<<p.stm_text<<endl; // wyświetla SQL, który spowodował błąd
				cerr<<p.sqlstate<<endl; // wyświetla wiadomość SQLSTATE 
				cerr<<p.var_info<<endl; // wyświetla zmienną, która spowodowała błąd
			} catch (std::exception &ex) { // obsługa wyjątków standardowych 
				cerr << "An std:: exception occured" <<endl; 
				cerr << ex.what() <<endl;
			} catch (...) {                // obsługa pozostałych wyjątkówz
				cerr << "Unknown error occured" <<endl;
			}

		}

		// usunięcie pracownika o podanym id z relacji EmployeesTest
		void removeEmployee(int id) {

			try {

				otl_stream 
				   o(1, // rozmiar bufora
					 " DELETE FROM EmployeesTest "
					 " WHERE id=:id<int>", 
					 dbConn // obiekt połączenia
					);

				// przekazanie parametru do sql-a
				o<<id;

			} catch(otl_exception& p){ 
				cerr<<p.msg<<endl;		// wyświetla wiadomość o błędzie
				cerr<<p.stm_text<<endl; // wyświetla SQL, który spowodował błąd
				cerr<<p.sqlstate<<endl; // wyświetla wiadomość SQLSTATE 
				cerr<<p.var_info<<endl; // wyświetla zmienną, która spowodowała błąd
			} catch (std::exception &ex) { // obsługa wyjątków standardowych 
				cerr << "An std:: exception occured" <<endl; 
				cerr << ex.what() <<endl;
			} catch (...) {                // obsługa pozostałych wyjątkówz
				cerr << "Unknown error occured" <<endl;
			}

		}

		// pobranie informacji nt. pracownika o podanym id z relacji EmployeesTest 
		Employee* getEmployee(const int id) {

			Employee *emp = NULL;

			try {

				 otl_stream i(50, // rozmiar bufora
							  "select firstname,lastname,departmentId,salary,startDate from EmployeesTest "
							  "where id=:id<int> ",
							  dbConn // obiekt połączenia
							 ); 
				 
				 char firstname[31],lastname[31];
				 int departmentId, salary;
				 otl_datetime date;
				 bool create = false;

				 // przekazanie parametru do sql-a
				 i<<id; 

				 // dopóki istnieją dane w strumieniu - powtarzaj
				 // ponieważ id pracowników są unikalne, pętla będzie miała zawsze 
				 // albo jeden albo zero obiegów
				 while(!i.eof()){ 
				  create = true;
				  i>>firstname;
				  i>>lastname;
				  i>>departmentId;
				  i>>salary;
				  i>>date;
				 }
				
				 // jeśli istniał pracownik o podanym id
				 if (create)
					 emp = new Employee(id,
										firstname,
										lastname,
										departmentId,
										salary,
										Date(date.year,date.month,date.month));

			} catch(otl_exception& p){ 
				cerr<<p.msg<<endl;		// wyświetla wiadomość o błędzie
				cerr<<p.stm_text<<endl; // wyświetla SQL, który spowodował błąd
				cerr<<p.sqlstate<<endl; // wyświetla wiadomość SQLSTATE 
				cerr<<p.var_info<<endl; // wyświetla zmienną, która spowodowała błąd
			} catch (std::exception &ex) { // obsługa wyjątków standardowych 
				cerr << "An std:: exception occured" <<endl; 
				cerr << ex.what() <<endl;
			} catch (...) {                // obsługa pozostałych wyjątkówz
				cerr << "Unknown error occured" <<endl;
			}

			return emp;
		}

		// pobranie informacji nt. pracowników z relacji EmployeesTest
		vector<Employee> getEmployees() {
			vector<Employee> v; // vector of rows
			
			try {		
				otl_stream i(50, //rozmiar bufora
							  "select * from EmployeesTest ",
							  dbConn // obiekt połączenia
							 ); 
				 

				// przekopiuj do wektora wszystkie dane wyjściowe zawarte w strumieniu
				// wykorzystywany jest tu przeciążony operator >> klasy Employee
				copy(otl_input_iterator<Employee,ptrdiff_t>(i), 
					  otl_input_iterator<Employee,ptrdiff_t>(),
					  back_inserter(v));    

			} catch(otl_exception& p){ 
				cerr<<p.msg<<endl;		// wyświetla wiadomość o błędzie
				cerr<<p.stm_text<<endl; // wyświetla SQL, który spowodował błąd
				cerr<<p.sqlstate<<endl; // wyświetla wiadomość SQLSTATE 
				cerr<<p.var_info<<endl; // wyświetla zmienną, która spowodowała błąd
			} catch (std::exception &ex) { // obsługa wyjątków standardowych 
				cerr << "An std:: exception occured" <<endl; 
				cerr << ex.what() <<endl;
			} catch (...) {                // obsługa pozostałych wyjątkówz
				cerr << "Unknown error occured" <<endl;
			}
			
			return v;
		}

		// sprawdza czy istnieje pracownik o zadanym id
		bool existEmployee(int id) {

			try {

				 otl_stream i(50, // rozmiar bufora
							  "select count(*) from EmployeesTest "
							  "where id=:id<int> ",
							  dbConn // obiekt połączenia
							 ); 

				 char count = 0;

				 // zapidanie parametrów do sql-a
				 i<<id; 
			
				 // dopóki są dane - powtarzaj
				 // pętla ta będzie miała tylko jeden obieg
				 while(!i.eof()){ 
				  i>>count;
				 }
				
				 // jeśli liczba pracownikó o zadnym id jest różna od 0
				 if (count != '0') return true;

			} catch(otl_exception& p){ 
				cerr<<p.msg<<endl;		// wyświetla wiadomość o błędzie
				cerr<<p.stm_text<<endl; // wyświetla SQL, który spowodował błąd
				cerr<<p.sqlstate<<endl; // wyświetla wiadomość SQLSTATE 
				cerr<<p.var_info<<endl; // wyświetla zmienną, która spowodowała błąd
			} catch (std::exception &ex) { // obsługa wyjątków standardowych 
				cerr << "An std:: exception occured" <<endl; 
				cerr << ex.what() <<endl;
			} catch (...) {                // obsługa pozostałych wyjątkówz
				cerr << "Unknown error occured" <<endl;
			}

			return false;
		}


	private:
		/**
		Inicjalizacja bazy danych.
		*/
		void initDB() {

			try {

			  // usunięcie relacji EmployeesTest
			  otl_cursor::direct_exec
			   (
				dbConn,
				"drop table EmployeesTest",
				otl_exception::disabled // wyłączenie wyjątków OTL
			   ); 

			  // stworzenie relacji EmployeesTest
			  otl_cursor::direct_exec
			   (
				dbConn,
				"create table EmployeesTest(id integer primary key autoincrement,"
				"firstname varchar(30),"
				"lastname varchar(30),"
				"departmentId int,"
				"salary int,"
				"startDate date)"
				); 

			} catch(otl_exception& p){ 
				cerr<<p.msg<<endl;		// wyświetla wiadomość o błędzie
				cerr<<p.stm_text<<endl; // wyświetla SQL, który spowodował błąd
				cerr<<p.sqlstate<<endl; // wyświetla wiadomość SQLSTATE 
				cerr<<p.var_info<<endl; // wyświetla zmienną, która spowodowała błąd
			} catch (std::exception &ex) { // obsługa wyjątków standardowych 
				cerr << "An std:: exception occured" <<endl; 
				cerr << ex.what() <<endl;
			} catch (...) {                // obsługa pozostałych wyjątkówz
				cerr << "Unknown error occured" <<endl;
			}

		}

	private:
		otl_connect dbConn; // obiekt połączenia

};

// interfejs obiektu dostarczającego informacji o pracownikach
virtual class Employees {

	public:

		virtual void add(Employee *emp) = 0;

		virtual void add(vector<Employee> &emps) = 0;

		virtual void remove(int id) = 0;

		virtual void update(Employee *emp) = 0;

		virtual Employee* get(int id) = 0;

		virtual vector<Employee> getAll() = 0;

		virtual bool exist(int id) = 0;

		friend ostream& operator<< (ostream &out, Employees &emps); 

};

// implementacja interfejsu Employees - z wykorzystaniem bazy danych
class EmployeesDB : public Employees {

	public:

		EmployeesDB() {
			// stwórz pomocniczy obiekt biznesowy
			empBO = new EmployeeBusinessObject(DEFAULT_CONNECTION);
		}

		// dodaj pracownika
		void add(Employee *emp) {
			empBO->addEmployee(emp);
		}

		// dodaj pracowników
		void add(vector<Employee> &emps) {
			empBO->addEmployees(emps);
		}

		// usuń pracownika o podanym id
		void remove(int id) {
			empBO->removeEmployee(id);
		}

		// uaktualnij podanego pracownika
		void update(Employee *emp) {
			empBO->updateEmployee(emp);
		}

		// zwróć informacje o pracowniku o podanym id
		Employee* get(int id) {
			return empBO->getEmployee(id);
		}

		// zwróć kolekcję zawierającą informacje o wszystkich pracownikach
		vector<Employee> getAll() {
			return empBO->getEmployees();
		}

		// sprawdź czy istnieje pracownik o podanym id
		bool exist (int id) {
			return empBO->existEmployee(id);
		}

		// przeciążenie strumienia << dla potrzeb np. serializacji
		friend ostream& operator<< (ostream &out, Employees &emps) {
			// pobierz kolekcje wszystkich pracowników
			vector<Employee> empsVec = emps.getAll();
			// przekopiuj pobraną kolekcję do strumienia out
			copy(empsVec.begin(), empsVec.end(), ostream_iterator<Employee>(out, "\n"));

			return out;
		}

	private:
	
		EmployeeBusinessObject *empBO;

};



int main () {

	cout << "Otl sqlite example" << endl << endl;
	
	cout << "Adding an employee" << endl;
	Employees *emps = new EmployeesDB(); // stworzenie obiektu EmployeesDB polączonego z relacją EmployeesTest
	Date date(2006,6,15);
	Employee *emp1 = new Employee(1, "Piotr", "Stepaniak", 1, 1250, date); // tworzenie nowego pracownika
	emps->add( emp1 ); // dodanie pracownika do bazy
	cout << *emps->get(1) <<endl;  // pobranie inf. o pracowniku z bazy

	cout << "Changing employees salary " << endl;
	emp1->setSalary(1500);
	emps->update(emp1); // zmiana informacji o danym pracowniku
	cout << *emps->get(1) <<endl;

	cout << "Adding 2 more employees" << endl;
	vector<Employee> vec;
	vec.push_back(*(new Employee(2, "Paweł", "Kozak", 1, 1250, date)));
	vec.push_back(*(new Employee(3, "Tomasz", "Ziss", 1, 1250, date)));
	emps->add(vec); // dodanie kolekcji nowych pracowników do bazy
	
	cout<< "Employees nmbr: " << (emps->getAll()).size()<<endl; // pobranie kolekcji z wszystkimi pracownikami zapisanymi w bazie
																// i wyświetlenie jej rozmiaru
	cout<< "Employees list: " << endl << endl;
	cout<<*emps; // wyświetlenie wszystkich pracowników zapisanych w bazie

	cout << "Checking if employee 2 is present" <<endl;
	if (emps->exist(2)) { // sprawdzenie czy istnieje w bazie pracownik o id = 2
		cout << "There is such an employee!" <<endl;
	} else 
		cout << "There is no such employee!" <<endl;

	cout << "Deleting employee 2" << endl;
	emps->remove(2); // usunięcie pracownika o id = 2

	cout << "Checking if employee 2 is present" <<endl;
	if (emps->exist(2)) { // sprawdzenie czy istnieje w bazie pracownik o id = 2
		cout << "There is such an employee!" <<endl;
	} else 
		cout << "There is no such employee!" <<endl;

	cout<< endl <<"Employees nmbr: " << (emps->getAll()).size()<<endl;
	cout<< "Employees list: " << endl << endl;
	cout<<*emps;

	cout << "Press a key .. " <<endl;
	char c;
	cin >> c;
	return 0;
}
