package com.devpaul.bluetoothutillib.utils;

import android.app.NotificationManager;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;

import com.devpaul.bluetoothutillib.handlers.BluetoothHandler;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.UUID;

/**
 * Created by Paul T.
 *
 * Bluetooth service class so you can connect to bluetooth devices persistently in an app
 * background.
 */
public class BluetoothService extends Service {

    /**
     * Callback for the service.
     */
    public interface BluetoothServiceCallback {
        public void onDeviceConnected(BluetoothDevice device);
    }

    /**
     * Local binder
     */
    private LocalBinder mLocalBinder = new LocalBinder();

    /**
     * Bluetooth device.
     */
    private BluetoothDevice device;

    /**
     * BluetoothAdapter instance for the service.
     */
    private BluetoothAdapter adapter;

    /**
     * The connect device thread for connecting to a device.
     */
    private ConnectDeviceThread connectDeviceThread;

    /**
     * Connected thread for when handling when the device is connected.
     */
    private ConnectedThread connectedThread;

    /**
     * Bluetooth socket holder for the created socket.
     */
    private BluetoothSocket bluetoothSocket;

    /**
     * Instance of a callback.
     */
    private BluetoothServiceCallback callback;

    /**
     * Notification Manager for if the service is sticky.
     */
    private NotificationManager notificationManager;

    /**
     * Handler for bluetooth.
     */
    private BluetoothHandler bluetoothHandler;

    /**
     * Stream type.
     */
    private BluetoothUtility.InputStreamType streamType;

    /**
     * Command for connecting to a device.
     */
    public static final String CONNECT_BLUETOOTH_DEVICE = "startListeningToSocket";

    /**
     * Address of device.
     */
    public static final String DEVICE_ADDRESS_EXTRA = "deviceAddressExtra";

    /**
     * Notification ID.
     */
    public static final int FOREGROUND_NOTIFICATION_ID = 1;

    /**
     * Stop service command.
     */
    public static final String STOP_SERVICE_ACTION = "stopBluetoothServiceAction";

    /**
     * {@code UUID} for a normal device connection.
     */
    private static final UUID NORMAL_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onRebind(Intent intent) {
        super.onRebind(intent);
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }

    public class LocalBinder extends Binder {
        public BluetoothService getService() {
            return BluetoothService.this;
        }
    }

    /**
     * Write value to the connected device.
     * @param message
     */
    public void write(String message) {
        if(connectedThread != null) {
            if(connectedThread.isAlive()) {
                connectedThread.write(message);
            }
        }

    }

    /**
     * Used to connect a device a generic socket.
     */
    private class ConnectDeviceThread extends Thread {

        private final BluetoothSocket mmSocket;
        private final BluetoothDevice mmDevice;

        public ConnectDeviceThread(BluetoothDevice device) {
            // Use a temporary object that is later assigned to mmSocket,
            // because mmSocket is final

            BluetoothSocket tmp = null;
            mmDevice = device;

            // Get a BluetoothSocket to connect with the given BluetoothDevice
            try {
                // MY_UUID is the app's UUID string, also used by the server code
                tmp = device.createRfcommSocketToServiceRecord(NORMAL_UUID);
            } catch (IOException e) {

            }
            mmSocket = tmp;
        }

        public void run() {
            // Cancel discovery because it will slow down the connection
            adapter.cancelDiscovery();

            try {
                // Connect the device through the socket. This will block
                // until it succeeds or throws an exception
                mmSocket.connect();
            } catch (IOException connectException) {
                // Unable to connect; close the socket and get out
                try {
                    mmSocket.close();
                } catch (IOException closeException) { }
                return;
            }
            if(callback != null) {
                callback.onDeviceConnected(mmDevice);
            }
            // Do work to manage the connection (in a separate thread)
            manageConnectedSocket(mmSocket);
        }

        /** Will cancel an in-progress connection, and close the socket */
        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) { }
        }
    }

    /**
     * Helper method for managing a connected socket.
     * @param mmSocket
     */
    private void manageConnectedSocket(BluetoothSocket mmSocket) {
        connectedThread = new ConnectedThread(mmSocket);
        connectedThread.start();
    }

    /**
     * Thread for when you're connected.
     */
    private class ConnectedThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final InputStream mInputStream;
        private final OutputStream mOutputStream;

        public ConnectedThread(BluetoothSocket socket) {
            mmSocket = socket;
            bluetoothSocket = socket;
            InputStream tmpIn = null;
            OutputStream tmpOut = null;
            // Get the input and output streams, using temp objects because
            // member streams are final
            try {
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException e) {
//                Log.d("ConnectedThread", e.getMessage());
            }

            mInputStream = tmpIn;
            mOutputStream = tmpOut;
        }

        public void run() {
            byte[] buffer;  // buffer store for the stream
            int bytes; // bytes returned from read()
            BufferedReader reader;

            if(streamType == BluetoothUtility.InputStreamType.NORMAL) {
                // Keep listening to the InputStream until an exception occurs
                while (true) {
                    try {
                        bytes = mInputStream.available();
                        if(bytes > 0) {
                            buffer = new byte[bytes];
                            // Read from the InputStream
                            bytes = mInputStream.read(buffer);
                            // Send the obtained bytes to the UI activity
                            bluetoothHandler.obtainMessage(BluetoothHandler.MESSAGE_READ, bytes, -1, buffer)
                                    .sendToTarget();
                        }
                    } catch (IOException e) {
                        break;
                    }
                }
                //Buffered reader.
            } else {
                reader = new BufferedReader(new InputStreamReader(mInputStream));
                // Keep listening to the InputStream until an exception occurs
                while (true) {
                    try {
                        if(reader.ready()) {
                            String message = reader.readLine();
                            bluetoothHandler.obtainMessage(BluetoothHandler.MESSAGE_READ, -1, -1, message)
                                    .sendToTarget();
                        }
//                        bytes = mInputStream.available();
//                        if(bytes > 0) {
//                            buffer = new byte[bytes];
//                            // Read from the InputStream
//                            bytes = mInputStream.read(buffer);
//                            // Send the obtained bytes to the UI activity
//                            bluetoothHandler.obtainMessage(BluetoothHandler.MESSAGE_READ, bytes, -1, buffer)
//                                    .sendToTarget();
//                        }
                    } catch (IOException e) {
                        break;
                    }
                }
            }


        }

        /**
         * Called to send a string across the bluetooth socket.
         * @param string the string to send.
         */
        public void write(String string) {
            if(mOutputStream != null) {
                try {
//                    Log.d("ConnectedThread", "Writing data: " + string);
                    mOutputStream.write(string.getBytes());
                } catch (IOException e) {
//                    Log.d("ConnectedThread",e.getMessage());
                }
            }
        }

        /**
         * Called to send bytes across the bluetooth socket.
         * @param bytes the bytes to send.
         */
        public void write(byte[] bytes) {
            if(mOutputStream != null) {
                try {
                    mOutputStream.write(bytes);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        public void write(int i) {
            if(mOutputStream != null) {

                try {
                    mOutputStream.write(i);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        /* Call this from the main activity to shutdown the connection */
        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) { }
        }
    }
}