/*******************************************************************************
 *  The MIT License (MIT)
 *
 *  Copyright (c) 2015 - 2019  Dr. Marc Mültin (V2G Clarity)
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 *******************************************************************************/
package com.v2gclarity.risev2g.evcc.transportLayer;

import java.io.IOException;
import java.net.Inet6Address;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

import com.v2gclarity.risev2g.shared.misc.V2GTPMessage;

public class TCPClient extends StatefulTransportLayerClient {
	
	/*
	 *  Lazy instantiation of the Singleton since a TCP connection might not be
	 *  initialized if the SECCDiscovery message exchange failed.
	 *  The volatile keyword ensures that multiple threads handle the uniqueTCPClientInstance
	 *  variable correctly when it is being initialized to the TCPClient instance.
	 */
	private static volatile TCPClient uniqueTCPClientInstance;
	private Socket tcpSocketToServer;
	
	public TCPClient() {} 
	
	/**
	 * Checks for an instance and creates one if there isn't one already.
	 * The synchronized block is only entered once as long as there is no existing instance of the
	 * TCPClient (safes valuable resource).
	 * @return
	 */
	public static TCPClient getInstance() {
		if (uniqueTCPClientInstance == null) {
			synchronized (TCPClient.class) {
				if (uniqueTCPClientInstance == null) {
					uniqueTCPClientInstance = new TCPClient();
				}
			}
		}
		
		return uniqueTCPClientInstance;
	}
	
	
	/**
	 * Initializes the TCP client as soon as a SECCDiscoveryRes message arrived.
	 * 
	 * @param host The address of the SECC's TCP server to connect to
	 * @param port The port of the SECC's TCP server to connect to
	 */
	public boolean initialize(Inet6Address host, int port) {
		super.initialize();
		
		try {
			setTcpSocketToServer(new Socket(host, port));  
			setInStream(getTcpSocketToServer().getInputStream());
			setOutStream(getTcpSocketToServer().getOutputStream());

			getLogger().info("TCP client connection established \n\t from link-local address " +
							  getClientAddress() + " and port " + getClientPort() + 
							  "\n\t to host " + host.getHostAddress() + " and port " + port);
			
			return true;
		} catch (UnknownHostException e) {
			getLogger().error("TCP client connection failed (UnknownHostException)!", e);
		} catch (IOException e) {
			getLogger().error("TCP client connection failed (IOException)!", e);
		}
		
		return false;
	}
	
	
	@Override
	public void run() {
		while (!Thread.currentThread().isInterrupted()) { 
			if (getTimeout() > 0) {
				try {
					getSocketToServer().setSoTimeout(getTimeout());
					
					if (!processIncomingMessage()) break;
					
				} catch (SocketTimeoutException e) {
					stopAndNotify("A timeout occurred while waiting for response message", null);
					break;
				} catch (IOException e2) {
					stopAndNotify("An IOException occurred while trying to read message", e2);
					break;
				}
			}
		}
		
		stop();
	}
	
	
	@Override
	public void send(V2GTPMessage message, int timeout) {
		setV2gTPMessage(null);
		
		try {
			getOutStream().write(message.getMessage());
			getOutStream().flush();
			getLogger().debug("Message sent");
			setTimeout(timeout);
		} catch (IOException e) {
			getLogger().error("An undefined IOException occurred while trying to send message", e);
		}
	}
	
	@Override
	public void stop() {
		if (!isStopAlreadyInitiated()) {
			getLogger().debug("Stopping TCP client ...");
			setStopAlreadyInitiated(true);
			
			try {
				getInStream().close();
				getOutStream().close();
				getTcpSocketToServer().close();
				uniqueTCPClientInstance = null;
				Thread.currentThread().interrupt();
			} catch (IOException e) {
				getLogger().error("Error occurred while trying to close TCP socket to server", e);
			} 
			
			getLogger().debug("TCP client stopped");
		}
	}
	
	public Socket getTcpSocketToServer() {
		return tcpSocketToServer;
	}

	public void setTcpSocketToServer(Socket tcpSocketToServer) {
		this.tcpSocketToServer = tcpSocketToServer;
	}


	public Socket getSocketToServer() {
		return tcpSocketToServer;
	}

	public void setSocketToServer(Socket socketToServer) {
		this.tcpSocketToServer = socketToServer;
	}
}