#include "Server.h"

Server* g_server;


void Server::SIGINT_HANDLER(int sig){
	if (sig==SIGINT) cerr << "- SIGINT RECEIVED" << endl;
// 	while (g_server->nb_of_job_attributed>0){
// 		cerr << "\twaiting clients to finish." << endl;
// 		sleep(15);
// 	}
	g_server->is_running=false;
}


Server::Server(int p){

	struct sigaction new_sa;
	struct sigaction old_sa;
	new_sa.sa_handler = Server::SIGINT_HANDLER;
	sigemptyset(&new_sa.sa_mask);
	new_sa.sa_flags = 0;
	sigaction(SIGINT, &new_sa, &old_sa);
	g_server=this;

	this->DEBUG=true;
	this->is_running=true;
	this->port=p;

	this->chars_allowed="abcdefghijklmnopqrstuvwxyz1234567890"; /*ABCDEFGHIJKLMNOPQRSTUVWXYZ*/
	this->min_size=3;
	this->max_size=4;
	this->salt="5357429144a0c53437fa3e3.80793609";
	this->hash_to_hack="e2fc714c4727ee9395f324cd2e7f331fa"; // abcd
	this->password="";

	this->nb_of_job_attributed=0;
	this->nb_of_job_done=0;
	ostringstream oss;
	oss << FILE_DIRECTORY << hash_to_hack << "_" << min_size << "_" << max_size << "_" << chars_allowed.length();
	string ini=oss.str()+".ini";
	string db=oss.str()+".db";

	this->config=new ConfigIniParser(ini);
	int ret;
	if ((ret = db_create(&dbp, NULL, 0)) != 0) cerr << "db_create : " << db_strerror(ret) << endl;
	else if ((ret = dbp->open(dbp,NULL, db.c_str(), NULL, DB_BTREE, DB_CREATE, 0664)) != 0) {
		dbp->err(dbp, ret, "%s", db.c_str());
		exit(1);
	}

	this->computePossibilities();
	this->DIVISE_WORK();
	this->LOAD_INI();
	this->LOAD_DB();
	this->INIT();
}


void Server::DIVISE_WORK(void){
	cerr << "- DIVISE_WORK() : ";
	tab_job.clear();
	long double tmp=this->chars_allowed.length();
	for (nb_char=0; pow(tmp,nb_char)<NUMBER_OF_ITERATIONS;nb_char++);
	
	nb_char=max_size-nb_char;
	if (nb_char>3) nb_char=3;
	else if (nb_char<=0) nb_char=1;

	string str="";
	computePassBegin(str);
	cerr << "OK" << endl;
}



void Server::computePassBegin(string str){
	if (str.length()>nb_char) return;
	for (int idx=0;idx<chars_allowed.length();idx++){
		string tmp=str+chars_allowed[idx];
		computePassBegin(tmp);
	}
	if (str.length()==nb_char) {
		Job* j=new Job(str);
		tab_job.push_back(j);
	}
}



void Server::LOAD_INI(void){
	cerr << "- LOAD_INI() : ";
	config->open();
	if(!config->getSection(MAIN_SECTION)) config->addSection(MAIN_SECTION,config->end());

	if (config->getValeur(MAIN_SECTION,"port")=="ERROR") config->addValeur(MAIN_SECTION,"port",port);
	else config->getValeur(MAIN_SECTION,"port",this->port);
	if (config->getValeur(MAIN_SECTION,"min_size")=="ERROR") config->addValeur(MAIN_SECTION,"min_size",min_size);
	else config->getValeur(MAIN_SECTION,"min_size",min_size);
	if (config->getValeur(MAIN_SECTION,"max_size")=="ERROR") config->addValeur(MAIN_SECTION,"max_size",max_size);
	else config->getValeur(MAIN_SECTION,"max_size",max_size);
	if (config->getValeur(MAIN_SECTION,"chars_allowed")=="ERROR") config->addValeur(MAIN_SECTION,"chars_allowed",chars_allowed);
	else config->getValeur(MAIN_SECTION,"chars_allowed",chars_allowed);
	if (config->getValeur(MAIN_SECTION,"hash_to_hack")=="ERROR") config->addValeur(MAIN_SECTION,"hash_to_hack",hash_to_hack);
	else config->getValeur(MAIN_SECTION,"hash_to_hack",hash_to_hack);
	if (config->getValeur(MAIN_SECTION,"salt")=="ERROR") config->addValeur(MAIN_SECTION,"salt",salt);
	else config->getValeur(MAIN_SECTION,"salt",salt);
	if (config->getValeur(MAIN_SECTION,"password")=="ERROR") config->addValeur(MAIN_SECTION,"password",password);
	else config->getValeur(MAIN_SECTION,"password",password);
	if (password!="") this->is_running=false;

	cerr << "OK" << endl;
}

void Server::LOAD_DB(void){
	cerr << "- LOAD_DB() : " << endl;
	config->save();
	list<Job*>::iterator i = tab_job.begin();
	while(i != tab_job.end()){
		Job* j=*i;
		key.data = (void*)j->begin.c_str();
		key.size = sizeof(j->begin.c_str());
		data.data = (void*)JOB_TODO;
		data.size = sizeof(JOB_TODO);

		int ret;
		if ((ret = dbp->get(dbp, NULL, &key, &data, 0)) == 0){
			char tmp[256];
			sprintf(tmp,"%s",(char *)data.data);
			if (strcmp(tmp,JOB_TODO)==0){
				j->done=false;
			} else if (strcmp(tmp,JOB_DONE)==0) {
				j->done=true;
				nb_of_job_done++;
			}
		} else {
			if ((ret = dbp->put(dbp, NULL, &key, &data, 0))!= 0) dbp->err(dbp, ret, "DB->put");
		}
		i++;
	}

	cerr << "\tCONFIG FILE       =   " << this->config->m_fichier << endl;
	cerr << "\tPORT              =   " << this->port << endl;
	cerr << "\tMIN LENGTH        =   " << this->min_size << endl;
	cerr << "\tMAX LENGTH        =   " << this->max_size << endl;
	cerr << "\tALLOWED CHARS     =   " << this->chars_allowed << endl;
	cerr << "\tNB POSSIBILITIES  =   " << this->nb_possibilities << endl;
	cerr << "\tMD5 TO HACK       =   " << this->hash_to_hack << endl;
	cerr << "\tSALT              =   " << this->salt << endl;
	cerr << "\tTODO / DONE       =   " << this->nb_of_job_done << " / " << tab_job.size() << endl;
}


void Server::SAVE_INI(void){
	cerr << "- SAVE_INI() : ";
	config->setValeur(MAIN_SECTION,"port",port);
	config->setValeur(MAIN_SECTION,"min_size",min_size);
	config->setValeur(MAIN_SECTION,"max_size",max_size);
	config->setValeur(MAIN_SECTION,"chars_allowed",chars_allowed);
	config->setValeur(MAIN_SECTION,"hash_to_hack",hash_to_hack);
	config->setValeur(MAIN_SECTION,"salt",salt);
	config->save();
	cerr << "OK" << endl;
}


void Server::SAVE_DB(void){
	cerr << "- SAVE_DB() : ";
	list<Job*>::reverse_iterator i = tab_job.rbegin();
	while(i != tab_job.rend()){
		Job* j=*i;
		int ret;
		if ((ret = dbp->get(dbp, NULL, &key, &data, 0)) == 0){
			char tmp[256];
			sprintf(tmp,"%s",(char *)data.data);
			if (j->done) {
				key.data = (void*)j->begin.c_str();
				key.size = sizeof(j->begin.c_str());
				data.data = (void*)JOB_DONE;
				data.size = sizeof(JOB_DONE);
				if ((ret = dbp->put(dbp, NULL, &key, &data, 0)) != 0) dbp->err(dbp, ret, "DB->put");
			}
		} else dbp->err(dbp, ret, "DB->get");
		i++;
	}
	dbp->close(dbp, 0);
	cerr << "OK" << endl;
}



void Server::computePossibilities(void){
	this->nb_possibilities=0;
	long double tmp=this->chars_allowed.length();
	for (int idx=this->min_size;idx<=this->max_size;idx++){
		this->nb_possibilities+=pow(tmp,idx);
	}
}





void Server::INIT(void){
	cerr << "- INIT() : ";
	int idx=0;
	socket_server=new ServerSocket( this->port );
	cerr << "OK" << endl;
	while (((nb_of_job_done+this->nb_of_job_attributed)<tab_job.size() && this->is_running==true) || this->nb_of_job_attributed>0 ){
		cerr << "\n- WAITING CONNECT ( " << nb_of_job_done << " / " << tab_job.size() << " job(s) done )" << endl;
		ClientConnection* client_connection=new ClientConnection(this);
		try {
			socket_server->accept( client_connection->socket_client );
			client_connection->id=socket_server->Socket::host+":"+socket_server->Socket::port;
		} catch ( SocketException& e ){
			string input="";
			switch (errno){
				case 0:
					cout << "ERROR(" << errno << ") in socket : " << strerror(errno) << endl;
					break;
				case 4: 
					this->is_running=false;
					this->nb_of_job_attributed=0;
					break;
				default: 
					cout << "SocketException : ERROR(" << errno << ") in socket : " << strerror(errno) << endl;
					break;
			}
			continue;
		}
		this->INIT_CLIENT(client_connection);
	}
	socket_server->close();
	this->SAVE_INI();
	this->SAVE_DB();



	cerr << "\t########################################" << endl;
	if (password=="") cerr << "\t#     PASS NOT FOUND :'(" << endl;
	else cerr << "\t#     PASS FOUND  : " << password << endl;
	cerr << "\t########################################" << endl;
	cerr << endl;
}


void Server::INIT_CLIENT(ClientConnection* client_connection){
	if (client_connection->is_running==false) 
		pthread_t* join_to=client_connection->initThread();
}



Job* Server::AvailableJobs(void){
	time_t end;
	time(&end);
	list<Job*>::iterator i = tab_job.begin();
	while(i != tab_job.end()){
		Job* j=*i;
		if (j->allocated==true && j->done==false && difftime(end,j->start)>JOB_TIMEOUT*60) {
			j->allocated=false;
			cerr << "- RELEASED(" << j->begin << ", timeout < " << difftime(end,j->start) << "\")" << endl;
		}

		if (j->allocated==false && j->done==false) {
			pthread_mutex_lock( &mutex );
			j->allocated=true;
			time(&j->start);
			nb_of_job_attributed++;
			pthread_mutex_unlock( &mutex );
			cerr << "- ALLOCATED(" << j->begin << ")" << endl;
			return j;
		} else i++;
	}
	return NULL;
}


void Server::ReportJob(string b){
	list<Job*>::iterator i = tab_job.begin();
	while(i != tab_job.end()){
		Job* j=*i;
		if (j->begin==b) {
			j->done=true;
			j->allocated=false;
			nb_of_job_done++;
			nb_of_job_attributed--;
			break;
		}
		i++;
	}
}









int main(int argc, char* argv[]){
	Server* server=new Server(1337);
}

