/*
    Copyright (C) 2012 Bartosz Broda, Jacek Bochenek, Łukasz Biłeńkij, 
    Damian Jaworski, Wojciech Mazur, Dominik Piasecki, Maciej Piasecki, 
    Radosław Ramocki
    
    This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option)
any later version.

    This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. 

    See the LICENSE and COPYING files for more details.
*/

#include "kbestserver.h"
#include <boost/asio.hpp>
#include <boost/filesystem.hpp>

#include <boost/foreach.hpp>
#define foreach BOOST_FOREACH

namespace kbestd{

KBestServer::KBestServer(const options &opts)
	: opts_(opts)
{
	processing_id_ = -1;
}

KBestServer::~KBestServer()
{
	CleanUp();
}
bool KBestServer::CleanUp()
{
	if(opts_.print_text)
		std::cout << "Cleaning up...\n";

	Connect(); // try to connect
	std::stringstream idss, ss;
	idss << opts_.ip << ":" << opts_.port_no;
	std::string idss_str = idss.str();
	ss << "DELETE from kbestdaemons WHERE id ='" << idss_str << "';";
	mysqlpp::Query query = connection_.query();
	mysqlpp::SimpleResult result = query.execute(ss.str());

	if(!result)
	{
		std::cerr << "Cannot remove this instance of takipid from database: " << idss_str << std::endl;
		std::cerr << "Problem: " << query.error() << std::endl;

		return false;
	}

	if(processing_id_ > 0){
		if(opts_.print_text)
			std::cout << "Trying to reject last word processed" << std::endl;
		std::stringstream ss;
		ss << "UPDATE kbest_requests_words as krw SET krw.state = 'REJECTED', "
			  << "system_comment=\"unknown error\" "
			  << "WHERE request_word_id = " << processing_id_
			  << ";";
		mysqlpp::Query query2 = connection_.query(ss.str());
		query2.execute();
	}

	if(opts_.print_text)
		std::cout << "Cleaning up... successful" << std::endl;



	return true;

}
void KBestServer::Connect()
{
	if(! connection_.connected() || (! connection_.ping() ) )
	{
		if(opts_.print_text)
			std::cout << "--------- Connecting ---------" << std::endl;
		if(! connection_.connect(opts_.mysql_db.c_str(),
								 opts_.mysql_host.c_str(),
								 opts_.mysql_user.c_str(),
								 opts_.mysql_pass.c_str(),
								 opts_.mysql_port) )
		{
			std::cerr << "Cannot connect to db" << std::endl;
			std::cerr << opts_.mysql_db << " " << opts_.mysql_port
					  << " " << opts_.mysql_host << " "
					  << opts_.mysql_user << " "
					  << opts_.mysql_pass << "\n";
			exit(-1);
		}
		if(opts_.print_text)
			std::cout << "Conecttion successful.\n";
	}
}

void KBestServer::Run()
{
	if(opts_.print_text)
		std::cout << "Starting main event loop..." << std::endl;
	// add kbestd to db (working state), check if it wasn't added before
	UpdateMyStatus(false);
	while(true)
	{
		// process requests
		ProcessRequests();
		// listen for processing
		Listen();
	}
}
void KBestServer::ProcessRequests()
{
	UpdateMyStatus(false);
	while(true)	{
		if (opts_.print_text)
			std::cout << "Processing requests..." << std::endl;

		// LOCK
		mysqlpp::Query query = connection_.query();
		mysqlpp::SimpleResult result = query.execute("LOCK TABLES kbest_requests_words AS krw WRITE, kbest_requests AS kr WRITE, matrices WRITE, kbestdaemons WRITE;");
		bool no_work = false;
		if(result){
			if (opts_.print_text)
				std::cout << "Locked kbest_requests_words..." << std::endl;


			request rq = SearchForWork();

			if( rq.request_word_id >= 0 ){// if there is: change status of the first request to processing


				std::stringstream ss;
				ss << "UPDATE kbest_requests_words as krw SET krw.state = 'PROCESSING' "
					  << "WHERE request_word_id = " << rq.request_word_id
					  << ";";
				mysqlpp::Query query = connection_.query(ss.str());
				query.execute();

			}
			else
				no_work = true;

			result = query.execute("UNLOCK TABLES;");
			if (opts_.print_text)
			{
				if(result)
					std::cout << "Unlocked..." << std::endl;
				else
					std::cout << "Problem with unlocking db: " << result.info() << std::endl;
			}


			if(no_work)
			{
				if (opts_.print_text)
					std::cout << "Nothing to process" << std::endl;
				return;
			}
			else{
				if (opts_.print_text)
					std::cout << "Will process id: "
							  << rq.request_word_id << std::endl
							  << "Will process " << rq.matrix_path
							  << " word: " << rq.word << " msr: "
							  << rq.msr << std::endl;

				try{
					processing_id_ = rq.request_word_id;
					KBestCounter::kbestvec results = counter_.generate_kbest(rq.matrix_path, rq.msr, rq.word, rq.k);
					std::stringstream resstr;
					BOOST_FOREACH (KBestCounter::kbestvec::value_type &t, results)
						resstr << t.first << "; " << t.second << ";";

					if(opts_.print_text)
						std::cout << "Will insert into db: " << resstr.str() << std::endl;
					std::stringstream q;
					q <<"INSERT INTO kbest_results VALUES(" << rq.request_word_id
						 << ", '" << resstr.str() << "');";
					mysqlpp::Query query = connection_.query(q.str());
					query.execute();

					processing_id_ = -1;

					std::stringstream ss;
					ss << "UPDATE kbest_requests_words as krw SET krw.state = 'READY' "
						  << "WHERE request_word_id = " << rq.request_word_id
						  << ";";
					mysqlpp::Query query2 = connection_.query(ss.str());
					query2.execute();
					processing_id_ = -1;


				}catch(KBestCounterError &e){
					std::cerr << e.what() << std::endl;
					std::stringstream ss;
					ss << "UPDATE kbest_requests_words as krw SET krw.state = 'REJECTED', "
						  << "system_comment= \"" << e.what() << "\" "
						  << "WHERE request_word_id = " << rq.request_word_id
						  << ";";
					mysqlpp::Query query2 = connection_.query(ss.str());
					query2.execute();
					processing_id_ = -1;
				}catch(...){
					std::cerr << "Unknown error" << std::endl;
					std::stringstream ss;
					ss << "UPDATE kbest_requests_words as krw SET krw.state = 'REJECTED', "
						  << "system_comment=\"unknown error\" "
						  << "WHERE request_word_id = " << rq.request_word_id
						  << ";";
					mysqlpp::Query query2 = connection_.query(ss.str());
					query2.execute();
					processing_id_ = -1;
				}


			}

		}else{
			std::cerr << "Could not lock kbest_requests_words table. "
					  << result.info() << std::endl;
		}
	}
}

request KBestServer::SearchForWork()
{
	request rq;
	std::string q = "SELECT request_word_id, matrix_id, word, msr,k FROM kbest_requests_words krw JOIN kbest_requests kr ON krw.request_id = kr.request_id  WHERE state = 'QUEUED' ORDER BY request_word_id LIMIT 1;";
	mysqlpp::Query query = connection_.query(q);

	if (mysqlpp::StoreQueryResult res = query.store()){
		if(res.num_rows() > 0){
			rq.request_word_id = boost::lexical_cast<long> (res[0]["request_word_id"]);
			rq.k = boost::lexical_cast<int> (res[0]["k"]);
			rq.word = std::string(res[0]["word"]);
			rq.msr = std::string(res[0]["msr"]);
			long matrix_id = boost::lexical_cast<long> (res[0]["matrix_id"]);
			rq.matrix_path = determine_path(matrix_id);
			return rq;

		}
	}

	return rq;
}

std::string KBestServer::determine_path(long matrix_id)
{
	std::stringstream q;
	q << "SELECT user_id, name FROM matrices WHERE matrix_id = "
		 << matrix_id << " LIMIT 1;";


	mysqlpp::Query query = connection_.query(q.str());
	if (mysqlpp::StoreQueryResult res = query.store()){
		if(res.num_rows() > 0){
			std::string matrix_name = std::string(res[0]["name"]);
			std::string user_id= std::string(res[0]["user_id"]);
			boost::filesystem::path p(opts_.matrix_dir);
			p /= user_id;
			p /= matrix_name;
			return p.string();
		}
	}

	return std::string();

}

void KBestServer::Listen()
{
	// set this kbestd db state  to ready
	UpdateMyStatus(true);
	using namespace std;
	using boost::asio::ip::tcp;
	if (opts_.print_text)
		cout << "Starting kbestd server. Listening on port "
			 << opts_.port_no << "." << endl;

	boost::asio::io_service io_service;

	tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(),
													 opts_.port_no));
	for (;;)
	{
		string message;
		boost::array<char, 4096> buf;
		boost::system::error_code error;

		tcp::socket socket(io_service);
		acceptor.accept(socket);

		size_t len = socket.read_some(boost::asio::buffer(buf), error);
		if (len == 0)
			continue;

		message = buf.data();
		if (opts_.print_text)
			std::cout << "Got message by tcp/ip: " << message << std::endl;

		std::string accept_and_queue = "2";
		boost::asio::write(socket, boost::asio::buffer(accept_and_queue),
						   boost::asio::transfer_all(), error);
		socket.close();
		Connect();
		return;
	}

}

void KBestServer::UpdateMyStatus(bool ready)
{
	Connect();
	std::stringstream idss;
	idss << opts_.ip << ":" << opts_.port_no;
	mysqlpp::Query query = connection_.query();
	// lock write
	mysqlpp::SimpleResult result =
			query.execute("LOCK TABLES kbestdaemons WRITE;");
	if(result)
	{
		// check if I need to update my info
		std::stringstream ss;
		ss << "SELECT * FROM kbestdaemons WHERE id = '" << idss.str() << "';" ;
		mysqlpp::StoreQueryResult res = query.store(ss.str());
		if(res.num_rows() != 0)
		{
			// if update only: do it now!
			std::stringstream ss;
			ss << "UPDATE kbestdaemons SET ready = '" << (ready?"1":"0") << "' WHERE id ='" << idss.str() << "';";
			mysqlpp::SimpleResult result = query.execute(ss.str());
			if(!result)
			{
				std::cerr << "Cannot update kbestdaemons state!" << std::endl;
				result = query.execute("UNLOCK TABLES;");
				std::stringstream ss;
				ss << "DELETE from kbestdaemons WHERE id ='" << idss.str() << "';";
				result = query.execute(ss.str());
				exit(-1);
			}
		}
		else
		{
			// else : insert
			std::stringstream ss;
			ss << "INSERT INTO kbestdaemons() VALUES('" << idss.str() << "', '" << (ready?"1":"0") << "');";
			mysqlpp::SimpleResult result = query.execute(ss.str());
			if(!result)
			{
				std::cerr << "Cannot add kbest daemon to a table!"
						  << std::endl;
				result = query.execute("UNLOCK TABLES;");
				std::stringstream ss;
				ss << "DELETE from kbestdaemons WHERE id ='"
				   << idss.str() << "';";
				result = query.execute(ss.str());
				exit(-1);
			}
		}
	}
	else
	{
		std::cerr << "Cannot lock table kbestdaemons. " << result.info() << std::endl;
		std::stringstream ss;
		ss << "DELETE from kbestdaemons WHERE id ='"
		   << idss.str() << "';";
		result = query.execute(ss.str());
		exit(-1);
	}
	result = query.execute("UNLOCK TABLES;");
}
} // ns kbestd
