/*
 * PlatformSessionManager.cpp - implementation of PlatformSessionManager class
 *
 * Copyright (c) 2018-2024 Tobias Junghans <tobydox@veyon.io>
 *
 * This file is part of Veyon - https://veyon.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 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 GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 */

#include <QElapsedTimer>
#include <QLocalServer>
#include <QLocalSocket>
#include <QMutexLocker>

#include "PlatformSessionManager.h"
#include "VariantArrayMessage.h"
#include "VeyonConfiguration.h"


PlatformSessionManager::PlatformSessionManager( QObject* parent ) :
	QThread( parent ),
	m_mode( VeyonCore::config().multiSessionModeEnabled() ? Mode::Multi :
					   VeyonCore::config().activeSessionModeEnabled() ?
					   Mode::Active :
					   Mode::Local
				   ),
	m_maximumSessionCount( VeyonCore::config().maximumSessionCount() )
{
	vDebug();

	start();
}



PlatformSessionManager::~PlatformSessionManager()
{
	vDebug();

	quit();
	wait();
}



void PlatformSessionManager::run()
{
	if( mode() == Mode::Multi )
	{
		auto server = new QLocalServer;
		server->setSocketOptions( QLocalServer::WorldAccessOption );
		server->listen( serverName() );

		connect( server, &QLocalServer::newConnection, server, [this, server]() {
			auto connection = server->nextPendingConnection();
			connect( connection, &QLocalSocket::disconnected, connection, &QLocalSocket::deleteLater );

			m_mutex.lock();
			VariantArrayMessage(connection).write( m_sessions ).send();
			m_mutex.unlock();

			connection->flush();
			connection->disconnectFromServer();
		} );
	}

	QThread::run();
}

PlatformSessionManager::SessionId PlatformSessionManager::openSession( const PlatformSessionId& platformSessionId )
{
	QMutexLocker l( &m_mutex );

	const auto sessionIds = m_sessions.values();

	for( SessionId i = 0; i < m_maximumSessionCount; ++i )
	{
		if( sessionIds.contains( i ) == false )
		{
			vDebug() << "Opening session" << i << "for platform session" << platformSessionId;
			m_sessions[platformSessionId] = i;

			return i;
		}
	}

	vWarning() << "Failed to allocate session for platform session" << platformSessionId;

	return PlatformSessionFunctions::InvalidSessionId;
}



void PlatformSessionManager::closeSession( const PlatformSessionId& platformSessionId )
{
	QMutexLocker l( &m_mutex );

	const auto closedSessionId = m_sessions.take( platformSessionId ).toInt();

	vDebug() << "Closing session" << closedSessionId << "for platform session" << platformSessionId;
}



PlatformSessionManager::SessionId PlatformSessionManager::resolveSessionId( const PlatformSessionId& platformSessionId )
{
	if( VeyonCore::component() == VeyonCore::Component::Service )
	{
		return PlatformSessionFunctions::DefaultSessionId;
	}

	QLocalSocket socket;
	socket.connectToServer( serverName(), QLocalSocket::ReadOnly );
	if( socket.waitForConnected( ServerConnectTimeout ) == false )
	{
		if( VeyonCore::component() != VeyonCore::Component::CLI &&
			VeyonCore::component() != VeyonCore::Component::Configurator )
		{
			vCritical() << "could not read session map";
		}

		return PlatformSessionFunctions::InvalidSessionId;
	}

	if( waitForMessage( &socket ) )
	{
		VariantArrayMessage message( &socket );
		message.receive();
		return message.read().toMap().value( platformSessionId, PlatformSessionFunctions::InvalidSessionId ).toInt();
	}

	vCritical() << "could not receive session map";

	return PlatformSessionFunctions::InvalidSessionId;
}



bool PlatformSessionManager::waitForMessage(QLocalSocket* socket)
{
	// wait for acknowledge
	QElapsedTimer messageTimeoutTimer;
	messageTimeoutTimer.start();

	VariantArrayMessage inMessage( socket );
	while( messageTimeoutTimer.elapsed() < MessageReadTimeout &&
		   inMessage.isReadyForReceive() == false )
	{
		socket->waitForReadyRead( SocketWaitTimeout );
	}

	return inMessage.isReadyForReceive();
}
