//To run chip you need the black/red usb cables with the pickit 3
//Connect to a lab computer with USB in order to power it

package com.tonydicola.bletest.app;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAdapter.LeScanCallback;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import com.parse.ParseObject;
import com.parse.ParseUser;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import at.markushi.ui.CircleButton;

public class MainActivity extends Activity {

    // UUIDs for UAT service and associated characteristics.
    public static UUID UART_UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
    public static UUID TX_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
    public static UUID RX_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");
    // UUID for the BTLE client characteristic which is necessary for notifications.
    public static UUID CLIENT_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");

    // UI elements
    private TextView messages;
    private EditText input;
    private Spinner afterSpinner;
    private Spinner beforeSpinner;
    private TextView connStatus;
    private TextView readingVal;
    private CircleButton connColor;
    private CircleButton connColor2;

    // BTLE state
    private BluetoothAdapter adapter;
    private BluetoothGatt gatt;
    private BluetoothGattCharacteristic tx;
    private BluetoothGattCharacteristic rx;

    //Global Variables
    private String userString, theReading = "0", beforeSpinnerText, afterSpinnerText;
    private String timeStamp = "0";
    private String timeStamp2 = "0";
    private int sendCount = 1;
    private String previousValue = "zzzzzzzz";
    private List<String> readingsList = new ArrayList<String>(); //List to store all readings with their timestamps
    private List<String> timeList = new ArrayList<String>(); //List to store all readings with their timestamps

    //Parse Globals
    ParseObject testObject = new ParseObject("Readings");
    ParseUser currentUser = ParseUser.getCurrentUser();

    // Main BTLE device callback where much of the logic occurs.
    private BluetoothGattCallback callback = new BluetoothGattCallback() {
        // Called whenever the device connection state changes, i.e. from disconnected to connected.
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            System.out.println("Connection State Changed");
            if (newState == BluetoothGatt.STATE_CONNECTED) {
                //writeLine("Connected!");
                System.out.println("Connected!");
//                sendCount = 1;
                //connStatus.setText("Connected");
                setConnStatus("Connected!");

                // Discover services.
                if (!gatt.discoverServices()) {
                    //writeLine("Failed to start discovering services!");
                    System.out.println("Failed to start discovering services!");
                }
            }
            else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
                //writeLine("Disconnected!");
                System.out.println("Disconnected!");
                //connStatus.setText("Disconnected");
                setConnStatus("Disconnected!");
            }
            else {
                //writeLine("Connection state changed.  New state: " + newState);
                System.out.println("Connection state changed.  New state: " + newState);
            }
        }

        // Called when services have been discovered on the remote device.
        // It seems to be necessary to wait for this discovery to occur before
        // manipulating any services or characteristics.
        @Override
        public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
            super.onServicesDiscovered(gatt, status);
            System.out.println("Services Discovered");
            if (status == BluetoothGatt.GATT_SUCCESS) {
                //writeLine("Service discovery completed!");
                System.out.println("Service discovery completed!");
            }
            else {
                //writeLine("Service discovery failed with status: " + status);
                System.out.println("Service discovery failed with status: " + status);
            }
            // Save reference to each characteristic.
            tx = gatt.getService(UART_UUID).getCharacteristic(TX_UUID);
            rx = gatt.getService(UART_UUID).getCharacteristic(RX_UUID);

            // Setup notifications on RX characteristic changes (i.e. data received).
            // First call setCharacteristicNotification to enable notification.
            if (!gatt.setCharacteristicNotification(rx, true)) {
                //writeLine("Couldn't set notifications for RX characteristic!");
                System.out.println("Couldn't set notifications for RX characteristic!");
            }
            // Next update the RX characteristic's client descriptor to enable notifications.
            if (rx.getDescriptor(CLIENT_UUID) != null) {
                BluetoothGattDescriptor desc = rx.getDescriptor(CLIENT_UUID);
                desc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                if (!gatt.writeDescriptor(desc)) {
                    //writeLine("Couldn't write RX client descriptor value!");
                    System.out.println("Couldn't write RX client descriptor value!");
                }
            }
            else {
                //writeLine("Couldn't get RX client descriptor!");
                System.out.println("Couldn't get RX client descriptor!");
            }
        }

        // Called when a remote characteristic changes (like the RX characteristic).
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
//            System.out.println("Characteristic Changed");
//            writeLine("Received: " + characteristic.getStringValue(0));
//            System.out.println("Received: " + characteristic.getStringValue(0));
            sendTime();
            String value = characteristic.getStringValue(0);
            if (!value.equals(previousValue)){
//                System.out.println("Received: " + characteristic.getStringValue(0));
                if (sendCount == 1){
                    theReading = characteristic.getStringValue(0);
                    //writeLine(theReading);
                    System.out.println("Reading: " + theReading);
                    setReadingVal(theReading);
                    readingsList.add(theReading);
                    sendCount = 2;
                }
                else if (sendCount == 2) {
                    timeStamp = characteristic.getStringValue(0);
                    sendCount = 3;
                }
                else if (sendCount == 3) {
                    timeStamp2 = characteristic.getStringValue(0);
                    System.out.println("Timestamp: " + timeStamp+timeStamp2);
                    timeList.add(timeStamp+timeStamp2);
                    sendCount = 1;
                }
            }


//                if (sendCount == 1){
//                    timeStamp = characteristic.getStringValue(0);
//                    System.out.println("Timestamp: " + timeStamp);
//                    timeList.add(timeStamp);
//                    sendCount = 2;
//                }
//                else if (sendCount == 2) {
//                    theReading = characteristic.getStringValue(0);
//                    //writeLine(theReading);
//                    System.out.println("Reading: " + theReading);
//                    setReadingVal(theReading);
//                    readingsList.add(theReading);
//
//                    sendCount = 1;
//                }

            previousValue = value;
        }
    };

    //Upload reading to parse
    public void uploadReading(View view){
        //Get the value of the spinners
        beforeSpinnerText = beforeSpinner.getSelectedItem().toString();
        afterSpinnerText = afterSpinner.getSelectedItem().toString();

        System.out.println("Reading List Size: " + readingsList.size());
        for (int i = 0; i < readingsList.size(); i++) {
            System.out.println("Uploading Reading #: " + i);
            try {
                testObject.put("username", userString);
                testObject.put("reading", Integer.parseInt(readingsList.get(i)));
                testObject.put("timestamp", timeList.get(i));
                testObject.put("before", beforeSpinnerText);
                testObject.put("after", afterSpinnerText);
                testObject.saveInBackground();
                System.out.println("Uploaded Reading #: " + i);
                Toast.makeText(getApplicationContext(),
                        "Data Uploaded", Toast.LENGTH_LONG)
                        .show();
            } catch (Exception e) {
                System.out.println("Failed Reading #: " + i);
                Toast.makeText(getApplicationContext(),
                        "Data Upload Failed", Toast.LENGTH_LONG)
                        .show();
            }
            testObject = new ParseObject("Readings");
        }
        readingsList.clear();
        timeList.clear();
    }

    // BTLE device scanning callback.
    private LeScanCallback scanCallback = new LeScanCallback() {
        // Called when a device is found.
        @Override
        public void onLeScan(final BluetoothDevice bluetoothDevice, int i, byte[] bytes) {
            //writeLine("Found device: " + bluetoothDevice.getAddress());
            // Check if the device has the UART service.
            if (parseUUIDs(bytes).contains(UART_UUID)) {
                // Found a device, stop the scan.
                adapter.stopLeScan(scanCallback);
                //writeLine("Found UART service!");
                // Connect to the device.
                // Control flow will now go to the callback functions when BTLE events occur.
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        gatt = bluetoothDevice.connectGatt(getApplicationContext(), false, callback);
                    }
                });
            }
        }
    };

    public void attemptConnect(View view){
        //writeLine("Scanning for devices...");
        adapter.startLeScan(scanCallback);
        sendTime();
    }

    // Send the time
    public void sendTime() {
        long unixTime = System.currentTimeMillis() / 1000L;

        //String message = Long.toString(unixTime);
        String message = String.valueOf(unixTime);
        System.out.println(message);

        if (tx == null || message == null || message.isEmpty()) {
            // Do nothing if there is no device or message to send.
            return;
        }
        // Update TX characteristic value.  Note the setValue overload that takes a byte array must be used.
        tx.setValue(message.getBytes(Charset.forName("UTF-8")));
        if (gatt.writeCharacteristic(tx)) {
            System.out.println("Sent: " + message);
        }
        else {
            System.out.println("Couldn't write TX characteristic!");
        }
    }

    // OnCreate, called once to initialize the activity.
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Grab references to UI elements.
        adapter = BluetoothAdapter.getDefaultAdapter();
        connStatus = (TextView) findViewById(R.id.connStatus);
        connColor = (CircleButton) findViewById(R.id.connColor);
        connColor2 = (CircleButton) findViewById(R.id.connColor2);
        readingVal = (TextView) findViewById(R.id.readingVal);

        //Acquire username
        userString = currentUser.getUsername().toString();

        //Set up the spinners
        afterSpinner = (Spinner) findViewById(R.id.afterSpinner);
        // Create an ArrayAdapter using the string array and a default spinner layout
        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
                R.array.activities_array, android.R.layout.simple_spinner_item);
        // Specify the layout to use when the list of choices appears
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        // Apply the adapter to the spinner
        afterSpinner.setAdapter(adapter);

        beforeSpinner = (Spinner) findViewById(R.id.beforeSpinner);
        // Create an ArrayAdapter using the string array and a default spinner layout
        ArrayAdapter<CharSequence> adapter2 = ArrayAdapter.createFromResource(this,
                R.array.activities_array, android.R.layout.simple_spinner_item);
        // Specify the layout to use when the list of choices appears
        adapter2.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        // Apply the adapter to the spinner
        beforeSpinner.setAdapter(adapter2);
    }

    // OnResume, called right before UI is displayed.  Start the BTLE connection.
    @Override
    protected void onResume() {
        super.onResume();
        // Scan for all BTLE devices.
        // The first one with the UART service will be chosen--see the code in the scanCallback.
        //writeLine("Scanning for devices...");
        //adapter.startLeScan(scanCallback);
    }

    // OnStop, called right before the activity loses foreground focus.  Close the BTLE connection.
    @Override
    protected void onStop() {
        super.onStop();
        if (gatt != null) {
            // For better reliability be careful to disconnect and close the connection.
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    gatt.disconnect();
                    gatt.close();
                    gatt = null;
                    tx = null;
                    rx = null;
                }
            });
        }
    }

    // Handler for mouse click on the send button.
    public void sendClick(View view) {
        String message = input.getText().toString();
        if (tx == null || message == null || message.isEmpty()) {
            // Do nothing if there is no device or message to send.
            return;
        }
        // Update TX characteristic value.  Note the setValue overload that takes a byte array must be used.
        tx.setValue(message.getBytes(Charset.forName("UTF-8")));
        if (gatt.writeCharacteristic(tx)) {
            //writeLine("Sent: " + message);
            System.out.println("Sent: " + message);
        }
        else {
            //writeLine("Couldn't write TX characteristic!");
            System.out.println("Couldn't write TX characteristic!");
        }
    }

    // Write some text to the messages text view.
    // Care is taken to do this on the main UI thread so writeLine can be called
    // from any thread (like the BTLE callback).
    private void writeLine(final CharSequence text) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                messages.append(text);
                messages.append("\n");
            }
        });
    }

    // Write some text to the messages text view.
    // Care is taken to do this on the main UI thread so writeLine can be called
    // from any thread (like the BTLE callback).
    private void setReadingVal(final CharSequence text) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                readingVal.setText(text + " mg/dl");
            }
        });
    }

    // Write some text to the messages text view.
    // Care is taken to do this on the main UI thread so writeLine can be called
    // from any thread (like the BTLE callback).
    private void setConnStatus(final CharSequence text) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                connStatus.setText(text);
                if (text == "Connected!"){
                    connColor.setVisibility(View.VISIBLE);
                    connColor2.setVisibility(View.INVISIBLE);
                }
                else{
                    connColor.setVisibility(View.INVISIBLE);
                    connColor2.setVisibility(View.VISIBLE);
                }
            }
        }
        );
    }

    // Filtering by custom UUID is broken in Android 4.3 and 4.4, see:
    //   http://stackoverflow.com/questions/18019161/startlescan-with-128-bit-uuids-doesnt-work-on-native-android-ble-implementation?noredirect=1#comment27879874_18019161
    // This is a workaround function from the SO thread to manually parse advertisement data.
    private List<UUID> parseUUIDs(final byte[] advertisedData) {
        List<UUID> uuids = new ArrayList<UUID>();

        int offset = 0;
        while (offset < (advertisedData.length - 2)) {
            int len = advertisedData[offset++];
            if (len == 0)
                break;

            int type = advertisedData[offset++];
            switch (type) {
                case 0x02: // Partial list of 16-bit UUIDs
                case 0x03: // Complete list of 16-bit UUIDs
                    while (len > 1) {
                        int uuid16 = advertisedData[offset++];
                        uuid16 += (advertisedData[offset++] << 8);
                        len -= 2;
                        uuids.add(UUID.fromString(String.format("%08x-0000-1000-8000-00805f9b34fb", uuid16)));
                    }
                    break;
                case 0x06:// Partial list of 128-bit UUIDs
                case 0x07:// Complete list of 128-bit UUIDs
                    // Loop through the advertised 128-bit UUID's.
                    while (len >= 16) {
                        try {
                            // Wrap the advertised bits and order them.
                            ByteBuffer buffer = ByteBuffer.wrap(advertisedData, offset++, 16).order(ByteOrder.LITTLE_ENDIAN);
                            long mostSignificantBit = buffer.getLong();
                            long leastSignificantBit = buffer.getLong();
                            uuids.add(new UUID(leastSignificantBit,
                                    mostSignificantBit));
                        } catch (IndexOutOfBoundsException e) {
                            // Defensive programming.
                            //Log.e(LOG_TAG, e.toString());
                            continue;
                        } finally {
                            // Move the offset to read the next uuid.
                            offset += 15;
                            len -= 16;
                        }
                    }
                    break;
                default:
                    offset += (len - 1);
                    break;
            }
        }
        return uuids;
    }

    // Boilerplate code from the activity creation:

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu items for use in the action bar
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.main_activity_actions, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        // Handle presses on the action bar items
        switch (item.getItemId()) {
            case R.id.action_chart:
                //String urlString="http://csevm01.crc.nd.edu:8501/hw6/ee/charts.html" + "?user=" + currentUser.getUsername().toString();
                String urlString="http://www3.nd.edu/~ssaputra/charts.html" + "?user=" + currentUser.getUsername().toString();
                Intent intent=new Intent(Intent.ACTION_VIEW, Uri.parse(urlString));
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.setPackage("com.android.chrome");
                try {
                    startActivity(intent);
                } catch (ActivityNotFoundException ex) {
                    // Chrome browser presumably not installed so allow user to choose instead
                    intent.setPackage(null);
                    startActivity(intent);
                }
                return true;
            case R.id.action_settings:
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

}
