/**
 * Copyright (c) 2014 Etherios, a Division of Digi International Inc.
 * All rights not expressly granted are reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Etherios 11001 Bren Road East, Minnetonka, MN 55343
 * =======================================================================
 */
package com.etherios.connector.emulator.network;

import java.io.InputStream;
import java.io.OutputStream;

import javax.microedition.io.Connector;
import javax.microedition.io.SecureConnection;

import com.etherios.connector.core.network.ISocket;
import javax.microedition.io.AccessPoint;
import javax.microedition.io.ConnectionOption;

/**
 * Class which handles the secure Cloud Connector sslSocket.
 */

public class ConnectorSSLSocket implements ISocket {
	
	// Variables
	private String host;
	
	private int port;
	
	private SecureConnection socket;
	
	/**
	 * Class constructor. Instances a new object of type ConnectorSSLSocket
	 * with the given parameters.
	 * 
	 * @param host Socket destination host.
	 * @param port Socket destination port.
	 */
	public ConnectorSSLSocket(String host, int port) {
		this.host = host;
		this.port = port;
	}

	@Override
	public void close() throws Exception {
		if (socket != null) {
			socket.close();
			socket = null;
		}
	}

	@Override
	public InputStream getInputStream() throws Exception {
		socket = openSocketConnection();
		if (socket == null)
			return null;
		return socket.openInputStream();
	}

	@Override
	public OutputStream getOutputStream() throws Exception {
		socket = openSocketConnection();
		if (socket == null)
			return null;
		return socket.openOutputStream();
	}

	/**
	 * Instances the SSL Socket.
	 * 
	 * @param ap the Access Point to be used for the connection.
	 * @return a secure connection.
	 * @throws Exception if any error occurs while instancing the SSL socket.
	 */
	private SecureConnection instanceSSLSocket(AccessPoint ap) throws Exception {
		ConnectionOption protocol = new ConnectionOption("Protocol", "SSLv3");
		ConnectionOption cipherTLS128 = new ConnectionOption("CipherSuite", "TLS_RSA_WITH_AES_128_CBC_SHA");
		ConnectionOption cipherTLS256 = new ConnectionOption("CipherSuite", "TLS_RSA_WITH_AES_256_CBC_SHA");
		ConnectionOption cipherSSL128 = new ConnectionOption("CipherSuite", "SSL_RSA_WITH_RC4_128_SHA");
		ConnectionOption apOption = new ConnectionOption("AccessPoint", ap);
		
		ConnectionOption[] options;
		if (ap != null)
			options = new ConnectionOption[]{apOption, protocol, cipherTLS256, cipherTLS128, cipherSSL128};
		else
			options = new ConnectionOption[]{protocol, cipherTLS256, cipherTLS128, cipherSSL128};
		
		return (SecureConnection) Connector.open("ssl://" + host + ":" + port, options);
	}

	@Override
	public boolean isClosed() {
		return socket == null;
	}

	/**
	 * Returns the sslSocket connection.
	 * 
	 * @return the sslSocket connection.
	 */
	public SecureConnection getSocketConnection() {
		return socket;
	}

	/**
	 * Returns an open sslSocket connection to host:port.
	 * <p>First it tries with the default access point, if it is not connected, it 
	 * will look for a connected one in the following order:</p>
	 * 
	 * <p>WIRED, WIFI, 3GPP, CDMA, DIALUP, LOOPBACK</p>
	 * 
	 * @return an open sslSocket connection or <code>null</code> if there is not
	 *         any access point connected.
	 * @throws Exception if any error occurs while opening the secure socket 
	 *                   connection.
	 */
	private SecureConnection openSocketConnection() throws Exception {
		if (socket != null)
			return socket;
		// Try first with the default access point
		SecureConnection sslSocket = instanceSSLSocket(null);
		if (sslSocket != null && sslSocket.getAccessPoint() != null
				&& sslSocket.getAccessPoint().isConnected())
			return sslSocket;
		// Look for a connected access point using the following priorities:
		// WIRED, WIFI, 3GPP, CDMA, DIALUP, LOOPBACK
		String[] typesPriority = new String[]{"WIRED", "WIFI", "3GPP", 
												"CDMA", "DIALUP", "LOOPBACK"};
		AccessPoint ap = null;
		for (int i = 0; i < typesPriority.length; i++) {
			ap = getActiveAccessPoint(typesPriority[i]);
			if (ap != null) 
				break;
		}
		if (ap == null)
			return null;
		sslSocket = instanceSSLSocket(ap);
		return sslSocket;
	}
	
	/**
	 * Get the active access point of the given network interface type.
	 * The active access point is the first found access point connected for the
	 * given network interface type.
	 * 
	 * @param type the network interface type 
	 *             (WIRED, WIFI, 3GPP, CDMA, DIALUP, LOOPBACK)
	 * @return the first access point connected found for the given network 
	 *         interface type, <code>null</code> if none.
	 */
	private AccessPoint getActiveAccessPoint(String type) {
		AccessPoint[] caps = AccessPoint.getAccessPoints(true);
		for (int i = 0; i < caps.length; i++) {
			if (caps[i].getType().equals(type))
				return caps[i];
		}
		return null;
	}
}
