/*
 *
 *
 * Copyright  1990-2009 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 *
 * This program 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
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 *
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */
package com.sun.jsr082.bluetooth;

import java.io.IOException;
import com.sun.jsr082.obex.ObexPacketStream;
import com.sun.jsr082.obex.ObexTransport;

import javax.bluetooth.RemoteDevice;
import javax.bluetooth.BluetoothConnectionException;
import javax.microedition.io.Connection;
import javax.microedition.io.Connector;

/*
 * Base class for all bluetooth connections.
 */
abstract public class BluetoothConnection {

    /* Keeps requested connection details. */
    protected BluetoothUrl url;

    /* Keeps open mode. */
    protected int mode;

    /* true if this connection was authorized, false otherwise. */
    private boolean authorized;

    /* true if this connection has requested encryption and is encrypted. */
    private boolean encrypted;

    /* Remote device for this connection. */
    private RemoteDevice remoteDevice;

    /*
     * Retrieves <code>BluetoothConnection</code> from given one.
     * Connection given is supposed to be either Bluetooth connection
     * or a connection that uses Bluetooth as transport. All involved
     * connections supposed to be open.
     *
     * @param conn the connection to extract Bluetooth connection from
     * @return proper <code>BluetoothConnection</code> instance
     * @throws IllegalArgumentException if connection is neither an instance
     *         of BluetoothConnection, nor uses one as transport.
     * @throws IOException if connection given or transport is closed or
     *         transport is invalid.
     */
    public static BluetoothConnection getConnection(Connection conn)
            throws IOException {
        if (conn == null) {
            throw new NullPointerException("Null connection specified.");
        }

        if (conn instanceof ObexPacketStream) {
            conn = ((ObexPacketStream)conn).getTransport();
        }

        if (!(conn instanceof BluetoothConnection)) {
            throw new IllegalArgumentException("The specified connection " +
                    "is not a Bluetooth connection.");
        }

        BluetoothConnection btConn = (BluetoothConnection)conn;
        btConn.checkOpen();
        return btConn;
    }

    /*
     * Creates a new instance of this class.
     *
     * @param url connection url
     * @param mode I/O access mode server side otherwise it's false
     */
    protected BluetoothConnection(BluetoothUrl url, int mode) {
        // IMPL_NOTE: find proper place; the intent here is to start EmulationPolling
        // and SDPServer prior to create a user's notifier
        SDDB.getInstance();

        this.url = url;
        this.mode = mode;
    }

    /*
     * Returns remote device for this connection.
     *
     * @return <code>RemoteDevice</code> object for this connection
     * @throws IOException if this connection is closed
     */
    public RemoteDevice getRemoteDevice() throws IOException {
        checkOpen();
        return remoteDevice;
    }

    /*
     * Returns Bluetooth address of the remote device for this connection.
     *
     * @return Bluetooth address of the remote device
     */
    public abstract String getRemoteDeviceAddress();

    /*
     * Retrieves reference to the remote device for this connection.
     */
    protected void setRemoteDevice() {
        remoteDevice = DiscoveryAgentImpl.getInstance().
                getRemoteDevice(getRemoteDeviceAddress());
        BCC.getInstance().addConnection(getRemoteDeviceAddress());
    }

    /*
     * Removes reference to the remote device.
     */
    protected void resetRemoteDevice() {
        if (encrypted) {
            encrypt(false);
        }
        remoteDevice = null;
        BCC.getInstance().removeConnection(getRemoteDeviceAddress());
    }

    /*
     * Determines if this connection is closed.
     *
     * @return true if this connection is closed, false otherwise
     */
    public boolean isClosed() {
        return remoteDevice == null;
    }

    /*
     * Determines whether this connection represents the server side,
     * i.e. this connection was created by a notifier in acceptAndOpen().
     *
     * @return true if this connection is a server-side connection,
     *         false otherwise
     */
    public boolean isServerSide() {
        return url.isServer;
    }

    /*
     * Returns the authorization state of this connection.
     *
     * @return true if this connection has been authorized, false otherwise
     */
    public boolean isAuthorized() {
        return authorized;
    }

    /*
     * Authorizes this connection. It is assumed that the remote device has
     * previously been authenticated. This connection must represent the server
     * side, i.e. isServer() should return true.
     *
     * @return true if the operation succeeded, false otherwise
     */
    public boolean authorize() {
        authorized = BCC.getInstance().authorize(
                remoteDevice.getBluetoothAddress(), getServiceRecordHandle());
        return authorized;
    }

    /*
     * Changes encryption for this connection.
     *
     * @param enable specifies whether encription should be turned on or off
     * @return true if encryption has been set as required, false otherwise
     */
    public boolean encrypt(boolean enable) {
        if (enable == encrypted) {
            return true;
        }
        BCC.getInstance().encrypt(remoteDevice.getBluetoothAddress(), enable);
        if (remoteDevice.isEncrypted()) {
            if (enable) {
                encrypted = true;
                return true;
            }
            encrypted = false;
            return false;
        } else {
            if (enable) {
                return false;
            }
            encrypted = false;
            return true;
        }
    }

    /*
     * Returns handle for the service record of the service this connection
     * is attached to. Valid for server-side (incoming) connections only.
     *
     * @return service record handle, or 0 if the handle is not available
     */
    protected int getServiceRecordHandle() {
        return 0;
    }

    /*
     * Checks if this connection is open.
     *
     * @throws IOException if this connection is closed
     */
    protected void checkOpen() throws IOException {
        if (isClosed()) {
            throw new IOException("Connection is closed.");
        }
    }

    /*
     * Performs security checks, such as authentication, authorization, and
     * encryption setup.
     *
     * @throws BluetoothConnectionException when failed
     */
    protected void checkSecurity()
            throws BluetoothConnectionException, IOException {
        if (url.authenticate) {
            if (!remoteDevice.authenticate()) {
                throw new BluetoothConnectionException(
                        BluetoothConnectionException.SECURITY_BLOCK,
                        "Authentication failed.");
            }
        }
        if (url.authorize) {
            if (!remoteDevice.authorize((Connection)this)) {
                throw new BluetoothConnectionException(
                        BluetoothConnectionException.SECURITY_BLOCK,
                        "Authorization failed.");
            }
        }
        if (url.encrypt) {
            if (!remoteDevice.encrypt((Connection)this, true)) {
                throw new BluetoothConnectionException(
                        BluetoothConnectionException.SECURITY_BLOCK,
                        "Encryption failed.");
            }
        }
    }

    /*
     * Checks read access.
     *
     * @throws IOException if open mode does not permit read access
     */
    protected void checkReadMode() throws IOException {
        if ((mode & Connector.READ) == 0) {
            throw new IOException("Invalid mode: " + mode);
        }
    }

    /*
     * Checks write access.
     *
     * @throws IOException if open mode does not permit write access
     */
    protected void checkWriteMode() throws IOException {
        if ((mode & Connector.WRITE) == 0) {
            throw new IOException("Invalid mode: " + mode);
        }
    }

}