package temple.multiplayer.net.bluetooth.service;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;

import temple.multiplayer.net.bluetooth.device.BluetoothCommunicationThread;
import temple.multiplayer.net.common.service.ServiceMessageKeys;
import temple.multiplayer.net.common.service.ServiceMessageType;

import java.io.IOException;
import java.util.UUID;

/**
 * Created by stephan on 22-5-2015.
 */
public class BluetoothClientService extends AbstractBluetoothService {
    private static final String TAG = BluetoothClientService.class.getSimpleName();

    private ConnectThread _connectThread;
    private BluetoothCommunicationThread _communicationThread;


    /**
     * Constructor. Prepares a new BluetoothChat session.
     *
     * @param handler A Handler to send messages back to the UI Activity
     */
    public BluetoothClientService(Handler handler) {
        super(handler);
    }

    @Override
    public synchronized void start() {
        super.start();

        cancelConnectThread();
    }

    @Override
    public synchronized void stop() {
        if (_debug) Log.d(TAG, "stop");
        super.stop();

        cancelConnectThread();
        cancelCommunicationThread(_communicationThread);
    }

    public boolean acceptServerUUID (UUID uuid) {
        return uuid.equals(_insecureUuid) || uuid.equals(_secureUuid);
    }

    @Override
    public void write(String deviceAddress, byte[] out) {
        if (_debug) Log.d(TAG, "write: " + out.length + " bytes to write");

        // Create temporary object
        BluetoothCommunicationThread communicationThread;

        // Synchronize a copy of the communcation thread
        synchronized (this) {
            if (_state != STATE_CONNECTED) return;
            communicationThread = _communicationThread;
        }
        // Perform the write unsynchronized
        communicationThread.write(out);
    }

    /**
     * Start the ConnectThread to initiate a connection to a remote device.
     *
     * @param device The BluetoothDevice to connect
     * @param secure Socket Security type - Secure (true) , Insecure (false)
     */
    public synchronized void connect(BluetoothDevice device, boolean secure) {
        if (_debug) Log.d(TAG, "connect to: " + device.getName());

        // Cancel any thread attempting to make a connection
        if (_state == STATE_CONNECTING) {
            cancelConnectThread();
        }
        cancelCommunicationThread(_communicationThread);

        // Start the thread to connect with the given device
        _connectThread = new ConnectThread(device, secure);
        _connectThread.start();

        setState(STATE_CONNECTING);
    }

    private void cancelConnectThread() {
        if (_connectThread != null) {
            _connectThread.cancel();
        }
        _connectThread = null;
    }

    @Override
    protected void cancelCommunicationThread(BluetoothCommunicationThread communicationThread) {
        super.cancelCommunicationThread(communicationThread);

        _communicationThread = null;
    }

    /**
     * Start the CommunicationThread to begin managing a Bluetooth connection
     *
     * @param socket The BluetoothSocket on which the connection was made
     * @param device The BluetoothDevice that has been connected
     */
    private synchronized void connected(BluetoothSocket socket, BluetoothDevice device, final String socketType) {
        if (_debug) Log.d(TAG, "connected: ");

        cancelConnectThread();
        cancelCommunicationThread(_communicationThread);

        _communicationThread = createCommunicationThread(socket, device);

        // Send the name of the connected device back to the UI Activity
        Bundle bundle = new Bundle();
        bundle.putString(ServiceMessageKeys.DEVICE_NAME, device.getName());
        bundle.putString(ServiceMessageKeys.DEVICE_ADDRESS, device.getAddress());
        sendMessage(ServiceMessageType.MESSAGE_DEVICE_CONNECTED, bundle);

        setState(STATE_CONNECTED);
    }

    /**
     * Indicate that the connection attempt failed and notify the UI Activity.
     */
    private void connectionFailed(BluetoothDevice device) {
        // Send a failure message back to the Activity
        Bundle bundle = new Bundle();
        bundle.putString(ServiceMessageKeys.DEVICE_NAME, device.getName());
        bundle.putString(ServiceMessageKeys.DEVICE_ADDRESS, device.getAddress());
        sendMessage(ServiceMessageType.MESSAGE_CONNECT_FAILED);

        if (_debug) Log.d(TAG, "connectionFailed: restarting");

        setState(STATE_IDLE);

        // Start the service over to restart listening mode
        start();
    }

    @Override
    protected void connectionLost(BluetoothDevice device) {
        super.connectionLost(device);

        if (_debug) Log.d(TAG, "connectionLost: ");

        stop();
    }

    /**
     * This thread runs while attempting to make an outgoing connection
     * with a device. It runs straight through; the connection either
     * succeeds or fails.
     */
    private class ConnectThread extends Thread {
        private final BluetoothSocket _socket;
        private final BluetoothDevice _device;
        private String _socketType;

        public ConnectThread(BluetoothDevice device, boolean secure) {
            _device = device;
            _socketType = secure ? "Secure" : "Insecure";

            BluetoothSocket socket = null;
            // Get a BluetoothSocket for a connection with the
            // given BluetoothDevice
            try {
                if (secure) {
                    socket = device.createRfcommSocketToServiceRecord(_secureUuid);
                } else {
                    socket = device.createInsecureRfcommSocketToServiceRecord(_insecureUuid);
                }
            } catch (IOException e) {
                Log.e(TAG, "ConnectThread: create failed");
                e.printStackTrace();
            }
            _socket = socket;
        }

        public void run() {
            Log.i(TAG, "run: _connectThread SocketType:" + _socketType);

            setName("ConnectThread " + _socketType);

            // Always cancel discovery because it will slow down a connection
            _adapter.cancelDiscovery();

            // Make a connection to the BluetoothSocket
            try {
                // This is a blocking call and will only return on a
                // successful connection or an exception
                _socket.connect();
            } catch (IOException e) {
                Log.e(TAG, "run: failed to connect");
                e.printStackTrace();

                // Close the socket
                try {
                    _socket.close();
                } catch (IOException e2) {
                    Log.e(TAG, "run: unable to close socket ");
                    e.printStackTrace();
                }
                connectionFailed(_device);
                return;
            }

            if (_debug) Log.d(TAG, "run: socket connected");

            // Clear the ConnectThread because we're done
            synchronized (BluetoothClientService.this) {
                _connectThread = null;
            }

            // Start the connected thread
            connected(_socket, _device, _socketType);
        }

        public void cancel() {
            try {
                _socket.close();
            } catch (IOException e) {
                Log.e(TAG, "cancel: close failed");
                e.printStackTrace();
            }
        }
    }
}