// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the Andrey N. Sabelnikov nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER  BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// 


#pragma comment(lib, "Ws2_32.lib")

namespace epee
{
namespace net_utils
{
template<class TProtocol> 
cp_server_impl<TProtocol>::cp_server_impl():
									m_port(0), m_stop(false),
									m_worker_thread_counter(0), m_listen_socket(INVALID_SOCKET)
{
}
//-------------------------------------------------------------
template<class TProtocol>
cp_server_impl<TProtocol>::~cp_server_impl()
{
	deinit_server();
}
//-------------------------------------------------------------
template<class TProtocol> 
bool cp_server_impl<TProtocol>::init_server(int port_no)
{
	m_port = port_no;
	
	WSADATA wsad = {0};
	int err = ::WSAStartup(MAKEWORD(2,2), &wsad);
	if ( err != 0  || LOBYTE( wsad.wVersion ) != 2 || HIBYTE( wsad.wVersion ) != 2 )
	{
		LOG_ERROR("Could not find a usable WinSock DLL, err = " << err << " \"" << socket_errors::get_socket_error_text(err) <<"\"");
		return false;
	}
	
	m_initialized = true;

	m_listen_socket = ::WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
	if(INVALID_SOCKET == m_listen_socket)
	{
		err = ::WSAGetLastError();
		LOG_ERROR("Failed to create socket, err = " << err << " \"" << socket_errors::get_socket_error_text(err) <<"\"");
		return false;
	}


	int opt = 1;
	err = setsockopt (m_listen_socket, SOL_SOCKET,SO_REUSEADDR, reinterpret_cast<char*>(&opt), sizeof(int));
	if(SOCKET_ERROR == err )
	{
		err = ::WSAGetLastError();
		LOG_PRINT("Failed to setsockopt(SO_REUSEADDR), err = " << err << " \"" << socket_errors::get_socket_error_text(err) <<"\"", LOG_LEVEL_1);
		deinit_server();
		return false;
	}


	sockaddr_in adr = {0};
	adr.sin_family = AF_INET;
	adr.sin_addr.s_addr = htonl(INADDR_ANY);
	adr.sin_port = (u_short)htons(m_port);

	//binding
	err = bind(m_listen_socket, (const sockaddr*)&adr, sizeof(adr ));
	if(SOCKET_ERROR == err )
	{
		err = ::WSAGetLastError();
		LOG_PRINT("Failed to Bind, err = " << err << " \"" << socket_errors::get_socket_error_text(err) <<"\"", LOG_LEVEL_1);
		deinit_server();
		return false;
	}


	m_completion_port = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
	if(INVALID_HANDLE_VALUE == m_completion_port)
	{
		err = ::WSAGetLastError();
		LOG_PRINT("Failed to CreateIoCompletionPort, err = " << err << " \"" << socket_errors::get_socket_error_text(err) <<"\"", LOG_LEVEL_1);
		deinit_server();
		return false;
	}


	return true;
}
//-------------------------------------------------------------

//-------------------------------------------------------------
static int CALLBACK CPConditionFunc(
						   IN LPWSABUF lpCallerId,
						   IN LPWSABUF lpCallerData,
						   IN OUT LPQOS lpSQOS,
						   IN OUT LPQOS lpGQOS,
						   IN LPWSABUF lpCalleeId,
						   OUT LPWSABUF lpCalleeData,
						   OUT GROUP FAR *g,
						   IN DWORD_PTR dwCallbackData
						   )
{

	/*cp_server_impl* pthis = (cp_server_impl*)dwCallbackData;
	if(!pthis)
		return CF_REJECT;*/
	/*if(pthis->get_active_connections_num()>=FD_SETSIZE-1)
	{
		LOG_PRINT("Maximum connections count overfull.", LOG_LEVEL_2);
		return CF_REJECT;
	}*/
	
	return  CF_ACCEPT;
}
//-------------------------------------------------------------
template<class TProtocol>
size_t cp_server_impl<TProtocol>::get_active_connections_num()
{
	return m_connections.size();
}
//-------------------------------------------------------------
template<class TProtocol>
unsigned CALLBACK cp_server_impl<TProtocol>::worker_thread(void* param)
{
	if(!param)
		return 0;

	cp_server_impl<TProtocol>* pthis = (cp_server_impl<TProtocol>*)param;
	pthis->worker_thread_member();
	return 1;
}
//-------------------------------------------------------------
template<class TProtocol> 
bool cp_server_impl<TProtocol>::worker_thread_member()
{
	LOG_PRINT("Worker thread STARTED", LOG_LEVEL_1);
	bool stop_handling = false;
	while(!stop_handling)
	{
		PROFILE_FUNC("[worker_thread]Worker Loop");
		DWORD bytes_transfered = 0;
		connection<TProtocol>* pconnection = 0;
		io_data_base* pio_data = 0;
		
		{
			PROFILE_FUNC("[worker_thread]GetQueuedCompletionStatus");
			BOOL res = ::GetQueuedCompletionStatus (m_completion_port, &bytes_transfered ,	(PULONG_PTR)&pconnection, (LPOVERLAPPED *)&pio_data, INFINITE);
			if (res == 0)
			{
				// check return code for error
				int err = GetLastError();
				LOG_PRINT("GetQueuedCompletionStatus failed with error " << err << " " << log_space::get_win32_err_descr(err), LOG_LEVEL_1);

				if(pio_data)
					::InterlockedExchange(&pio_data->m_is_in_use, 0);


				continue;
			}
		}

		if(pio_data)
			::InterlockedExchange(&pio_data->m_is_in_use, 0);



		if(!bytes_transfered && !pconnection && !pio_data)
		{
			//signal to stop
			break;
		}
		if(!pconnection || !pio_data)
		{
			LOG_PRINT("BIG FAIL: pconnection or pio_data is empty: pconnection=" << pconnection << " pio_data=" << pio_data, LOG_LEVEL_0);
			break;
		}



		if(::InterlockedCompareExchange(&pconnection->m_connection_shutwoned, 0, 0))
		{
			LOG_ERROR("InterlockedCompareExchange(&pconnection->m_connection_shutwoned, 0, 0)");
			//DebugBreak();
		}

		if(pio_data->m_op_type == op_type_stop)
		{
			if(!pconnection)
			{
				LOG_ERROR("op_type=op_type_stop, but pconnection is empty!!!");
				continue;
			}
			shutdown_connection(pconnection);
			continue;//
		}
		else if(pio_data->m_op_type == op_type_send)
		{
			continue;
			//do nothing, just queuing request
		}else if(pio_data->m_op_type == op_type_recv)
		{
			PROFILE_FUNC("[worker_thread]m_tprotocol_handler.handle_recv");
			if(bytes_transfered)
			{
				bool res = pconnection->m_tprotocol_handler.handle_recv(pio_data->Buffer, bytes_transfered);
				if(!res)
					pconnection->query_shutdown();
			}
			else
			{
				pconnection->query_shutdown();
				continue;
			}

		}

		//preparing new request, 

		{
			PROFILE_FUNC("[worker_thread]RECV Request small loop");
			int res = 0;
			while(true)
			{
				LOG_PRINT("Prepearing data for WSARecv....", LOG_LEVEL_3);
				ZeroMemory(&pio_data->m_overlapped, sizeof(OVERLAPPED));
				pio_data->DataBuf.len = pio_data->TotalBuffBytes;
				pio_data->DataBuf.buf = pio_data->Buffer;
				pio_data->m_op_type = op_type_recv;
				//calling WSARecv() and go to completion waiting
				DWORD bytes_recvd = 0;
				DWORD flags = 0;

				LOG_PRINT("Calling WSARecv....", LOG_LEVEL_3);
				::InterlockedExchange(&pio_data->m_is_in_use, 1);
				res = WSARecv(pconnection->m_sock, &(pio_data->DataBuf), 1, &bytes_recvd , &flags, &(pio_data->m_overlapped), NULL);
				if(res == SOCKET_ERROR )
				{
					int err = ::WSAGetLastError();
					if(WSA_IO_PENDING == err )
					{//go pending, ok
						LOG_PRINT("WSARecv return WSA_IO_PENDING", LOG_LEVEL_3);
						break;
					}
					LOG_ERROR("BIG FAIL: WSARecv error code not correct, res=" << res << " last_err=" << err);
					::InterlockedExchange(&pio_data->m_is_in_use, 0);
					pconnection->query_shutdown();
					break;
				}
				break;
				/*else if(0 == res)
				{
					if(!bytes_recvd)
					{
						::InterlockedExchange(&pio_data->m_is_in_use, 0);
						LOG_PRINT("WSARecv return 0, bytes_recvd=0, graceful close.", LOG_LEVEL_3);
						int err = ::WSAGetLastError();
						//LOG_ERROR("BIG FAIL: WSARecv error code not correct, res=" << res << " last_err=" << err);
						//pconnection->query_shutdown();
						break;
					}else
					{
						LOG_PRINT("WSARecv return immediatily 0, bytes_recvd=" << bytes_recvd, LOG_LEVEL_3);
						//pconnection->m_tprotocol_handler.handle_recv(pio_data->Buffer, bytes_recvd);
					}
				}*/
			}
		}
	}

	
	LOG_PRINT("Worker thread STOPED", LOG_LEVEL_1);
	::InterlockedDecrement(&m_worker_thread_counter);
	return true;
}
//-------------------------------------------------------------
template<class TProtocol> 
bool cp_server_impl<TProtocol>::shutdown_connection(connection<TProtocol>* pconn)
{
	PROFILE_FUNC("[shutdown_connection]");
	
	if(!pconn)
	{
		LOG_ERROR("Attempt to remove null pptr connection!");
		return false;
	}
	else 
	{
		LOG_PRINT("Shutting down connection ("<< pconn << ")", LOG_LEVEL_3);
	}
	m_connections_lock.lock();
	connections_container::iterator it = m_connections.find(pconn->m_sock);
	m_connections_lock.unlock();
	if(it == m_connections.end())
	{
		LOG_ERROR("Failed to find closing socket=" << pconn->m_sock);
		return false;
	}
	SOCKET sock = it->second->m_sock;
	{
		PROFILE_FUNC("[shutdown_connection] shutdown, close");
		::shutdown(it->second->m_sock,  SD_SEND );
	}
	size_t close_sock_wait_count = 0;
	{
		LOG_PRINT("Entered to 'in_use wait zone'", LOG_LEVEL_3);
		PROFILE_FUNC("[shutdown_connection] wait for in_use");
		while(::InterlockedCompareExchange(&it->second->m_precv_data->m_is_in_use, 1, 1))
		{

			Sleep(100);
			close_sock_wait_count++;
		}
		LOG_PRINT("First step to 'in_use wait zone'", LOG_LEVEL_3);


		while(::InterlockedCompareExchange(&it->second->m_psend_data->m_is_in_use, 1, 1))
		{
			Sleep(100);
			close_sock_wait_count++;
		}
		LOG_PRINT("Leaved 'in_use wait zone'", LOG_LEVEL_3);
	}

	::closesocket(it->second->m_sock);

	::InterlockedExchange(&it->second->m_connection_shutwoned, 1);
	m_connections_lock.lock();
	m_connections.erase(it);
	m_connections_lock.unlock();
	LOG_PRINT("Socked " << sock << " closed, wait_count=" << close_sock_wait_count, LOG_LEVEL_2);
	return true;
}
//-------------------------------------------------------------
template<class TProtocol> 
bool cp_server_impl<TProtocol>::run_server(int threads_count = 0)
{
	int err = listen(m_listen_socket, 100);
	if(SOCKET_ERROR == err )
	{
		err = ::WSAGetLastError();
		LOG_ERROR("Failed to listen, err = " << err << " \"" << socket_errors::get_socket_error_text(err) <<"\"");
		return false;
	}

	if(!threads_count)
	{
		SYSTEM_INFO si = {0};
		::GetSystemInfo(&si);
		threads_count = si.dwNumberOfProcessors + 2;
	}
	for(int i = 0; i != threads_count; i++)
	{
		boost::thread(boost::bind(&cp_server_impl::worker_thread_member, this));
		//HANDLE h_thread = threads_helper::create_thread(worker_thread, this);
		InterlockedIncrement(&m_worker_thread_counter);
		//::CloseHandle(h_thread);
	}

	LOG_PRINT("Numbers of worker threads started: " << threads_count, LOG_LEVEL_1);

	m_stop = false;
	while(!m_stop)
	{
		PROFILE_FUNC("[run_server] main_loop");
		TIMEVAL tv = {0};
		tv.tv_sec = 0;
		tv.tv_usec = 100;
		fd_set sock_set;
		sock_set.fd_count = 1;
		sock_set.fd_array[0] = m_listen_socket;
		int select_res = 0;
		{
			PROFILE_FUNC("[run_server] select");
			select_res = select(0, &sock_set, &sock_set, NULL, &tv);
		}
		
		if(SOCKET_ERROR == select_res)
		{
			err = ::WSAGetLastError();
			LOG_ERROR("Failed to select, err = " << err << " \"" << socket_errors::get_socket_error_text(err) <<"\"");
			return false;
		}
		if(!select_res)
		{
			on_net_idle();
			continue;
		}
		else
		{
			sockaddr_in adr_from = {0};
			int adr_len = sizeof(adr_from);
			SOCKET new_sock = INVALID_SOCKET;
			{
				PROFILE_FUNC("[run_server] WSAAccept");
				new_sock = ::WSAAccept(m_listen_socket, (sockaddr *)&adr_from, &adr_len, CPConditionFunc, (DWORD_PTR)this);
			}
			
			if(INVALID_SOCKET == new_sock)
			{
				if(m_stop)
					break;
				int err = ::WSAGetLastError();
				LOG_PRINT("Failed to WSAAccept, err = " << err << " \"" << socket_errors::get_socket_error_text(err) <<"\"", LOG_LEVEL_2);
				continue;
			}
			LOG_PRINT("Accepted connection (new socket=" << new_sock << ")", LOG_LEVEL_2);
			{
				PROFILE_FUNC("[run_server] Add new connection");
				add_new_connection(new_sock, adr_from.sin_addr.s_addr, adr_from.sin_port);
			}
			
		}

	}
	LOG_PRINT("Closing connections("<< m_connections.size() << ") and waiting...", LOG_LEVEL_2);
	m_connections_lock.lock();
	for(connections_container::iterator it = m_connections.begin(); it != m_connections.end(); it++)
	{
		::shutdown(it->second->m_sock, SD_BOTH);
		::closesocket(it->second->m_sock);
	}
	m_connections_lock.unlock();
	size_t wait_count = 0;
	while(m_connections.size() && wait_count < 100)
	{
		::Sleep(100);
		wait_count++;
	}
	LOG_PRINT("Connections closed OK (wait_count=" << wait_count << ")", LOG_LEVEL_2);


	LOG_PRINT("Stopping worker threads("<< m_worker_thread_counter << ").", LOG_LEVEL_2);
	for(int i = 0; i<m_worker_thread_counter; i++)
	{
		::PostQueuedCompletionStatus(m_completion_port, 0, 0, 0);
	}

	wait_count = 0;
	while(InterlockedCompareExchange(&m_worker_thread_counter, 0, 0) && wait_count < 100)
	{
		Sleep(100);
		wait_count++;
	}

	LOG_PRINT("Net Server STOPPED, wait_count = " << wait_count, LOG_LEVEL_1);
	return true;
}
//-------------------------------------------------------------
template<class TProtocol>
bool cp_server_impl<TProtocol>::add_new_connection(SOCKET new_sock, long ip_from, int port_from)
{
	PROFILE_FUNC("[add_new_connection]");
	
	LOG_PRINT("Add new connection zone: entering lock", LOG_LEVEL_3);
	m_connections_lock.lock();

	boost::shared_ptr<connection<TProtocol> > ptr;
	ptr.reset(new connection<TProtocol>(m_config));

	connection<TProtocol>& conn = *ptr.get();
	m_connections[new_sock] = ptr;
	LOG_PRINT("Add new connection zone: leaving lock", LOG_LEVEL_3);
	m_connections_lock.unlock();
	conn.init_buffers();
	conn.m_sock = new_sock;
	conn.context.m_remote_ip = ip_from;
	conn.context.m_remote_port = port_from;
	conn.m_completion_port = m_completion_port;
	{
		PROFILE_FUNC("[add_new_connection] CreateIoCompletionPort");
		::CreateIoCompletionPort((HANDLE)new_sock, m_completion_port, (ULONG_PTR)&conn, 0);
	}

	//if(NULL == ::CreateIoCompletionPort((HANDLE)new_sock, m_completion_port, (ULONG_PTR)&conn, 0))
	//{
    //	int err = ::GetLastError();
	//	LOG_PRINT("Failed to CreateIoCompletionPort(associate socket and completion port), err = " << err << " \"" << socket_errors::get_socket_error_text(err) <<"\"", LOG_LEVEL_2);
	//	return false;
	//}

	conn.m_tprotocol_handler.after_init_connection();
	{
		PROFILE_FUNC("[add_new_connection] starting loop");
		int res = 0;
		while(true)//res!=SOCKET_ERROR)
		{
			PROFILE_FUNC("[add_new_connection] in loop time");
			conn.m_precv_data->TotalBuffBytes = LEVIN_DEFAULT_DATA_BUFF_SIZE;
			ZeroMemory(&conn.m_precv_data->m_overlapped, sizeof(OVERLAPPED));
			conn.m_precv_data->DataBuf.len = conn.m_precv_data->TotalBuffBytes;
			conn.m_precv_data->DataBuf.buf = conn.m_precv_data->Buffer;
			conn.m_precv_data->m_op_type = op_type_recv;
			InterlockedExchange(&conn.m_precv_data->m_is_in_use, 1);
			DWORD bytes_recvd = 0;
			DWORD flags = 0;

			::InterlockedExchange(&conn.m_precv_data->m_is_in_use, 1);
			{
				PROFILE_FUNC("[add_new_connection] ::WSARecv");
				res = ::WSARecv(conn.m_sock, &(conn.m_precv_data->DataBuf), 1, &bytes_recvd , &flags, &(conn.m_precv_data->m_overlapped), NULL);
			}
			if(res == SOCKET_ERROR )
			{
				int err = ::WSAGetLastError();
				if(WSA_IO_PENDING == err )
				{
					break;
				}
				LOG_ERROR("BIG FAIL: WSARecv error code not correct, res=" << res << " last_err=" << err << " " << log_space::get_win32_err_descr(err));
				::InterlockedExchange(&conn.m_precv_data->m_is_in_use, 0);
				conn.query_shutdown();
				//shutdown_connection(&conn);
				break;
			}
			

			break;
			/*else if(0 == res)
			{
				if(!bytes_recvd)
				{
					PROFILE_FUNC("[add_new_connection] shutdown_connection");
					::InterlockedExchange(&conn.m_precv_data->m_is_in_use, 0);
					conn.query_shutdown();
					//shutdown_connection(&conn);
					break;
				}else
				{
					PROFILE_FUNC("[add_new_connection] handle_recv");
				}
			}*/
		}
	}



	return true;
}
//-------------------------------------------------------------
template<class TProtocol>
bool cp_server_impl<TProtocol>::deinit_server()
{
	if(!m_initialized)
		return true;

	if(INVALID_SOCKET != m_listen_socket)
	{
		shutdown(m_listen_socket, SD_BOTH);
		int res = closesocket(m_listen_socket);
		if(SOCKET_ERROR == res)
		{
			int err = ::WSAGetLastError();
			LOG_ERROR("Failed to closesocket(), err = " << err << " \"" << socket_errors::get_socket_error_text(err) <<"\"");
		}
		m_listen_socket = INVALID_SOCKET;
	}

	int res = ::WSACleanup();
	if(SOCKET_ERROR == res)
	{
		int err = ::WSAGetLastError();
		LOG_ERROR("Failed to WSACleanup(), err = " << err << " \"" << socket_errors::get_socket_error_text(err) <<"\"");
	}
	m_initialized = false;

	return true;
}

//-------------------------------------------------------------
template<class TProtocol>
bool cp_server_impl<TProtocol>::send_stop_signal()
{
	::InterlockedExchange(&m_stop, 1);
	return true;
}
//-------------------------------------------------------------
template<class TProtocol>
bool cp_server_impl<TProtocol>::is_stop_signal()
{
	return m_stop?true:false;	
}
//-------------------------------------------------------------
}
}
