package com.eveningoutpost.dexdrip;

import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.activeandroid.query.Select;
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.PacketBuilder;
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.ReadData;
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.ReadDataShare;
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.CalRecord;
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.EGVRecord;
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.SensorRecord;
import com.eveningoutpost.dexdrip.Models.ActiveBluetoothDevice;
import com.eveningoutpost.dexdrip.Models.BgReading;
import com.eveningoutpost.dexdrip.Models.Calibration;
import com.eveningoutpost.dexdrip.UtilityModels.DexShareAttributes;
import com.eveningoutpost.dexdrip.UtilityModels.ForegroundServiceStarter;
import com.eveningoutpost.dexdrip.UtilityModels.HM10Attributes;

import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import rx.Observable;
import rx.functions.Action1;


public class ShareTest extends Activity {
    private final static String TAG = ShareTest.class.getSimpleName();
    Button button;
    Button closeButton;
    Button readButton;
    Button bondButton;
    TextView details;

    private String mDeviceName;
    private String mDeviceAddress;
    private boolean is_connected = false;
    private boolean reconnecting = false;
    SharedPreferences prefs;

    private BluetoothManager mBluetoothManager;
    private BluetoothAdapter mBluetoothAdapter;
    private String mBluetoothDeviceAddress;
    private BluetoothGatt mBluetoothGatt;
    private ForegroundServiceStarter foregroundServiceStarter;
    private int mConnectionState = STATE_DISCONNECTED;
    private BluetoothDevice device;
    int mStartMode;
    private Context mContext = null;

    private static final int STATE_DISCONNECTED = BluetoothProfile.STATE_DISCONNECTED;
    private static final int STATE_DISCONNECTING = BluetoothProfile.STATE_DISCONNECTING;
    private static final int STATE_CONNECTING = BluetoothProfile.STATE_CONNECTING;
    private static final int STATE_CONNECTED = BluetoothProfile.STATE_CONNECTED;

    private BluetoothGattService mShareService;
    private BluetoothGattCharacteristic mAuthenticationCharacteristic;
    private BluetoothGattCharacteristic mSendDataCharacteristic;
    private BluetoothGattCharacteristic mReceiveDataCharacteristic;
    private BluetoothGattCharacteristic mHeartBeatCharacteristic;
    private BluetoothGattCharacteristic mCommandCharacteristic;
    private BluetoothGattCharacteristic mResponseCharacteristic;

    //Gatt Tasks
    public final int GATT_NOTHING = 0;
    public final int GATT_SETUP = 1;
    public final int GATT_WRITING_COMMANDS = 2;
    public final int GATT_READING_RESPONSE = 3;
    public int successfulWrites;

    //RXJAVA FUN
    Action1<byte[]> mDataResponseListener;

    public ReadDataShare mReadDataShare;

    public int currentGattTask;
    public int step;

    public List<byte[]> writePackets;
    public int recordType;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_share_test);
        button = (Button) findViewById(R.id.connect);
        closeButton = (Button) findViewById(R.id.closeConnect);
        bondButton = (Button) findViewById(R.id.bond);
        readButton = (Button) findViewById(R.id.read);
        details = (TextView) findViewById(R.id.connection_details);
        addListenerOnButton();
        addListenerOnCloseButton();
        IntentFilter intent = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
        registerReceiver(mPairReceiver, intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        close();
        Log.w(TAG, "CLOSING CONNECTION");
    }

    public void addListenerOnButton() {
        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                attemptConnection();
            }
        });
        readButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                attemptRead();
            }
        });
        bondButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                bond(mBluetoothGatt);
            }
        });
    }

    public void addListenerOnCloseButton() {
        closeButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                close();
                details.setText("");
            }
        });
    }

    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                mBluetoothGatt = gatt;
                mConnectionState = STATE_CONNECTED;
                ActiveBluetoothDevice.connected();
                Log.w(TAG, "Connected to GATT server.");
                Log.w(TAG, "Connection state: Bonded - " + device.getBondState());

                if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
                    currentGattTask = GATT_SETUP;
                    mBluetoothGatt.discoverServices();

                } else {
                    device.setPin("000000".getBytes());
                    device.createBond();
                }
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                mConnectionState = STATE_DISCONNECTED;
                ActiveBluetoothDevice.disconnected();
                Log.w(TAG, "Disconnected from GATT server.");
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.w(TAG, "Services Discovered: " + status);
                authenticateConnection(gatt);

            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.w(TAG, "Characteristic Read");
                byte[] value = characteristic.getValue();
                if(value != null) {
                    Log.w(TAG, "VALUE" + value);
                } else {
                    Log.w(TAG, "Characteristic was null");
                }
                nextGattStep();
            } else {
                Log.w(TAG, "Characteristic failed to read");
            }
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            Log.w(TAG, "Characteristic changed");
            UUID charUuid = characteristic.getUuid();
            Log.w(TAG, "Characteristic Update Received: " + charUuid);
            if(charUuid.compareTo(mResponseCharacteristic.getUuid()) == 0) {
                Log.w(TAG, "mResponseCharacteristic Update");
            }
            if(charUuid.compareTo(mCommandCharacteristic.getUuid()) == 0) {
                Log.w(TAG, "mCommandCharacteristic Update");
            }
            if(charUuid.compareTo(mHeartBeatCharacteristic.getUuid()) == 0) {
                Log.w(TAG, "mHeartBeatCharacteristic Update");
            }
            if(charUuid.compareTo(mReceiveDataCharacteristic.getUuid()) == 0) {
                Log.w(TAG, "mReceiveDataCharacteristic Update");
                byte[] value = characteristic.getValue();
                if(value != null) {
                    Log.w(TAG, "Characteristic: " + value);
                    Log.w(TAG, "Characteristic: " + value.toString());
                    Log.w(TAG, "Characteristic getstring: " + characteristic.getStringValue(0));
                    Log.w(TAG, "SUBSCRIBED TO RESPONSE LISTENER");
                    Observable.just(characteristic.getValue()).subscribe(mDataResponseListener);
                } else {
                    Log.w(TAG, "Characteristic was null");
                }
            }
            Log.w(TAG, "NEW VALUE: " + characteristic.getValue().toString());
        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            Log.w(TAG, "Wrote a discriptor, status: " + status);
            if(step == 2 && currentGattTask == GATT_SETUP) {
                setListeners(2);
            } else if(step == 3) {
                setListeners(3);
            } else if(step == 4) {
                setListeners(4);
            } else if(step == 5) {
                Log.w(TAG, "Done setting Listeners");
            }
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            Log.w(TAG, "Wrote a characteristic: " + status);
            nextGattStep();
        }
    };

    public void attemptConnection() {
        mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        if (device != null) {
            details.append("\nConnection state: " + " Device is not null");
            mConnectionState = mBluetoothManager.getConnectionState(device, BluetoothProfile.GATT);
        }

        Log.w(TAG, "Connection state: " + mConnectionState);
        details.append("\nConnection state: " + mConnectionState);
        if (mConnectionState == STATE_DISCONNECTED || mConnectionState == STATE_DISCONNECTING) {
            ActiveBluetoothDevice btDevice = new Select().from(ActiveBluetoothDevice.class)
                    .orderBy("_ID desc")
                    .executeSingle();
            if (btDevice != null) {
                details.append("\nBT Device: " + btDevice.name);
                mDeviceName = btDevice.name;
                mDeviceAddress = btDevice.address;
                mBluetoothAdapter = mBluetoothManager.getAdapter();
                boolean newConnection = true;
                if(newConnection) {
                    is_connected = connect(mDeviceAddress);
                    details.append("\nConnecting...: ");
                }
            }
        }
    }

    public void attemptRead() {
        final ReadDataShare readData = new ReadDataShare(this);
        final Action1<Long> systemTimeListener = new Action1<Long>() {
            @Override
            public void call(Long s) {

                Log.d(TAG, "Made the full round trip, got " + s + " as the system time");
                Log.d("SYSTTIME", "Made the full round trip, got " + s + " as the system time");
                final long addativeSystemTimeOffset = new Date().getTime() - s;
                Log.d(TAG, "Made the full round trip, got " + addativeSystemTimeOffset + " offset");
                Log.d("SYSTTIME", "Made the full round trip, got " + addativeSystemTimeOffset + " offset");

                final Action1<CalRecord[]> calRecordListener = new Action1<CalRecord[]>() {
                    @Override
                    public void call(CalRecord[] calRecords) {
                        Log.d(TAG, "Made the full round trip, got " + calRecords.length + " Cal Records");
                        Calibration.create(calRecords, addativeSystemTimeOffset, getApplicationContext());

                        final Action1<SensorRecord[]> sensorRecordListener = new Action1<SensorRecord[]>() {
                            @Override
                            public void call(SensorRecord[] sensorRecords) {
                                Log.d(TAG, "Made the full round trip, got " + sensorRecords.length + " Sensor Records");
                                BgReading.create(sensorRecords, addativeSystemTimeOffset, getApplicationContext());

                                final Action1<EGVRecord[]> evgRecordListener = new Action1<EGVRecord[]>() {
                                    @Override
                                    public void call(EGVRecord[] egvRecords) {
                                        Log.d(TAG, "Made the full round trip, got " + egvRecords.length + " EVG Records");
                                        BgReading.create(egvRecords, addativeSystemTimeOffset, getApplicationContext());
                                    }
                                };
                                readData.getRecentEGVs(evgRecordListener);
                            }
                        };
                        readData.getRecentSensorRecords(sensorRecordListener);
                    }
                };
                readData.getRecentCalRecords(calRecordListener);
            }
        };
        readData.readSystemTime(systemTimeListener);
    }

    public void bond(BluetoothGatt gatt) {
        reconnecting = true;
        attemptConnection();
    }

    public boolean connect(final String address) {

        details.append("\nConnecting to device");
        Log.w(TAG, "CONNECTING TO DEVICE");
        if (mBluetoothAdapter == null || address == null) {
            details.append("\nBT adapter is null");
            Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
            return false;
        }
        if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress) && mBluetoothGatt != null) {
            details.append("\nTrying to use an existing mBluetoothGatt for connection.");
            if (mBluetoothGatt.connect()) {
                mConnectionState = STATE_CONNECTING;
                return true;
            } else {
                return false;
            }
        } else {
            device = mBluetoothAdapter.getRemoteDevice(address);
            device.setPin("000000".getBytes());
            if (device == null) {
                Log.w(TAG, "Device not found.  Unable to connect.");
                details.append("\nDevice not found.  Unable to connect.");
                return false;
            }
            mBluetoothGatt = device.connectGatt(getApplicationContext(), true, mGattCallback);
            Log.w(TAG, "Trying to create a new connection.");
            details.append("\nTrying to create a new connection to device");
            mConnectionState = STATE_CONNECTING;
            return true;
        }
    }

    public void authenticateConnection(BluetoothGatt bluetoothGatt) {
        Log.w(TAG, "Trying to auth");
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        String receiverSn = prefs.getString("share_key", "SM00000000").toUpperCase();
        if(bluetoothGatt != null) {
            mBluetoothGatt = bluetoothGatt;
            mShareService = mBluetoothGatt.getService(DexShareAttributes.CradleService);
            if (mShareService != null) {
                mAuthenticationCharacteristic = mShareService.getCharacteristic(DexShareAttributes.AuthenticationCode);
                if(mAuthenticationCharacteristic != null) {
                    Log.w(TAG, "Auth Characteristic found: " + mAuthenticationCharacteristic.toString());
                    mAuthenticationCharacteristic.setValue((receiverSn + "000000").getBytes(StandardCharsets.US_ASCII));
                    currentGattTask = GATT_SETUP;
                    step = 1;
                    bluetoothGatt.writeCharacteristic(mAuthenticationCharacteristic);
                } else {
                    Log.w(TAG, "Authentication Characteristic IS NULL");
                }
            } else {
                Log.w(TAG, "CRADLE SERVICE IS NULL");
            }
        }
    }

    public void assignCharacteristics() {
        mSendDataCharacteristic = mShareService.getCharacteristic(DexShareAttributes.ShareMessageReceiver);
        mReceiveDataCharacteristic = mShareService.getCharacteristic(DexShareAttributes.ShareMessageResponse);
        mHeartBeatCharacteristic = mShareService.getCharacteristic(DexShareAttributes.HeartBeat);
        mCommandCharacteristic = mShareService.getCharacteristic(DexShareAttributes.Command);
        mResponseCharacteristic = mShareService.getCharacteristic(DexShareAttributes.Response);
    }

    public void setListeners(int listener_number) {

        Log.w(TAG, "Setting Listener: #" + listener_number);
        if(listener_number == 1) {
            step = 3;
            setCharacteristicIndication(mReceiveDataCharacteristic);
        } else if(listener_number == 3) {
            setCharacteristicIndication(mResponseCharacteristic);
            step = 5;
        }
     }

    public void disconnect() {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }
        mBluetoothGatt.disconnect();
    }

    public void close() {
        disconnect();
        if (mBluetoothGatt == null) {
            return;
        }
        mBluetoothGatt.close();
        mBluetoothGatt = null;
        mConnectionState = STATE_DISCONNECTED;
        Log.w(TAG, "bt Disconnected");
    }

    public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }
        mBluetoothGatt.readCharacteristic(characteristic);
    }

    public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic){ setCharacteristicNotification(characteristic, true);}
    public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {
        Log.w(TAG, "Characteristic setting notification");
        mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
        Log.w(TAG, "UUID FOUND: " + characteristic.getUuid());
        BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(HM10Attributes.CLIENT_CHARACTERISTIC_CONFIG));
        Log.w(TAG, "Descriptor found: " + descriptor.getUuid());
        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        mBluetoothGatt.writeDescriptor(descriptor);
    }

    public void setCharacteristicIndication(BluetoothGattCharacteristic characteristic){ setCharacteristicIndication(characteristic, true);}
    public void setCharacteristicIndication(BluetoothGattCharacteristic characteristic, boolean enabled) {
        Log.w(TAG, "Characteristic setting notification");
        mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
        Log.w(TAG, "UUID FOUND: " + characteristic.getUuid());
        BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(HM10Attributes.CLIENT_CHARACTERISTIC_CONFIG));
        Log.w(TAG, "Descriptor found: " + descriptor.getUuid());
        descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
        mBluetoothGatt.writeDescriptor(descriptor);
    }

    private final BroadcastReceiver mPairReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();

            if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
                final int state        = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR);
                final int prevState    = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, BluetoothDevice.ERROR);
                if (state == BluetoothDevice.BOND_BONDED) {
                    Log.d(TAG, "CALLBACK RECIEVED Bonded");
                    currentGattTask = GATT_SETUP;
                    mBluetoothGatt.discoverServices();
                } else if (state == BluetoothDevice.BOND_NONE){
                    Log.d(TAG, "CALLBACK RECIEVED: Not Bonded");
                    Toast.makeText(getApplicationContext(), "unBonded", Toast.LENGTH_LONG).show();
                } else if (state == BluetoothDevice.BOND_BONDING) {
                    Log.d(TAG, "CALLBACK RECIEVED: Trying to bond");
                    Toast.makeText(getApplicationContext(), "trying to bond", Toast.LENGTH_LONG).show();
                }
            }
        }
    };

    public void writeCommand(List<byte[]> packets, int aRecordType, Action1<byte[]> dataResponseListener) {
        mDataResponseListener = dataResponseListener;
        successfulWrites = 0;
        writePackets = packets;
        recordType = aRecordType;
        step = 0;
        currentGattTask = GATT_WRITING_COMMANDS;
        gattWritingStep();
    }

    private void nextGattStep() {
        Log.d(TAG, "Next Gatt Step");
        step++;
        switch (currentGattTask) {
        case GATT_NOTHING:
            Log.d(TAG, "Next NOTHING: " + step);
            break;
        case GATT_SETUP:
            Log.d(TAG, "Next GATT SETUP: " + step);
            gattSetupStep();
            break;
        case GATT_WRITING_COMMANDS:
            Log.d(TAG, "Next GATT WRITING: " + step);
            gattWritingStep();
            break;
        }
    }

    public void clearGattTask() {
        currentGattTask = GATT_NOTHING;
        step = 0;
    }

    private void gattSetupStep() {
        step = 1;
        assignCharacteristics();
        setListeners(1);
    }

    private void gattWritingStep() {
        Log.d(TAG, "Writing command to the Gatt, step: " + step);
        int index = step;
        if (index <= (writePackets.size() - 1)) {
            Log.d(TAG, "Writing: " + writePackets.get(index) + " index: " + index);
            mSendDataCharacteristic.setValue(writePackets.get(index));
            mBluetoothGatt.writeCharacteristic(mSendDataCharacteristic);
        } else {
            clearGattTask();
        }
    }
}