/***********************************************************
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.serverstub;

import java.io.IOException;
import java.io.OutputStream;

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

import com.verisign.epp.codec.gen.EPPDcp;
import com.verisign.epp.codec.gen.EPPGreeting;
import com.verisign.epp.codec.gen.EPPPurpose;
import com.verisign.epp.codec.gen.EPPRecipient;
import com.verisign.epp.codec.gen.EPPResponse;
import com.verisign.epp.codec.gen.EPPResult;
import com.verisign.epp.codec.gen.EPPStatement;
import com.verisign.epp.codec.gen.EPPTransId;
import com.verisign.epp.framework.EPPAssemblerException;
import com.verisign.epp.framework.EPPByteArrayDispatcher;
import com.verisign.epp.framework.EPPDispatcher;
import com.verisign.epp.framework.EPPEventException;
import com.verisign.epp.framework.EPPEventResponse;
import com.verisign.epp.framework.HttpEPPXMLAssembler;
import com.verisign.epp.util.EPPEnv;

import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

/**
 * EPP Server Stub HTTP Controller Servlet. This Servlet, will route EPP
 * commands to the appropriate handlers and return the result to the client.
 */
public class ControllerServlet extends HttpServlet {

	private static Logger cat = LoggerFactory.getLogger(ControllerServlet.class);

	/**
	 * Constructor.
	 *
	 * @param config
	 *           Servlet configuration
	 *
	 * @throws ServletException
	 *            Error initializing the servlet
	 */
	@Override
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
	}

	/**
	 * Process the HTTP POST request
	 *
	 * @param aRequest
	 *           HTTP request
	 * @param aResponse
	 *           HTTP response
	 *
	 * @throws ServletException
	 *            Exception processing the HTTP request
	 * @throws IOException
	 *            Exception reading and writing from/to the streams
	 */
	@Override
	public void doPost(HttpServletRequest aRequest, HttpServletResponse aResponse) throws ServletException, IOException {
		cat.debug("doPost: <<< Received Request >>");

		ServletInputStream theInputStream = null;
		ServletOutputStream theOutputStream = null;

		try {
			theInputStream = aRequest.getInputStream();
			theOutputStream = aResponse.getOutputStream();
		}
		catch (IOException e) {
			cat.error("doPost: Exception getting input and output streams for client request");
			e.printStackTrace();
			return;
		}

		// Get the HTTP session
		HttpSession theHttpSession = aRequest.getSession(false);
		if (theHttpSession == null) {
			sendErrorResponse(EPPResult.COMMAND_USE_ERROR, "Command use error", theOutputStream,
			      new SessionData());
			cat.error("doPost: No HTTP session established on POST");
			return;
		}

		SessionData theSessionData;

		synchronized (theHttpSession) {
			theSessionData = (SessionData) theHttpSession.getAttribute("sessionData");

			if (theSessionData == null) {
				cat.debug("doPost: No session context exists, creating session context");
				theSessionData = new SessionData();
				theHttpSession.setAttribute("sessionData", theSessionData);
			}
		}

		cat.debug("Processing command");
		byte[] theRequestPacket = theInputStream.readAllBytes();
		byte[] theResponsePacket = null;
		try {
			theResponsePacket = EPPByteArrayDispatcher.getInstance().processMessage(theRequestPacket, theSessionData);
			if (theResponsePacket != null) {
				aResponse.addHeader("Content-Type", "application/epp+xml;charset=UTF-8");
				aResponse.addHeader("Cache-Control", "no-cache");
				aResponse.addHeader("Expires", "0");
				theOutputStream.write(theResponsePacket);
			}
			else {
				cat.info("doPost: null response packet");
			}
		}
		catch (EPPEventException | EPPAssemblerException e) {
			cat.error("doPost: Exception processing command: " + e);
			sendErrorResponse(EPPResult.COMMAND_FAILED, "Error processing command", theOutputStream, theSessionData);
		}
	}

	/**
	 * Process the HTTP GET request
	 *
	 * @param aRequest
	 *           HTTP request
	 * @param aResponse
	 *           HTTP response
	 *
	 * @throws ServletException
	 *            Exception processing the HTTP request
	 * @throws IOException
	 *            Exception reading and writing from/to the streams
	 */
	@Override
	public void doGet(HttpServletRequest aRequest, HttpServletResponse aResponse) throws ServletException, IOException {
		ServletOutputStream theOutputStream = null;

		try {
			theOutputStream = aResponse.getOutputStream();
		}
		catch (IOException e) {
			cat.error("Exception getting output streams for client request");
			e.printStackTrace();
			return;
		}

		HttpSession theHttpSession = null;
		SessionData theSessionData = null;

		try {

			// Start HTTP Session
			cat.info("Establishing HTTP session");
			theHttpSession = aRequest.getSession(true);

			// Create EPP Greeting
			EPPGreeting theGreeting = new EPPGreeting();

			theGreeting.setServer(EPPEnv.getGreetingServerName());

			// Set DCP
			EPPDcp theDCP = new EPPDcp();
			theDCP.setAccess(EPPDcp.ACCESS_ALL);

			EPPStatement theStatement = new EPPStatement();

			EPPPurpose thePurpose = new EPPPurpose();
			thePurpose.setAdmin(true);
			thePurpose.setProv(true);
			theStatement.setPurpose(thePurpose);

			EPPRecipient theRecipient = new EPPRecipient();
			theRecipient.addOurs(null);
			theRecipient.setPublic(true);
			theStatement.setRecipient(theRecipient);

			theStatement.setRetention(EPPStatement.RETENTION_STATED);

			theDCP.addStatement(theStatement);
			theGreeting.setDcp(theDCP);

			theSessionData = new SessionData();
			theSessionData.setGreeting(theGreeting);

			/**
			 * Set the session data in the HTTP Session
			 */
			synchronized (theHttpSession) {
				theHttpSession.setAttribute("sessionData", theSessionData);
			}

			// Return EPP Greeting
			aResponse.addHeader("Content-Type", "application/epp+xml;charset=UTF-8");
			aResponse.addHeader("Cache-Control", "no-cache");
			aResponse.addHeader("Expires", "0");
			EPPEventResponse theEventResponse = new EPPEventResponse(theGreeting);
			HttpEPPXMLAssembler theAssembler = new HttpEPPXMLAssembler();
			byte theGreetingPacket[];
			theGreetingPacket = theAssembler.encode(theEventResponse, theSessionData);
			theOutputStream.write(theGreetingPacket);
		}
		catch (EPPAssemblerException e) {
			cat.error("doPost: Exception encoding EPP Greeting: " + e);
			sendErrorResponse(EPPResult.COMMAND_FAILED, "Error processing connection", theOutputStream, theSessionData);
		}
	}

	/**
	 * Send EPP error response
	 *
	 * @param aCode
	 *           EPP result code, using one of the constants in
	 *           {@link EPPResult}.
	 * @param aDescription
	 *           The error error description
	 * @param out
	 *           Output stream to write the EPP error response
	 * @param aSessionData
	 *           HTTP session data
	 */
	private void sendErrorResponse(int aCode, String aDescription, OutputStream out, SessionData aSessionData) {
		cat.debug("Enter sendErrorResponse()");
		cat.debug("Sending error response to client, code = " + aCode + ", description = " + aDescription);

		// Create the result
		EPPResult theResult = new EPPResult(aCode);
		theResult.addExtValueReason(aDescription);

		EPPTransId theTransId = new EPPTransId("svrTrans1");

		// Create the response
		EPPResponse response = new EPPResponse(theTransId, theResult);

		// Send the response
		try {
			EPPDispatcher.getInstance().send(response, out, aSessionData);
			out.flush();
		}
		catch (Exception e) {
			cat.debug(".sendErrorResponse() " + "Error sending error response to client: " + e);
		}

		cat.debug(".sendErrorResponse() " + "Return");
	}
}
