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

import java.io.IOException;

import com.etherios.connector.IPlatformCustomization;
import com.etherios.connector.core.log.ConnectorLogger;
import com.etherios.connector.core.log.Logger;
import com.etherios.connector.core.network.ISocket;
import com.etherios.connector.core.network.ImeiNumber;
import com.etherios.connector.core.network.MacAddress;
import com.etherios.connector.core.network.INetworkImplementation;
import com.etherios.connector.core.preferences.IConnectorPreferences;
import com.etherios.connector.core.system.Location;
import com.etherios.connector.core.utils.HexUtils;
import com.etherios.connector.emulator.network.ConnectorSSLSocket;
import com.etherios.connector.emulator.network.ConnectorSocket;
import com.etherios.connector.log.DefaultConnectorLogger;
import com.etherios.connector.preferences.PreferencesManager;

import javax.microedition.io.AccessPoint;
import javax.microedition.io.Connector;
import javax.microedition.io.ServerSocketConnection;
import javax.microedition.io.SocketConnection;
import javax.microedition.location.Criteria;
import javax.microedition.location.LocationException;
import javax.microedition.location.LocationProvider;
import javax.microedition.location.QualifiedCoordinates;

import jdk.dio.DeviceManager;
import jdk.dio.watchdog.WatchdogTimer;

class PlatformCustomization implements IPlatformCustomization {

	// Variables
	private DefaultConnectorLogger connectorLogger;
	private IConnectorPreferences preferencesManager;
	
	private ConnectorSocket socket;
	private ConnectorSSLSocket sslSocket;
	
	private final String configFile;
	
	/**
	 * Class constructor. Instances a new Platform Customization object.
	 */
	public PlatformCustomization() {
		configFile = null;
	}
	
	/**
	 * Class constructor. Instances a new Platform Customization object.
	 * 
	 * @param configFile Path of the Cloud Connector configuration file.
	 */
	public PlatformCustomization(String configFile) {
		this.configFile = configFile;
	}

	@Override
	public ISocket getSocket(String host, int port) {
		if (socket == null || socket.isClosed())
			socket = new ConnectorSocket(host, port);
		return socket;
	}

	@Override
	public ISocket getSSLSocket(String host, int port) {
		if (sslSocket == null || sslSocket.isClosed())
			sslSocket = new ConnectorSSLSocket(host, port);
		return sslSocket;
	}

	@Override
	public IConnectorPreferences getConnectorPreferences() {
		if (preferencesManager == null) {
			if (configFile == null)
				preferencesManager = new PreferencesManager(this);
			else
				preferencesManager = new PreferencesManager(this, configFile);
		}
		return preferencesManager;
	}

	@Override
	public ConnectorLogger getConnectorLogger() {
		if (connectorLogger == null) {
			if (configFile == null)
				connectorLogger = new DefaultConnectorLogger((PreferencesManager)getConnectorPreferences());
			else
				connectorLogger = new DefaultConnectorLogger((PreferencesManager)getConnectorPreferences(), configFile);
		}
		return connectorLogger;
	}

	@Override
	public Location getLocation() {
		// Read location from device.
		javax.microedition.location.Location location = null;
		boolean isCurrent = true;
		try {
			// Get the current location
			LocationProvider lp = LocationProvider.getInstance(new Criteria());
			location = lp.getLocation(2); // 2 seconds
			int state = lp.getState();
			boolean valid = location.isValid();
			if (state != LocationProvider.AVAILABLE || !valid) {
				Logger.logSystemDebug(getConnectorLogger(), "Warning! Could not get current device location, LocationProvider: " 
						+ (state == LocationProvider.OUT_OF_SERVICE ? "OUT OF SERVICE" : "TEMPORARILY UNAVAILABLE") 
						+ ", Location " + (valid ? "valid" : "invalid") + ": " + location.getExtraInfo("text/plain"));
				location = null;
			}
		} catch (LocationException e) {
			// Cannot retrieve location in the configured timeout
			Logger.logSystemDebug(getConnectorLogger(), 
					"Warning! Could not get current device location: " + e.getMessage());
		} catch (InterruptedException e) {
			// Interrupted exception... do nothing
			Logger.logSystemDebug(getConnectorLogger(), 
					"Warning! Could not get current device location: " + e.getMessage());
		}
		// Couldn't get current location, try to get last known
		if (location == null)
			location = LocationProvider.getLastKnownLocation();
		
		if (location == null)
			return null;
		else
			isCurrent = false;
		QualifiedCoordinates coordinates = location.getQualifiedCoordinates();
		Logger.logSystemDebug(getConnectorLogger(), 
				"Read device location: " + coordinates.getLatitude() + ", " + coordinates.getLongitude() + ", " + coordinates.getAltitude());
		Location newLocation = new Location("" + coordinates.getLatitude(), "" + coordinates.getLongitude(), "" + coordinates.getAltitude());
		newLocation.setCurrent(isCurrent);
		return newLocation;
	}

	@Override
	public boolean canReboot() {
		return false;
	}

	@Override
	public void reboot() {
		// Reboot using watchdog
		try {
			WatchdogTimer wd = DeviceManager.open("WDG", WatchdogTimer.class, (String[])null);
			Logger.logSystemInfo(getConnectorLogger(), "[Watchdog Reset] Watchdog started");
			Logger.logSystemInfo(getConnectorLogger(), "[Watchdog Reset] Last reboot caused by watchdog = " + wd.causedLastReboot());
			wd.start(100);
			Logger.logSystemInfo(getConnectorLogger(), "[Watchdog Reset] Timeout = " + wd.getTimeout());
		} catch (IOException e) {
			Logger.logException(getConnectorLogger(), e);
		}
	}

	@Override
	public boolean isSimulator() {
		String platform = System.getProperty("microedition.platform");
		if (platform.contains("NOKIA") || platform.contains("SAMSUNG")
			|| platform.contains("SONYERICSSON") || platform.contains("LG")
			|| platform.contains("BLACKBERRY") || platform.contains("RIM")) { 
			return false;
		} else if (platform.contains("J2ME") 
				|| platform.equalsIgnoreCase("SunMicrosystems_wtk"))
			return true;
		return false;
	}

	@Override
	public int getNetworkConnectionType() {
		AccessPoint ap = null;
		try {
			ap = getSocketAccessPoint();
		} catch (IOException e) {
			Logger.logSystemError(getConnectorLogger(), "Error getting the network type.");
			Logger.logException(getConnectorLogger(), e);
		}
		if (ap == null)
			return INetworkImplementation.UNKNOWN_TYPE;
		return getNetworkConnectionType(ap);
	}

	@Override
	public byte[] getPPPLinkSpeed() {
		Logger.logSystemInfo(getConnectorLogger(), "PPP link speed in bits per second "
				+ "not provided. Using default PPP link speed " 
				+ HexUtils.byteArrayToInt(INetworkImplementation.DEFAULT_LINK_SPEED, 
						INetworkImplementation.DEFAULT_LINK_SPEED.length));
		return INetworkImplementation.DEFAULT_LINK_SPEED;
	}

	@Override
	public byte[] getPPPPhoneNumber() {
		Logger.logSystemInfo(getConnectorLogger(), "Phone number dialed for the PPP "
				+ "connection not provided. Using default phone number " 
				+ HexUtils.byteArrayToInt(INetworkImplementation.DEFAULT_PHONE_NUMBER, 
						INetworkImplementation.DEFAULT_PHONE_NUMBER.length));
		return INetworkImplementation.DEFAULT_PHONE_NUMBER;
	}

	@Override
	public MacAddress getPrimaryMacAddress() {
		String devicIDType = ((PreferencesManager) getConnectorPreferences()).getParameterValue(
				PreferencesManager.PARAM_DEVICE_ID_TYPE);
		// If the Device ID is generated through the MAC, cancel the execution.
		// Otherwise, use the default MAC 00:11:22:33:44:55.
		if (devicIDType.equals(PreferencesManager.DEVICE_ID_TYPE_MAC)) {
			Logger.logSystemError(getConnectorLogger(), 
					"Method to get the Mac Address of the device has to be provided.");
			return null;
		}
		return new MacAddress("00:11:22:33:44:55");
	}

	@Override
	public ImeiNumber getImeiNumber() {
		Logger.logSystemError(getConnectorLogger(), 
				"Method to get the IMEI number of the device has to be provided.");
		return null;
	}

	@Override
	public String getLocalIPAddress() {
		String ipAddress = null;
		try {
			AccessPoint ap = getSocketAccessPoint();
			if (ap != null)
				ipAddress = ap.getProperty("ipaddr");
			if (ipAddress != null && ipAddress.length() > 0)
				return ipAddress;
		} catch (IOException ex) {
			// Do not log anything and try opening a socket.
		}
		
		ServerSocketConnection ssc = null;
		try {
			ssc = (ServerSocketConnection)Connector.open("socket://:30000");
			ipAddress = ssc.getLocalAddress();
		} catch (IOException e) {
			Logger.logException(getConnectorLogger(), e);
			return null;
		} finally {
			if(ssc != null)
				try {
					ssc.close();
				} catch (IOException e) {}
		}
		return ipAddress;
	}
	
	/**
	 * Returns the connected socket access point.
	 * 
	 * @return the connected socket access point.
	 * @throws IOException If an I/O error occurs.
	 */
	private AccessPoint getSocketAccessPoint() throws IOException {
		SocketConnection connection = null;
		if (getConnectorPreferences().isSecureConnectionEnabled())
			connection = sslSocket.getSocketConnection();
		else
			connection = socket.getSocketConnection();
		if (connection == null)
			return null;
		return connection.getAccessPoint();
	}
	
	/**
	 * Retrieves the active network connection type:
	 * 
	 * <p>See:</p>
	 * <ul>
	 * <li>{@link INetworkImplementation#ETH_TYPE}</li>
	 * <li>{@link INetworkImplementation#WIFI_TYPE}</li>
	 * <li>{@link INetworkImplementation#MOBILE_TYPE}</li>
	 * <li>{@link INetworkImplementation#UNKNOWN_TYPE}</li>
	 * </ul>
	 * 
	 * @param ap the access point to get the connection type.
	 * @return The active network connection type.
	 * 
	 * @see INetworkImplementation#ETH_TYPE
	 * @see INetworkImplementation#WIFI_TYPE
	 * @see INetworkImplementation#MOBILE_TYPE
	 * @see INetworkImplementation#UNKNOWN_TYPE
	 */
	private int getNetworkConnectionType (AccessPoint ap) {
		if (ap == null || !ap.isConnected())
			return INetworkImplementation.UNKNOWN_TYPE;
		if (ap.getType().equals("WIRED"))
			return INetworkImplementation.ETH_TYPE;
		if (ap.getType().equals("WIFI"))
			return INetworkImplementation.WIFI_TYPE;
		if (ap.getType().equals("3GPP")
				|| ap.getType().equals("CDMA")
				|| ap.getType().equals("DIALUP"))
			return INetworkImplementation.MOBILE_TYPE;
		// Even for LOOPBACK
		return INetworkImplementation.UNKNOWN_TYPE;
	}
}
