package moe.minori.openxiaomiscale;

import android.app.Activity;
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.Context;
import android.content.Intent;
import android.os.Handler;
import android.preference.PreferenceManager;

import java.util.ArrayList;

import moe.minori.openxiaomiscale.activity.MainActivity;
import moe.minori.openxiaomiscale.objects.Database;
import moe.minori.openxiaomiscale.objects.Log;
import moe.minori.openxiaomiscale.objects.Weight;
import moe.minori.openxiaomiscale.objects.WeightDBElement;

/**
 * Created by minori on 16. 8. 20.
 */
public class BTUtils
{
	private static BluetoothAdapter bluetoothAdapter = null;
	private static Handler mHandler = null;

	private static BluetoothGatt bluetoothGatt;
	private static BluetoothGattCharacteristic weightCharacteristic;


	private static BluetoothAdapter.LeScanCallback scanCallback = null;
	private static BluetoothGattCallback gattCallback = null;

	private static void prepareBLECallback(final MainActivity activity)
	{
		// Prepare callback
		scanCallback = new BluetoothAdapter.LeScanCallback()
		{
			@Override
			public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord)
			{
				if (device.getAddress().replace(":", "").startsWith("880f10") ||
						device.getAddress().replace(":", "").startsWith("880F10")) // Xiaomi
				{
					//TODO: Actual device GATT information matching instead of naive device name
					if (device.getName().equals("MI_SCALE")) // It really is scale
					{
						// Register device in the shared preference
						//TODO: Deal with multiple scale, maybe signal strength threshold?

						startGatt(device, activity);
						startStopBLEScanning(activity, false);

						// Register GATT listener to receive weight event from scale device
						Log.d("MainActivity", "Scale found, starting Gatt connection");
					}
				}
			}
		};
	}

	private static void prepareBTAdapter(final Activity activity)
	{
		// Initializes Bluetooth adapter.
		BluetoothManager bluetoothManager =
				(BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE);

		bluetoothAdapter = bluetoothManager.getAdapter();
	}

	private static void prepareGATTCallback(final MainActivity activity)
	{
		gattCallback = new BluetoothGattCallback()
		{
			@Override
			public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState)
			{
				super.onConnectionStateChange(gatt, status, newState);
				Log.d("GattCallback", "onConnectionStateChange");
				if (newState == BluetoothProfile.STATE_CONNECTED)
				{
					Log.d("GattCallback", "Gatt connected, attempting discovery...");
					gatt.discoverServices();
				}
				else if (newState == BluetoothProfile.STATE_DISCONNECTED)
				{
					Log.d("GattCallback", "Gatt disconnected");

					UIUtils.updateUIScaleInformation(activity,
							activity.getResources().getString(R.string.scaleDisconnectedString),
							activity.getResources().getString(R.string.stringDisconnectedStatusString),
							true);


					stopGatt();
					startStopBLEScanning(activity, false);
				}
			}

			@Override
			public void onServicesDiscovered(final BluetoothGatt gatt, int status)
			{
				super.onServicesDiscovered(gatt, status);
				Log.d("GattCallback", "onServicesDiscovered");
				ArrayList<BluetoothGattService> serviceList = (ArrayList) gatt.getServices();

				for (BluetoothGattService one : serviceList)
				{
					if (one != null)
					{
						ArrayList<BluetoothGattCharacteristic> characteristicsList = (ArrayList) one.getCharacteristics();
						for (BluetoothGattCharacteristic two : characteristicsList)
						{
							if (two.getUuid().toString().startsWith("00002a9d"))
							{
								// Weight param discovered

								UIUtils.updateUIScaleInformation(activity,
										gatt.getDevice().getName() + "(" + gatt.getDevice().getAddress() + ")",
										activity.getResources().getString(R.string.scaleFoundStatusString),
										false);

								weightCharacteristic = two;

								// Start constant notification
								bluetoothGatt.setCharacteristicNotification(weightCharacteristic, true);

								ArrayList<BluetoothGattDescriptor> descriptors = (ArrayList<BluetoothGattDescriptor>) weightCharacteristic.getDescriptors();

								if ( descriptors.size() == 1 )
								{
									descriptors.get(0).setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
									bluetoothGatt.writeDescriptor(descriptors.get(0));
								}
								else
								{
									//TODO: Deal with multiple descriptors
									Log.d("GattCallback", "Multiple descriptors found in weight characteristic, unhandled exception!");
									System.exit(-1);
								}
							}
						}

					}
				}

			}

			@Override
			public void onCharacteristicWrite(BluetoothGatt gatt,
											  BluetoothGattCharacteristic characteristic,
											  int status)
			{
				super.onCharacteristicWrite(gatt, characteristic, status);
				Log.d("GattCallback", "onCharacteristicWrite");

			}

			@Override
			public void onDescriptorRead(BluetoothGatt gatt,
										 BluetoothGattDescriptor descriptor,
										 int status)
			{
				super.onDescriptorRead(gatt, descriptor, status);
				Log.d("GattCallback", "onDescriptorRead");

			}

			@Override
			public void onDescriptorWrite(BluetoothGatt gatt,
										  BluetoothGattDescriptor descriptor,
										  int status)
			{
				super.onDescriptorWrite(gatt, descriptor, status);
				Log.d("GattCallback", "onDescriptorWrite");
			}

			@Override
			public void onReliableWriteCompleted(BluetoothGatt gatt, int status)
			{
				super.onReliableWriteCompleted(gatt, status);
				Log.d("GattCallback", "onReliableWriteCompleted");

			}

			@Override
			public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status)
			{
				super.onReadRemoteRssi(gatt, rssi, status);
				Log.d("GattCallback", "onReadRemoteRssi");

			}

			@Override
			public void onMtuChanged(BluetoothGatt gatt, int mtu, int status)
			{
				super.onMtuChanged(gatt, mtu, status);
				Log.d("GattCallback", "onMtuChanged");

			}

			@Override
			public void onCharacteristicRead(BluetoothGatt gatt,
											 BluetoothGattCharacteristic characteristic,
											 int status)
			{
				super.onCharacteristicRead(gatt, characteristic, status);
				Log.d("GattCallback", "onCharacteristicRead");
			}

			@Override
			public void onCharacteristicChanged(BluetoothGatt gatt,
												BluetoothGattCharacteristic characteristic)
			{
				super.onCharacteristicChanged(gatt, characteristic);
				Log.d("GattCallback", "onCharacteristicChanged");
				//TODO: Separate UI&DB and backend code

				final Weight weight = new Weight (characteristic.getValue());

				Log.d("GattCallback", "Weight data: " + weight.weight());
				Log.d("GattCallback", "IsStabilized: " + weight.isStabilized());
				Log.d("GattCallback", "IsWeightRemoved: " + weight.isWeightRemoved());

				UIUtils.updateUIWeightInformation(activity, weight, PreferenceManager.getDefaultSharedPreferences(activity));

				if ( weight.isStabilized() && weight.isWeightRemoved() )
				{
					// Store current weight object's data in database

					Database database = new Database(activity);

					database.insertElement(new WeightDBElement(weight, 1));


				}
			}
		};
	}

	/**
	 * Starts and stops BLE scanning from device
	 * Returns without changing mScanning value when BT is not on.
	 *
	 * @param activity activity of application to use handler
	 * @param enable   If true, start scanning for 10 seconds
	 *                 If false, stop scanning.
	 *                 Scanning will stop automatically after 10 seconds without explicit function call
	 */
	public static void startStopBLEScanning(final MainActivity activity, final boolean enable)
	{
		final int REQUEST_ENABLE_BT = 2;

		prepareBTAdapter(activity);

		if ( mHandler == null )
			mHandler = new Handler();

		if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled())
		{
			Log.d("StartStopBLEScanning", "BT not on");
			Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
			activity.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
			return;
		}

		if (scanCallback == null)
		{
			Log.d("StartStopBLEScanning", "No callback method, making one...");
			prepareBLECallback(activity);
		}


		if (enable)
		{
			mHandler.postDelayed(new Runnable()
			{
				@Override
				public void run()
				{
					Log.d("StartStopBLEScanning", "Scan expired because of time");
					bluetoothAdapter.stopLeScan(scanCallback);

					UIUtils.updateUIScaleInformation(activity,
							activity.getResources().getString(R.string.scaleNotFoundString),
							activity.getResources().getString(R.string.scaleNotFoundStatusString),
							true);


				}
			}, Long.parseLong(PreferenceManager.getDefaultSharedPreferences(activity).getString("settingsScanMaxDuration", "10000")));

			bluetoothAdapter.startLeScan(scanCallback);

		}
		else
		{
			Log.d("StartStopBLEScanning", "Scan exited gracefully");
			mHandler.removeCallbacksAndMessages(null);
			bluetoothAdapter.stopLeScan(scanCallback);
		}

	}

	public static void startGatt(BluetoothDevice device, MainActivity activity)
	{
		if (bluetoothAdapter == null)
		{
			Log.d("StartGatt", "BT adapter variable not initialized, this should not happen, but working around...");
			prepareBTAdapter(activity);
		}


		if (gattCallback == null)
		{
			Log.d("StartGatt", "No callback method, making one...");
			prepareGATTCallback(activity);
		}


		Log.d("StartGatt", "Starting Gatt...");
		bluetoothGatt = device.connectGatt(activity, false, gattCallback);
	}

	public static void stopGatt()
	{
		Log.d("StopGatt", "Gatt stopped");
		if (bluetoothGatt == null)
		{
			return;
		}
		bluetoothGatt.close();
		bluetoothGatt = null;

	}
}