/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed 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.
 */

package android.bluetooth;

import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;

/**
 * The Android Bluetooth API is not finalized, and *will* change. Use at your
 * own risk.
 *
 * The base RFCOMM (service) connection for a headset or handsfree device.
 *
 * In the future this class will be removed.
 *
 * @hide
 */
public final class HeadsetBase {
    private static final String TAG = "Bluetooth HeadsetBase";
    private static final boolean DBG = false;

    public static final int RFCOMM_DISCONNECTED = 1;

    public static final int DIRECTION_INCOMING = 1;
    public static final int DIRECTION_OUTGOING = 2;

    private static int sAtInputCount = 0;  /* TODO: Consider not using a static variable */

    private final BluetoothAdapter mAdapter;
    private final BluetoothDevice mRemoteDevice;
    private final String mAddress;  // for native code
    private final int mRfcommChannel;
    private int mNativeData;
    private Thread mEventThread;
    private volatile boolean mEventThreadInterrupted;
    private Handler mEventThreadHandler;
    private int mTimeoutRemainingMs;
    private final int mDirection;
    private final long mConnectTimestamp;

    protected AtParser mAtParser;

    private WakeLock mWakeLock;  // held while processing an AT command

    private native static void classInitNative();
    static {
        classInitNative();
    }

    protected void finalize() throws Throwable {
        try {
            cleanupNativeDataNative();
            releaseWakeLock();
        } finally {
            super.finalize();
        }
    }

    private native void cleanupNativeDataNative();

    public HeadsetBase(PowerManager pm, BluetoothAdapter adapter, BluetoothDevice device,
            int rfcommChannel) {
        mDirection = DIRECTION_OUTGOING;
        mConnectTimestamp = System.currentTimeMillis();
        mAdapter = adapter;
        mRemoteDevice = device;
        mAddress = device.getAddress();
        mRfcommChannel = rfcommChannel;
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetBase");
        mWakeLock.setReferenceCounted(false);
        initializeAtParser();
        // Must be called after this.mAddress is set.
        initializeNativeDataNative(-1);
    }

    /* Create from an already exisiting rfcomm connection */
    public HeadsetBase(PowerManager pm, BluetoothAdapter adapter, BluetoothDevice device,
            int socketFd, int rfcommChannel, Handler handler) {
        mDirection = DIRECTION_INCOMING;
        mConnectTimestamp = System.currentTimeMillis();
        mAdapter = adapter;
        mRemoteDevice = device;
        mAddress = device.getAddress();
        mRfcommChannel = rfcommChannel;
        mEventThreadHandler = handler;
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetBase");
        mWakeLock.setReferenceCounted(false);
        initializeAtParser();
        // Must be called after this.mAddress is set.
        initializeNativeDataNative(socketFd);
    }

    private native void initializeNativeDataNative(int socketFd);

    /* Process an incoming AT command line
     */
    protected void handleInput(String input) {
        acquireWakeLock();
        long timestamp;

        synchronized(HeadsetBase.class) {
            if (sAtInputCount == Integer.MAX_VALUE) {
                sAtInputCount = 0;
            } else {
                sAtInputCount++;
            }
        }

        if (DBG) timestamp = System.currentTimeMillis();
        AtCommandResult result = mAtParser.process(input);
        if (DBG) Log.d(TAG, "Processing " + input + " took " +
                       (System.currentTimeMillis() - timestamp) + " ms");

        if (result.getResultCode() == AtCommandResult.ERROR) {
            Log.i(TAG, "Error pocessing <" + input + ">");
        }

        sendURC(result.toString());

        releaseWakeLock();
    }

    /**
     * Register AT commands that are common to all Headset / Handsets. This
     * function is called by the HeadsetBase constructor.
     */
    protected void initializeAtParser() {
        mAtParser = new AtParser();
        //TODO(): Get rid of this as there are no parsers registered. But because of dependencies,
        //it needs to be done as part of refactoring HeadsetBase and BluetoothHandsfree
    }

    public AtParser getAtParser() {
        return mAtParser;
    }

    public void startEventThread() {
        mEventThread =
            new Thread("HeadsetBase Event Thread") {
                public void run() {
                    int last_read_error;
                    while (!mEventThreadInterrupted) {
                        String input = readNative(500);
                        if (input != null) {
                            handleInput(input);
                        }
                        else {
                            last_read_error = getLastReadStatusNative();
                            if (last_read_error != 0) {
                                Log.i(TAG, "headset read error " + last_read_error);
                                if (mEventThreadHandler != null) {
                                    mEventThreadHandler.obtainMessage(RFCOMM_DISCONNECTED)
                                            .sendToTarget();
                                }
                                disconnectNative();
                                break;
                            }
                        }
                    }
                }
            };
        mEventThreadInterrupted = false;
        mEventThread.start();
    }



    private native String readNative(int timeout_ms);
    private native int getLastReadStatusNative();

    private void stopEventThread() {
        mEventThreadInterrupted = true;
        mEventThread.interrupt();
        try {
            mEventThread.join();
        } catch (java.lang.InterruptedException e) {
            // FIXME: handle this,
        }
        mEventThread = null;
    }

    public boolean connect(Handler handler) {
        if (mEventThread == null) {
            if (!connectNative()) return false;
            mEventThreadHandler = handler;
        }
        return true;
    }
    private native boolean connectNative();

    /*
     * Returns true when either the asynchronous connect is in progress, or
     * the connect is complete.  Call waitForAsyncConnect() to find out whether
     * the connect is actually complete, or disconnect() to cancel.
     */

    public boolean connectAsync() {
        int ret = connectAsyncNative();
        return (ret == 0) ? true : false;
    }
    private native int connectAsyncNative();

    public int getRemainingAsyncConnectWaitingTimeMs() {
        return mTimeoutRemainingMs;
    }

    /*
     * Returns 1 when an async connect is complete, 0 on timeout, and -1 on
     * error.  On error, handler will be called, and you need to re-initiate
     * the async connect.
     */
    public int waitForAsyncConnect(int timeout_ms, Handler handler) {
        int res = waitForAsyncConnectNative(timeout_ms);
        if (res > 0) {
            mEventThreadHandler = handler;
        }
        return res;
    }
    private native int waitForAsyncConnectNative(int timeout_ms);

    public void disconnect() {
        if (mEventThread != null) {
            stopEventThread();
        }
        disconnectNative();
    }
    private native void disconnectNative();


    /*
     * Note that if a remote side disconnects, this method will still return
     * true until disconnect() is called.  You know when a remote side
     * disconnects because you will receive the intent
     * IBluetoothService.REMOTE_DEVICE_DISCONNECTED_ACTION.  If, when you get
     * this intent, method isConnected() returns true, you know that the
     * disconnect was initiated by the remote device.
     */

    public boolean isConnected() {
        return mEventThread != null;
    }

    public BluetoothDevice getRemoteDevice() {
        return mRemoteDevice;
    }

    public int getDirection() {
        return mDirection;
    }

    public long getConnectTimestamp() {
        return mConnectTimestamp;
    }

    public synchronized boolean sendURC(String urc) {
        if (urc.length() > 0) {
            boolean ret = sendURCNative(urc);
            return ret;
        }
        return true;
    }
    private native boolean sendURCNative(String urc);

    private synchronized void acquireWakeLock() {
        if (!mWakeLock.isHeld()) {
            mWakeLock.acquire();
        }
    }

    private synchronized void releaseWakeLock() {
        if (mWakeLock.isHeld()) {
            mWakeLock.release();
        }
    }

    public static int getAtInputCount() {
        return sAtInputCount;
    }

    private static void log(String msg) {
        Log.d(TAG, msg);
    }
}
