package jp.agentec.abook.abv.cl.util;

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.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;

import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.List;
import java.util.UUID;

import jp.agentec.abook.abv.bl.common.Constant.DeviceType;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.launcher.android.R;
import jp.agentec.abook.abv.ui.common.activity.ABVActivity;
import jp.agentec.abook.abv.ui.common.activity.ABVAuthenticatedActivity;
import jp.agentec.abook.abv.ui.common.appinfo.AppDefType;
import jp.agentec.adf.util.StringUtil;

import static android.bluetooth.BluetoothDevice.TRANSPORT_LE;

public class BleManagerUtil {
	private final String TAG = "BleManagerUtil";

	private BluetoothManager bluetoothManager;
	private Context mContext;
	private BleManagerUtilListener mListener;
	private Handler mHandler;

	// 定数（Bluetooth LE Gatt UUID）
	// Private Service
	private UUID UUID_SERVICE_PRIVATE;
	private UUID UUID_CHARACTERISTIC_PRIVATE;

	// for Notification
	private UUID UUID_NOTIFY;

	// メンバー変数
	public BluetoothAdapter mBluetoothAdapter;      // BluetoothAdapter : Bluetooth処理で必要
	private String        	mDeviceAddress = "";    // デバイスアドレス
	public BluetoothGatt	mBluetoothGatt = null;  // Gattサービスの検索、キャラスタリスティックの読み書き

	private int mBleConnectDeviceType;

	public BleManagerUtil(Context context, BleManagerUtilListener listener) {
		mContext = context;
		mListener = listener;
		mHandler = new Handler();
	}

	// BluetoothGattコールバック
	private final BluetoothGattCallback mGattcallback = new BluetoothGattCallback() {
		// 接続状態変更（connectGatt()の結果として呼ばれる。）
		@Override
		public void onConnectionStateChange(BluetoothGatt gatt, final int status, final int newState ) {
            Logger.d("onConnectionStateChange status = " + status + "  newState = " + newState);

			// デバイスと接続されていない場合のメッセージコード：133, 62
			// デバイスと離れて応答がなく、タイムアウトになる場合：8
			// デバイスと接続が切れた場合のメッセージコード：19
			if (status == 133 || status == 62 || status == 8 || status == 19) {  // 接続失敗
				((Activity)mContext).runOnUiThread(new Runnable() {
					@Override
					public void run() {
						mListener.onConnectionError(status);
					}
				});
				return;
			}

			if( status == BluetoothGatt.GATT_SUCCESS && BluetoothProfile.STATE_CONNECTED == newState ) {
				new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
					@Override
					public void run() {
						// 接続完了
						if (mBluetoothGatt == null || !mBluetoothGatt.discoverServices()) {  // サービス検索

							((Activity)mContext).runOnUiThread( new Runnable() {  // 接続失敗
								@Override
								public void run() {
									mListener.onGetDeviceInfoFailed(status);
									Logger.e("onConnectionStateChange2 status = " + status);
								}
							});
						}
					}
				}, 600);
				return;
			}

			if( BluetoothProfile.STATE_DISCONNECTED == newState ) {    // 切断完了（接続可能範囲から外れて切断された）
				// 切断が発生する場合、Bluetoothと接続を切断する。
				disconnect(true);
				return;
			}
		}

		// サービス検索が完了したときの処理（mBluetoothGatt.discoverServices()の結果として呼ばれる。）
		@Override
		public void onServicesDiscovered( BluetoothGatt gatt, int status ) {
			if( BluetoothGatt.GATT_SUCCESS != status ) {
				return;
			}

			Logger.d(TAG, "--gattSize : " + gatt.getServices().size());
			if (gatt.getServices().size() == 0) {
				// サービスがない場合は、再接続
				bleGattReconnect();
				return;
			}
			mBluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);

			// 発見されたサービスのループ
			for(BluetoothGattService service : gatt.getServices()) {
				// サービスごとに個別の処理
				if((service == null) || (service.getUuid() == null)) {
					continue;
				}

				// GATTサービスのキャラクタリスティックを列挙
				List<BluetoothGattCharacteristic> listCharacteristic = service.getCharacteristics();
				for (BluetoothGattCharacteristic characteristic : listCharacteristic) {
					// 利用したいものがあるはず！
					Logger.d(TAG, "characteristic : [ " + characteristic );
				}

				// プライベートサービス
				if(UUID_SERVICE_PRIVATE.equals(service.getUuid())) {
					// 最初の読み取り
					readCharacteristic(UUID_SERVICE_PRIVATE, UUID_CHARACTERISTIC_PRIVATE);

					((Activity)mContext).runOnUiThread( new Runnable() {
						@Override
						public void run() {
							// 操作可能
							mListener.onConnectionState();
						}
					});
					continue;
				}
			}
		}

		// キャラクタリスティックが読み込まれたときの処理
		@Override
		public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status ) {
			if( BluetoothGatt.GATT_SUCCESS != status ) {
				return;
			}
			if( UUID_CHARACTERISTIC_PRIVATE.equals(characteristic.getUuid())) {
//				final String strTemperature = byteToString(characteristic.getValue());

//				runOnUiThread( new Runnable() {
//					@Override
//					public void run() {
//						// 芯温計の温度を渡す。
//                        // 端末と接続時に呼ばれるのでコメント処理
////						mListener.onGetDeviceInfo(strTemperature);
//					}
//				});

				// 通知設定
				setCharacteristicNotification(UUID_SERVICE_PRIVATE, UUID_CHARACTERISTIC_PRIVATE, true);
				return;
			}
		}

		// キャラクタリスティック変更が通知されたときの処理
		@Override
		public void onCharacteristicChanged( BluetoothGatt gatt, BluetoothGattCharacteristic characteristic ) {
			if( UUID_CHARACTERISTIC_PRIVATE.equals( characteristic.getUuid() ) ) {
				final String strTemperature = byteToString(characteristic.getValue());

				((Activity)mContext).runOnUiThread( new Runnable() {
					@Override
					public void run() {
						// 複数呼ばれるため、mBluetoothGattがnullの場合、以下の処理を行わない
						if(mBluetoothGatt == null) {
							return;
						}
						// 芯温計の温度を渡す。
						mListener.onGetDeviceInfo(strTemperature);
					}
				});
				return;
			}
		}
	};
	/**
	 * 接続してるデバイスタイプを取得
	 * @return
	 */
	public int getBluetoothDeviceType() {
		return mBleConnectDeviceType;
	}

	// スキャンコールバック
	private final ScanCallback mScanCallback = new ScanCallback() {
		@Override
		public void onScanResult(int callbackType, final ScanResult result) {
			super.onScanResult(callbackType, result);
			((Activity)mContext).runOnUiThread(new Runnable() {
				@Override
				public void run() {
					BluetoothDevice device = result.getDevice();
					Logger.d(TAG, "--scaning : " + device.getName() );
					if (device.getAddress().equals(mDeviceAddress)) {
						// 異常と見做し、stopScanでエラーを返す。
						stopScan(device.getAddress());
					}
				}
			});
		}

		// スキャンに失敗
		@Override
		public void onScanFailed(final int errorCode) {
			super.onScanFailed(errorCode);
			Logger.e(TAG, "scan failed errorCode : " + errorCode);
			((Activity)mContext).runOnUiThread( new Runnable() {
				@Override
				public void run() {
					mListener.onConnectionError(errorCode);
				}
			});
		}
	};

	/**
	 * スキャンを中止
	 */
	private void stopScan(String deviceAddress) {
		Logger.d(TAG, "stop ble scan");

		// 一定期間後にスキャン停止するためのHandlerのRunnableの削除
		mHandler.removeCallbacksAndMessages(null);

		if (StringUtil.isNullOrEmpty(deviceAddress)) {
			// deviceAddressが見つからなかったと見做し、エラーで返す。
			mListener.onConnectionError(-1);
			return;
		}

		// スキャン停止
		mBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback);

		// ble接続
		BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(deviceAddress);
		bleGattConnect(device);
	}

	/**
	 * ディバイス情報の取得を開始します<br>
	 * 成功時にlistenerのonGetDeviceInfo、失敗時にlistenerのonGetDeviceInfoFailedが呼び出されます。
	 *
	 */
	public interface BleManagerUtilListener {
		void onGetDeviceInfo(String strTemperature);  // ディバイスから渡す情報
		void onGetDeviceInfoFailed(int status);  // ディバイスと接続失敗時の情報渡す。
		void onConnectionState();  // ディバイスと接続された時情報渡す。
		void onDisConnectionState();  // ディバイスと切断時
		void onConnectionError(int status);  // ディバイス接続エラー
	}
	// 接続
	public void connect(int connectTargetDeviceType, String deviceAddress) {
		mDeviceAddress = deviceAddress;
		// デバイスタイプセット
		mBleConnectDeviceType = connectTargetDeviceType;
		setUUID();
		if(StringUtil.isNullOrEmpty(mDeviceAddress)) {  // DeviceAddressが空の場合は処理しない
			return;
		}

		if(mBluetoothGatt != null) {  // mBluetoothGattがnullでないなら接続済みか、接続中。
			return;
		}

		// mBluetoothGattのサービスと接続
		BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(deviceAddress);
		if (device.getName() == null) {
			// deviceの名称がない場合、接続エラーになるため、再スキャンすることで検知したらgetRemoteDeviceメソッドに名称がセットされる
			mBluetoothAdapter.getBluetoothLeScanner().startScan(mScanCallback);
			// スキャン開始（一定時間後にスキャン停止する）
			mHandler.postDelayed( new Runnable() {
				@Override
				public void run() {
					// 20秒後に呼ばれた時はスキャンで端末を取得できなかったと見做す。
					stopScan(null);
					Logger.d(TAG, "scan in 20 sec");
				}
			}, 20000 );
		} else {
			bleGattConnect(device);
		}
	}

	/**
	 * GATT BLE接続処理
	 * @param device
	 */
	private void bleGattConnect(BluetoothDevice device) {
		// GATT BLEを利用する時Androidのバージョン「23」をチェックしてGATTと接続する。
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
			mBluetoothGatt = device.connectGatt(mContext, false, mGattcallback, TRANSPORT_LE);
		} else {
			mBluetoothGatt = device.connectGatt(mContext, false, mGattcallback );
		}
		// 該当デバイスのキャッシュをリフレッシュ
		refreshDeviceCache(mBluetoothGatt);
	}

	// 切断
	public void disconnect(boolean listenerFlg) {
		if(mBluetoothGatt == null) {
			return;
		}

		// 切断
		//   mBluetoothGatt.disconnect()ではなく、mBluetoothGatt.close()しオブジェクトを解放する。
		//   理由：「ユーザーの意思による切断」と「接続範囲から外れた切断」を区別するため。
		//   ①「ユーザーの意思による切断」は、mBluetoothGattオブジェクトを解放する。再接続は、オブジェクト構築から。
		//   ②「接続可能範囲から外れた切断」は、内部処理でmBluetoothGatt.disconnect()処理が実施される。
		//     切断時のコールバックでmBluetoothGatt.connect()を呼んでおくと、接続可能範囲に入ったら自動接続する。
		mBluetoothGatt.disconnect();
//		mBluetoothGatt.close();
		mBluetoothGatt = null;
		if (listenerFlg) {
			((Activity)mContext).runOnUiThread( new Runnable() {
				@Override
				public void run() {
					// 切断トーストメッセージを表示する。
					mListener.onDisConnectionState();
				}
			});
		}
	}

	// キャラクタリスティックの読み込み
	private void readCharacteristic( UUID uuid_service, UUID uuid_characteristic ) {
		if( null == mBluetoothGatt ) {
			return;
		}
		BluetoothGattCharacteristic blechar = mBluetoothGatt.getService( uuid_service ).getCharacteristic( uuid_characteristic );
		mBluetoothGatt.readCharacteristic( blechar );
	}

	// キャラクタリスティック通知の設定
	private void setCharacteristicNotification( UUID uuid_service, UUID uuid_characteristic, boolean enable ) {
		if( null == mBluetoothGatt ) {
			return;
		}

		BluetoothGattService service = mBluetoothGatt.getService( uuid_service );

		if(service == null) {
			return;
		}

		BluetoothGattCharacteristic blechar = service.getCharacteristic( uuid_characteristic );
		mBluetoothGatt.setCharacteristicNotification( blechar, enable );
		BluetoothGattDescriptor descriptor = blechar.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
		descriptor.setValue( BluetoothGattDescriptor.ENABLE_INDICATION_VALUE );
		mBluetoothGatt.writeDescriptor(descriptor);
	}

	// Signed short (16-bit) Two's complement to short, String
	public String byteToString(byte[] bytes) {
		short sht = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getShort();
		float flt = sht / (float)100;

		return String.valueOf(flt);
	}

	// Bluetoothアダプタの取得処理
	public void startDeviceInfo() {
		// Bluetoothアダプタの取得
		BluetoothManager bluetoothManager = (BluetoothManager) mContext.getSystemService( Context.BLUETOOTH_SERVICE );
		if (bluetoothManager != null) {
			mBluetoothAdapter = bluetoothManager.getAdapter();
			if( mBluetoothAdapter == null ) {    // Android端末がBluetoothをサポートしていない
				Toast.makeText( mContext, R.string.bluetooth_is_not_supported, Toast.LENGTH_SHORT ).show();
				return;
			}
		} else {
			Logger.e(TAG, "bluetoothManager is null");
		}
	}

	// UUIDセット
	private void setUUID() {
		if (mBleConnectDeviceType == DeviceType.centerThermomete) {
			// 中心温度計のUUIDセット
			UUID_SERVICE_PRIVATE = UUID.fromString("05fd8c58-9d23-11e7-abc4-cec278b6b50a");
			UUID_CHARACTERISTIC_PRIVATE = UUID.fromString("05fd8f5a-9d23-11e7-abc4-cec278b6b50a");
		} else if (mBleConnectDeviceType == DeviceType.radiationThermomete) {
			// 放射温度計のUUIDセット
			UUID_SERVICE_PRIVATE = UUID.fromString("462026f6-cfe1-11e7-abc4-cec278b6b50a");
			UUID_CHARACTERISTIC_PRIVATE = UUID.fromString("46202b74-cfe1-11e7-abc4-cec278b6b50a");
		}
	}

	/**
	 * bleGattのcache更新
	 * @param gatt
	 * @return
	 */
	private boolean refreshDeviceCache(BluetoothGatt gatt){
		try {
			BluetoothGatt localBluetoothGatt = gatt;
			Method localMethod = localBluetoothGatt.getClass().getMethod("refresh", new Class[0]);
			if (localMethod != null) {
				boolean bool = ((Boolean) localMethod.invoke(localBluetoothGatt, new Object[0])).booleanValue();
				return bool;
			}
		}
		catch (Exception localException) {
			Logger.e(TAG, "An exception occured while refreshing device");
		}
		return false;
	}

	/**
	 * 再接続処理
	 */
	private void bleGattReconnect() {
		disconnect(false);
		connect(mBleConnectDeviceType, mDeviceAddress);
	}
}
