/***********************************************************
Copyright (C) 2024 VeriSign, Inc.

This library 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 2.1 of the License, or (at your option) any later version.

This library 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
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

http://www.verisign.com/nds/naming/namestore/techdocs.html
***********************************************************/
package com.verisign.epp.transport.server;

import java.io.File;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.verisign.epp.transport.EPPConException;
import com.verisign.epp.transport.EPPServerCon;
import com.verisign.epp.transport.ServerEventHandler;
import com.verisign.epp.transport.client.EPPSSLConfig;
import com.verisign.epp.transport.client.EPPSSLContext;
import com.verisign.epp.transport.client.EPPSSLImpl;
import com.verisign.epp.util.EPPEnv;
import com.verisign.epp.util.EPPEnvException;

import tech.kwik.core.QuicConnection;
import tech.kwik.core.QuicStream;
import tech.kwik.core.log.NullLogger;
import tech.kwik.core.server.ApplicationProtocolConnection;
import tech.kwik.core.server.ApplicationProtocolConnectionFactory;
import tech.kwik.core.server.ServerConnectionConfig;
import tech.kwik.core.server.ServerConnector;

/**
 * SSL Server class. This class implements the {@code EPPServerCon} interface
 * and handles SSL communication with a SSL client.
 */
public class EPPQuicServer implements EPPServerCon {
	/** Category for logging */
	private static Logger cat = LoggerFactory.getLogger(EPPQuicServer.class);

	/** Server connector */
	private ServerConnector serverConnector = null;

	/** Is the server actively listening for new connections? */
	private boolean listening = false;

	/** The connection handler */
	private ServerEventHandler handler = null;

	/** Server port number to listen on */
	private int portNumber = 0;

	/**
	 * {@code EPPSSLContext} for setting up the server SSL.
	 */
	private EPPSSLContext sslContext;

	/**
	 * EoQ implementation of the {@code ApplicationProtocolConnection} that will
	 * implement the EoQ application protocol.
	 */
	private static class EoQProtocolConnection implements ApplicationProtocolConnection {
		private static Logger cat = LoggerFactory.getLogger(EoQProtocolConnection.class);

		/**
		 * Established Quic connection
		 */
		private QuicConnection quicConnection;

		/**
		 * {@code ServerEventHandler} to call for a new accepted client initiated
		 * stream
		 */
		private ServerEventHandler handler = null;

		/**
		 * List of accepted {@code QuicStream} instances for the connection
		 */
		private List<EPPQuicServerThread> quicStreamThreads = new ArrayList<EPPQuicServerThread>();

		/**
		 * Creates the EoQ Connection referencing the QuicConnection
		 * 
		 * @param aQuicConnection
		 *           Quic connection for the Quic stream
		 * @param aHandler
		 *           Handler for new accepted peer initiated streams.
		 */
		public EoQProtocolConnection(QuicConnection aQuicConnection, ServerEventHandler aHandler) {
			cat.debug("Connection created:" + aQuicConnection);
			this.quicConnection = aQuicConnection;
			this.handler = aHandler;
		}


		/**
		 * Accepts a peer initiated Quic stream.
		 * 
		 * @param aStream
		 *           Quic stream initiated by the client
		 */
		public void acceptPeerInitiatedStream(QuicStream aStream) {
			cat.debug(
			      "Stream accepted with connection:" + this.quicConnection + ", and stream id: " + aStream.getStreamId());
			
			// Start the EoQ server thread to process the stream
			EPPQuicServerThread theStreamThread = new EPPQuicServerThread(aStream, this.handler);
			theStreamThread.start();
			this.quicStreamThreads.add(theStreamThread);
		}
	}

	/**
	 * EoQ implementation of the {@code ApplicationProtocolConnectionFactory}
	 * that will be used to instantiate a {@code EoQProtocolConnection} for each
	 * client initiated connection to the server.
	 */
	private static class EoQProtocolConnectionFactory implements ApplicationProtocolConnectionFactory {
		/**
		 * EoQ connection handler, which processes all of the EoQ packets written on the EoQ connection
		 */
		private ServerEventHandler handler = null;

		/**
		 * {@code EoQProtocolConnectionFactory} that takes the
		 * {@link ServerEventHandler} as a parameter to get called for each
		 * accepted peer initiated stream.
		 * 
		 * @param aHandler
		 *           Handler for new accepted peer initiated streams.
		 */
		public EoQProtocolConnectionFactory(ServerEventHandler aHandler) {
			this.handler = aHandler;
		}

		/**
		 * Created the {@link EoQProtocolConnection} with the passed
		 * {@code QuicConnection}.
		 * 
		 * @param aProtocol
		 *           This should always be "eoq"
		 * @param aQuicConnection
		 *           Quic connection created
		 */
		public EoQProtocolConnection createConnection(String aProtocol, QuicConnection aQuicConnection) {
			return new EoQProtocolConnection(aQuicConnection, this.handler);
		}
	}

	/**
	 * Gets an SSL property list as a string for logging purposes. Examples of
	 * SSL property lists include supported protocols, enabled protocols,
	 * supported cipher suites, and enabled cipher suites.
	 * 
	 * @param aList
	 *           {@code Array} of {@code String}'s.
	 * @return Space delimited {@code String} representing the property list if
	 *         {@code aList} is not {@code null}; {@code null} otherwise
	 */
	protected String getSSLPropertyListString(String aList[]) {
		if (aList == null) {
			return null;
		}

		String theStr = "";
		for (int i = 0; i < aList.length; i++) {
			if (i > 0) {
				theStr += " ";
			}
			theStr += aList[i];
		}

		return theStr;
	}

	/**
	 * Creates an {@code EPPQuicServer} that initializes the SSL configuration
	 * and gets the port number to listen on. The server will listen on all
	 * interfaces.
	 *
	 * @throws EPPConException
	 *            Error initializing SSL server
	 */
	public EPPQuicServer() throws EPPConException {
		cat.debug("EPPQuicServer.EPPQuicServer(): entering Constructor");

		if (!EPPSSLImpl.isInitialized()) {
			cat.debug("EPPQuicServer: Initializing SSL with default EPPSSLImpl");
			EPPSSLImpl.initialize();
		}
		this.sslContext = EPPSSLImpl.getEPPSSLContext();
	}

	/**
	 * Starts the server by creating the SSL server socket and going into
	 * connection accept loop.
	 *
	 * @param aHandler
	 *           Connection handler
	 *
	 * @throws EPPConException
	 *            Error creating server socket
	 */
	public void RunServer(ServerEventHandler aHandler) throws EPPConException {
		cat.debug("EPPQuicServer.RunServer(): entering Method");

		this.handler = aHandler;
		loop();
		close();

		cat.debug("EPPQuicServer.RunServer(): Exiting Method");
	}

	/**
	 * Run the accept loop, where the server will continue listening while the
	 * listening flag is {@code true} as defined by the {@code getListening} and
	 * the {@code setListening(boolean)} methods. Inside the loop, the server
	 * will accept a client connection and spawn a new thread to handle the
	 * connection.
	 *
	 * @throws EPPConException
	 *            Any error with accepting or handling a client connection
	 */
	public void loop() throws EPPConException {
		cat.debug("EPPQuicServer.loop(): Entering Method");

		EPPSSLConfig theSslConfig = this.sslContext.getSslConfig();

		try {
			this.portNumber = EPPEnv.getServerPort();
		}
		catch (EPPEnvException ex) {
			cat.error("Connection Failed Due to : " + ex.getMessage(), ex);
			throw new EPPConException("Connection Failed Due to : " + ex.getMessage());
		}

		try {
			// Initialize the QUIC connector
			ServerConnector.Builder theBuilder = ServerConnector.builder();
			theBuilder.withPort(this.portNumber);

			// // Truststore
			// char[] theTrustStorePassPhrase = null;
			// if (theSslConfig.getTrustStorePassPhrase() != null) {
			// theTrustStorePassPhrase =
			// theSslConfig.getTrustStorePassPhrase().toCharArray();
			// }
			// theBuilder.customTrustStore(
			// KeyStore.getInstance(new File(theSslConfig.getTrustStoreFileName()),
			// theTrustStorePassPhrase));

			char[] theIdentityPassPhrase = null;
			if (theSslConfig.getIdentityPassPhrase() != null) {
				theIdentityPassPhrase = theSslConfig.getIdentityPassPhrase().toCharArray();
			}

			// @todo - Make certificate alias configurable
			theBuilder.withKeyStore(
			      KeyStore.getInstance(new File(theSslConfig.getIdentityFileName()), theIdentityPassPhrase), "eppsdk",
			      theSslConfig.getIdentityKeyPassPhraseCharArray());

			ServerConnectionConfig.Builder theServerConnectionConfigBuilder = ServerConnectionConfig.builder();
			
			// @todo - Make stream per connection configurable
			theServerConnectionConfigBuilder.maxOpenPeerInitiatedBidirectionalStreams(1);
			ServerConnectionConfig theServerConnectionConfig = theServerConnectionConfigBuilder.build();
			theBuilder.withConfiguration(theServerConnectionConfig);
			
			// Disable Kwik looger, since there is not very useful information
			theBuilder.withLogger(new NullLogger());
			
			this.serverConnector = theBuilder.build();

			// Register the "eoq" protocol handler
			this.serverConnector.registerApplicationProtocol("eoq", new EoQProtocolConnectionFactory(this.handler));

			this.listening = true;

			this.serverConnector.start();
		}
		catch (Exception ex) {
			ex.printStackTrace();
			cat.error("Exception creating QUIC connection/stream: " + ex);
			throw new EPPConException("Exception creating QUIC connection/stream");
		}

		cat.debug("EPPQuicServer.loop(): Exting Method");
	}

	/**
	 * Closing the server socket
	 *
	 * @throws EPPConException
	 *            Error closing the server socket
	 */
	public void close() throws EPPConException {
		cat.debug("EPPQuicServer.close(): Entering Method");

		// @todo - Fill this in

		cat.debug("EPPQuicServer.close(): Exting Method");
	}

	/**
	 * Is the server actively listening for connections?
	 * 
	 * @return Listening boolean property
	 */
	public boolean isListening() {
		return this.listening;
	}

	/**
	 * Sets the server listing property that can be used to stop the server from
	 * listening for new connections.
	 * 
	 * @param aListening
	 *           {@code false} to stop the server from listening to new
	 *           connections.
	 */
	public void setListening(boolean aListening) {
		this.listening = aListening;
	}
}
