/*
 *
 *
 * 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 com.sun.jsr082.obex.SessionNotifierImpl;
import javax.microedition.io.Connection;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.ServiceRegistrationException;

/*
 * The <code>LocalDeviceImpl</code> class is a LocalDevice
 * API class implementation. This is a singleton class.
 */
public final class LocalDeviceImpl {

    /* Set to false in RR version - then the javac skip the code. */
    private static final boolean DEBUG = false;

    /* Keeps this singleton object. */
    private static LocalDeviceImpl instance;

    /* Keeps bluetooth address of this device. */
    private String bluetoothAddress;

    /* Timeout canceller of limited discoverable mode. */
    private CancelerOfLIAC cancelerOfLIAC = new CancelerOfLIAC();

    /*
     * Device should not be in LIAC for more than 1 minute,
     * then return to the previous mode.
     */
    private class CancelerOfLIAC implements Runnable {
        /* One minute. */
        private long MINUTE = 60000;
        /* Specifies the delay for timeout checks. */
        private int RETRY_DELAY = 100; // ms
        /* Saved access code to get back to at timeout. */
        private int savedCode;
        /* Keeps canceller start time to check if timeout expired. */
        private long startTime = -1;
        /* Flaggs if LIAC mode has been cancelled from outside. */
        private boolean isCanceledFromOutside = true;

        /*
         * Starts timeout killer if new discoverable mode is LIAC.
         *
         * @param oldCode the previous value of discoverable mode.
         * @param newCode discoverable mode that has been set just.
         */
        synchronized void notifyNewAccessCode(int oldCode, int newCode) {
            if (newCode == oldCode) {
                return;
            }
            savedCode = oldCode;

            if (newCode == DiscoveryAgent.LIAC) {
                // the currentCode was not LIAC - start a killer
                startTime = System.currentTimeMillis();
                new Thread(this).start();
            } else {
                /*
                 * startTime != -1 if the killer is running, but
                 * this method may be called by the killer itself -
                 * then there is no need to stop it.
                 */
                boolean stopKiller = startTime != -1 && isCanceledFromOutside;
                startTime = -1;
                isCanceledFromOutside = false;

                if (stopKiller) {
                    try {
                        wait();
                    } catch (InterruptedException e) {}
                }
            }
        }

        /*
         * Implements of <code>run()</code> of <code>Runnable</code> interface.
         */
        public void run() {
            while (true) {
                try {
                    Thread.sleep(RETRY_DELAY);
                } catch (InterruptedException e) {} // ignore

                synchronized (this) {
                    // the access code was changed by application
                    if (startTime == -1) {
                        notify();
                        return;
                    }
                    // minute is running yet
                    if (System.currentTimeMillis() - startTime < MINUTE) {
                        continue;
                    }
                    // minute is over - change the mode back
                    isCanceledFromOutside = false;
                    boolean res = false;

                    try {
                        res = setDiscoverable(savedCode);
                    } catch (BluetoothStateException e) {}

                    if (!res) {
                        // not now - h-m-m, ok, try later then
                        isCanceledFromOutside = true;
                        continue;
                    }
                    return;
                }
            }
        }
    } // end of class 'CancelerOfLIAC' definition

    /*
     * Constructs the only instance of <code>LocalDeviceImpl</code>.
     */
    private LocalDeviceImpl() {
    }

    /*
     * Retrieves singleton instance.
     *
     * @return the only instance of <code>LocalDeviceImpl</code>
     * @throws BluetoothStateException if an error occured.
     */
    public static synchronized LocalDeviceImpl getInstance()
            throws BluetoothStateException {
        if (instance == null) {
            instance = new LocalDeviceImpl();
        }
        return instance;
    }

    // JAVADOC COMMENT ELIDED
    public String getFriendlyName() {
        return BCC.getInstance().getFriendlyName();
    }

    // JAVADOC COMMENT ELIDED
    public DeviceClass getDeviceClass() {
        return BCC.getInstance().getDeviceClass();
    }

    // JAVADOC COMMENT ELIDED
    public String getProperty(String property) {
        return System.getProperty(property);
    }

    // JAVADOC COMMENT ELIDED
    public int getDiscoverable() {
        return BCC.getInstance().getAccessCode();
    }

    // JAVADOC COMMENT ELIDED
    public String getBluetoothAddress() {
        return BCC.getInstance().getBluetoothAddress();
    }

    // JAVADOC COMMENT ELIDED
    public boolean setDiscoverable(int accessCode)
            throws BluetoothStateException {
        // Check if the specified mode has a valid value
        if (accessCode != DiscoveryAgent.GIAC &&
                accessCode != DiscoveryAgent.LIAC &&
                accessCode != DiscoveryAgent.NOT_DISCOVERABLE &&
                (accessCode < 0x9E8B00 || accessCode > 0x9E8B3F)) {
            throw new IllegalArgumentException("Access code is out of range: "
                    + "0x" + Integer.toHexString(accessCode).toUpperCase());
        }
        synchronized (cancelerOfLIAC) {
            /*
             * Accroding to the spec, the device should only be limited
             * discoverable (DiscoveryAgent.LIAC) for 1 minute -
             * then back to the PREVIOUS discoverable mode.
             */
            int oldAccessCode = BCC.getInstance().getAccessCode();
            if (BCC.getInstance().setAccessCode(accessCode)) {
                cancelerOfLIAC.notifyNewAccessCode(oldAccessCode, accessCode);
                if (accessCode != DiscoveryAgent.NOT_DISCOVERABLE) {
                    // Start SDDB if discoverable mode was set successfully
                    // IMPL_NOTE: Do we really need this step?
                    SDDB.getInstance();
                }
                return true;
            }
        }
        return false;
    }

    // JAVADOC COMMENT ELIDED
    public ServiceRecord getRecord(Connection notifier) {
        if (notifier == null) {
            throw new NullPointerException("Null notifier specified.");
        }
        if (!(notifier instanceof BluetoothNotifier)) {
            if (!(notifier instanceof SessionNotifierImpl)) {
                throw new IllegalArgumentException("Invalid notifier class.");
            }
            Connection transport =
                ((SessionNotifierImpl)notifier).getTransport();
            if (!(transport instanceof BluetoothNotifier)) {
                throw new IllegalArgumentException("Invalid notifier class.");
            }
            return ((BluetoothNotifier)transport).getServiceRecord();
        }
        return ((BluetoothNotifier)notifier).getServiceRecord();
    }

    // JAVADOC COMMENT ELIDED
    public void updateRecord(ServiceRecord srvRecord)
            throws ServiceRegistrationException {
        if (DEBUG) {
            System.out.println("LocalDeviceImpl.updateRecord");
        }
        if (srvRecord == null) {
            throw new NullPointerException("Null record specified.");
        }
        if (!(srvRecord instanceof ServiceRecordImpl)) {
            throw new IllegalArgumentException("Invalid service record class.");
        }
        ServiceRecordImpl record = (ServiceRecordImpl)srvRecord;
        BluetoothNotifier notifier = record.getNotifier();
        if (notifier == null) {
            throw new IllegalArgumentException(
                    "Service record is not from local SDDB.");
        }
        notifier.updateServiceRecord(record);
    }

    /*
     * Checks if Bluetooth device is turned on.
     *
     * @return <code>true</code> is the Bluetooth device is on,
     *         <code>false</code> otherwise.
     */
    public boolean isPowerOn() {
        return BCC.getInstance().isBluetoothEnabled();
    }

}