/**
 *  BlueCove - Java library for Bluetooth
 *  Copyright (C) 2006-2009 Vlad Skarzhevskyy
 *
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 *
 *  @author vlads
 *  @version $Id: BluetoothConnectionNotifierBase.java 2915 2009-03-13 17:07:26Z skarzhevskyy $
 */
package com.intel.bluetooth;

import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.DataElement;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.ServiceRegistrationException;
import javax.microedition.io.Connection;

/**
 *
 */
abstract class BluetoothConnectionNotifierBase implements Connection, BluetoothConnectionNotifierServiceRecordAccess {

	private static Hashtable stackConnections = new Hashtable();

	protected BluetoothStack bluetoothStack;

	protected volatile long handle;

	protected ServiceRecordImpl serviceRecord;

	protected boolean closed;

	protected int securityOpt;

	static void shutdownConnections(BluetoothStack bluetoothStack) {
		Vector connections;
		synchronized (stackConnections) {
			connections = (Vector) stackConnections.get(bluetoothStack);
		}
		if (connections == null) {
			return;
		}
		Vector c2shutdown = new Vector();
		c2shutdown = Utils.clone(connections.elements());
		for (Enumeration en = c2shutdown.elements(); en.hasMoreElements();) {
			BluetoothConnectionNotifierBase c = (BluetoothConnectionNotifierBase) en.nextElement();
			try {
				c.shutdown();
			} catch (IOException e) {
				DebugLog.debug("connection shutdown", e);
			}
		}
	}

	protected BluetoothConnectionNotifierBase(BluetoothStack bluetoothStack, BluetoothConnectionNotifierParams params)
			throws BluetoothStateException, Error {
		this.bluetoothStack = bluetoothStack;
		this.closed = false;
		if (params.name == null) {
			throw new NullPointerException("Service name is null");
		}
		/*
		 * create service record to be later updated by BluetoothStack
		 */
		this.serviceRecord = new ServiceRecordImpl(this.bluetoothStack, null, 0);
	}

	protected void connectionCreated() {
		Vector connections;
		synchronized (stackConnections) {
			connections = (Vector) stackConnections.get(this.bluetoothStack);
			if (connections == null) {
				connections = new Vector();
				stackConnections.put(this.bluetoothStack, connections);
			}
		}
		connections.addElement(this);
	}

	protected abstract void stackServerClose(long handle) throws IOException;

	/*
	 * Close the connection. When a connection has been closed, access to any of
	 * its methods except this close() will cause an an IOException to be
	 * thrown. Closing an already closed connection has no effect. Streams
	 * derived from the connection may be open when method is called. Any open
	 * streams will cause the connection to be held open until they themselves
	 * are closed. In this latter case access to the open streams is permitted,
	 * but access to the connection is not.
	 */

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.microedition.io.Connection#close()
	 */
	public void close() throws IOException {
		if (!closed) {
			shutdown();
		}
	}

	public void shutdown() throws IOException {
		closed = true;
		if (handle != 0) {
			DebugLog.debug("closing ConnectionNotifier", handle);
			Vector connections;
			synchronized (stackConnections) {
				connections = (Vector) stackConnections.get(this.bluetoothStack);
			}
			connections.removeElement(this);
			long synchronizedHandle;
			synchronized (this) {
				synchronizedHandle = handle;
				handle = 0;
			}
			if (synchronizedHandle != 0) {

				ServiceRecordsRegistry.unregister(serviceRecord);

				if ((serviceRecord.deviceServiceClasses != 0)
						&& ((bluetoothStack.getFeatureSet() & BluetoothStack.FEATURE_SET_DEVICE_SERVICE_CLASSES) != 0)) {
					bluetoothStack.setLocalDeviceServiceClasses(ServiceRecordsRegistry.getDeviceServiceClasses());
				}

				stackServerClose(synchronizedHandle);
			}
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.intel.bluetooth.BluetoothConnectionNotifierServiceRecordAccess#getServiceRecord()
	 */
	public ServiceRecord getServiceRecord() {
		if (closed) {
			throw new IllegalArgumentException("ConnectionNotifier is closed");
		}
		ServiceRecordsRegistry.register(this, serviceRecord);
		return serviceRecord;
	}

	protected void validateServiceRecord(ServiceRecord srvRecord) {
		DataElement protocolDescriptor = srvRecord.getAttributeValue(BluetoothConsts.ProtocolDescriptorList);
		if ((protocolDescriptor == null) || (protocolDescriptor.getDataType() != DataElement.DATSEQ)) {
			throw new IllegalArgumentException("ProtocolDescriptorList is mandatory");
		}

		DataElement serviceClassIDList = srvRecord.getAttributeValue(BluetoothConsts.ServiceClassIDList);
		if ((serviceClassIDList == null) || (serviceClassIDList.getDataType() != DataElement.DATSEQ)
				|| serviceClassIDList.getSize() == 0) {
			throw new IllegalArgumentException("ServiceClassIDList is mandatory");
		}

		boolean isL2CAPpresent = false;
		for (Enumeration protocolsSeqEnum = (Enumeration) protocolDescriptor.getValue(); protocolsSeqEnum
				.hasMoreElements();) {
			DataElement elementSeq = (DataElement) protocolsSeqEnum.nextElement();
			if (elementSeq.getDataType() == DataElement.DATSEQ) {
				Enumeration elementSeqEnum = (Enumeration) elementSeq.getValue();
				if (elementSeqEnum.hasMoreElements()) {
					DataElement protocolElement = (DataElement) elementSeqEnum.nextElement();
					if ((protocolElement.getDataType() == DataElement.UUID)
							&& (BluetoothConsts.L2CAP_PROTOCOL_UUID.equals(protocolElement.getValue()))) {
						isL2CAPpresent = true;
						break;
					}
				}
			}
		}
		if (!isL2CAPpresent) {
			throw new IllegalArgumentException("L2CAP UUID is mandatory in ProtocolDescriptorList");
		}
	}

	protected abstract void updateStackServiceRecord(ServiceRecordImpl serviceRecord, boolean acceptAndOpen)
			throws ServiceRegistrationException;

	/*
	 * (non-Javadoc)
	 *
	 * @see com.intel.bluetooth.BluetoothConnectionNotifierServiceRecordAccess#updateServiceRecord(boolean)
	 */
	public void updateServiceRecord(boolean acceptAndOpen) throws ServiceRegistrationException {
		if (serviceRecord.attributeUpdated || (!acceptAndOpen)) {
			try {
				validateServiceRecord(this.serviceRecord);
			} catch (IllegalArgumentException e) {
				if (acceptAndOpen) {
					throw new ServiceRegistrationException(e.getMessage());
				} else {
					throw e;
				}
			}
			try {
				updateStackServiceRecord(serviceRecord, acceptAndOpen);
			} finally {
				serviceRecord.attributeUpdated = false;
			}
		}
		if ((serviceRecord.deviceServiceClasses != serviceRecord.deviceServiceClassesRegistered)
				&& ((bluetoothStack.getFeatureSet() & BluetoothStack.FEATURE_SET_DEVICE_SERVICE_CLASSES) != 0)) {

			bluetoothStack.setLocalDeviceServiceClasses(ServiceRecordsRegistry.getDeviceServiceClasses());

			serviceRecord.deviceServiceClassesRegistered = serviceRecord.deviceServiceClasses;
		}
	}
}
