package com.nxp.vizncompanionapp.viewModel

import android.annotation.TargetApi
import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
import android.bluetooth.*
import android.bluetooth.le.BluetoothLeScanner
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanSettings
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.ParcelUuid
import android.util.Log
import com.nxp.vizncompanionapp.R
import com.nxp.vizncompanionapp.activities.BleListingActivity
import com.nxp.vizncompanionapp.activities.DiscoverActivity
import com.nxp.vizncompanionapp.activities.WifiListingActivity
import com.nxp.vizncompanionapp.utility.AppConstant
import com.nxp.vizncompanionapp.utility.DisplayNotifications
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.*
import com.thedamfr.android.BleEventAdapter.BleEventAdapter


class BLEViewModel : ViewModel() {

    private var bluetoothManager : BluetoothManager? = null
    private var mBluetoothAdapter: BluetoothAdapter? = null
    private var mDisplayNotifications: DisplayNotifications = DisplayNotifications()
    private var mLEScanner: BluetoothLeScanner? = null
    private var mBleList: MutableList<BluetoothDevice>? = ArrayList()
    private var listScanResults: MutableLiveData<List<BluetoothDevice>>? = MutableLiveData()
    private var filters: MutableList<ScanFilter>? = null
    private var settings: ScanSettings? = null
    private var mScanCallback: ScanCallback? = null
    private var mLeScanCallback: BluetoothAdapter.LeScanCallback? = null
    private var handlerBleRequest: Handler? = null
    private var handlerBleConnectRequest: Handler? = null
    var runnableBleRequest: MyRunnableBleRequest? = null
    var runnableBleConnectRequest: MyRunnableBleConnectRequest? = null
    var selectedDevice: BluetoothDevice? = null

    inner class MyRunnableBleRequest(context: Context) : Runnable {
        var context: Context? = null

        init {
            this.context = context
        }

        override fun run() {
            mDisplayNotifications.dismissDialog()
            mLEScanner?.stopScan(mScanCallback)
            if(mBleList?.isEmpty()!!){
                Log.e("TAG","List empty")
                mDisplayNotifications?.displayErrorDialog(context,context?.getString(R.string.ble_list_empty_msg),context?.getString(R.string.try_again),context?.getString(R.string.cancel))
            }
        }
    }

    inner class MyRunnableBleConnectRequest(context: Context) : Runnable {
        var context: Context? = null

        init {
            this.context = context
        }

        override fun run() {
            mDisplayNotifications.dismissDialog()
        }
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    fun initCallbackLollipop(){
        if (mScanCallback != null) return
        this.mScanCallback = object : ScanCallback() {
            override fun onScanResult(callbackType: Int, result: android.bluetooth.le.ScanResult) {
                super.onScanResult(callbackType, result)
                if (!mBleList?.contains(result.device)!!) {
                    val uuids = result.scanRecord!!.serviceUuids
                    Log.e("BLESCAN",uuids.toString());
                    if (uuids != null && uuids.contains(ParcelUuid.fromString(AppConstant.DEVICE_SERVICE_UUID))) {
                        mBleList?.add(result.device)
                        listScanResults?.postValue(mBleList)
                    }
                }
            }

            override fun onBatchScanResults(results: List<android.bluetooth.le.ScanResult>) {
                /*  Process a   batch   scan    results */
                Log.e("BLESCAN","onBatchScanResults")
                for (sr in results) {
                }
            }

            override fun onScanFailed(errorCode: Int) {
                super.onScanFailed(errorCode)
                Log.e("BLESCAN", "onScanFailed$errorCode")
            }
        }
    }

    fun initScanCallbackSupport(){
        if (mLeScanCallback != null) return
        this.mLeScanCallback = BluetoothAdapter.LeScanCallback { device, rssi, scanRecord ->
            try {
                val uuid = parseUUIDs(scanRecord)
                if (!mBleList?.contains(device)!!) {
                    if (uuid.contains(UUID.fromString(AppConstant.DEVICE_SERVICE_UUID))) {
                        mBleList?.add(device)
                        listScanResults?.postValue(mBleList)
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    /**
     * Fetch UUID from advertisement data
     *
     * @param advertisedData data of advertisement
     * @return list of uuid
     */
    private fun parseUUIDs(advertisedData: ByteArray): List<UUID> {
        val uuids = ArrayList<UUID>()

        var offset = 0
        while (offset < advertisedData.size - 2) {
            var len = advertisedData[offset++].toInt()
            if (len == 0)
                break

            val type = advertisedData[offset++].toInt()
            when (type) {
                0x02 // Partial list of 16-bit UUIDs
                    , 0x03 // Complete list of 16-bit UUIDs
                -> while (len > 1) {
                    var uuid16 = advertisedData[offset++].toInt()
                    //uuid16 += advertisedData[offset++] shl 8
                    len -= 2
                    uuids.add(UUID.fromString(String.format(
                            "%08x-0000-1000-8000-00805f9b34fb", uuid16)))
                }
                0x06// Partial list of 128-bit UUIDs
                    , 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.
                            val buffer = ByteBuffer.wrap(advertisedData,
                                    offset++, 16).order(ByteOrder.LITTLE_ENDIAN)
                            val mostSignificantBit = buffer.long
                            val leastSignificantBit = buffer.long
                            uuids.add(UUID(leastSignificantBit,
                                    mostSignificantBit))
                        } catch (e: IndexOutOfBoundsException) {
                            // Defensive programming.
                            //                            Log.e("HERE", e.toString());
                        } finally {
                            // Move the offset to read the next uuid.
                            offset += 15
                            len -= 16
                        }
                    }
                else -> offset += len - 1
            }
        }

        return uuids
    }


    /**
     * this is the observer method whenever any changes done in list it will called and notify to adapter
     */
    internal fun getAllBleDevices(): LiveData<List<BluetoothDevice>>? {
        return listScanResults
    }


    fun checkForBLE(context: Context) : Boolean {

        bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?
        mBluetoothAdapter = bluetoothManager?.adapter

        return if (mBluetoothAdapter == null)  {
            false
        } else {
            context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
        }
    }

    fun checkBLEState(context: Context) {
        if (!mBluetoothAdapter?.isEnabled!!){
            scanLeDevice(false)
            mDisplayNotifications?.displayErrorDialog(context,context.getString(R.string.bt_require_msg),context.getString(R.string.ok),context.getString(R.string.cancel))
        } else {
            when (context) {
                is DiscoverActivity -> context.gotoBleListingScreen()
                is BleListingActivity -> scanBLEDevices(context)
                is WifiListingActivity -> context.connectDeviceWithBt()
            }
        }
    }

    fun turnOnBLE(){
        mBluetoothAdapter?.enable()
    }

    fun scanBLEDevices(context: Context){
        selectedDevice = null
        mDisplayNotifications?.fetchData(context)
        mBleList?.clear()
        listScanResults?.postValue(mBleList)
        Handler(Looper.getMainLooper()).postDelayed({
            if (Build.VERSION.SDK_INT >= 21) {
                bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?
                mBluetoothAdapter = bluetoothManager?.adapter
                mLEScanner = mBluetoothAdapter?.bluetoothLeScanner
                val beaconFilter = ScanFilter.Builder()
                        .setServiceUuid(ParcelUuid.fromString(AppConstant.DEVICE_SERVICE_UUID))
                        .build()
                filters = ArrayList<ScanFilter>()
                filters?.add(beaconFilter)

                settings = ScanSettings.Builder()
                        .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                        .build()
            }

            scanLeDevice(true)
            removeHandlerBleRequest()
            handlerBleRequest = Handler(Looper.getMainLooper())
            runnableBleRequest = MyRunnableBleRequest(context)
            handlerBleRequest?.postDelayed(runnableBleRequest, AppConstant.BLE_SCAN_PERIOD)
        }, 2000)
    }

    fun scanLeDevice(b: Boolean) {
        if(b){
            try {
                if (Build.VERSION.SDK_INT < 21) {
                    mBluetoothAdapter?.startLeScan(mLeScanCallback)
                } else {
                    mLEScanner?.startScan(filters, settings, mScanCallback)
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        } else {
            try {
                if (Build.VERSION.SDK_INT < 21) {
                    mBluetoothAdapter?.stopLeScan(mLeScanCallback)
                } else {
                    mLEScanner?.stopScan(mScanCallback)
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    fun connectToDevice(context: Context,bluetoothDevice: BluetoothDevice){
        mDisplayNotifications?.fetchData(context)
        if (!mBluetoothAdapter?.isEnabled!!) {
            mBluetoothAdapter?.enable()
        }
        if(bluetoothDevice == null){
            return
        } else {
            selectedDevice = bluetoothDevice
            Log.e("TAG",selectedDevice?.name)
            Handler(Looper.getMainLooper()).postDelayed({

                BleEventAdapter.getInstance().connectDevice(context, bluetoothDevice)

                if (Build.VERSION.SDK_INT < 21) {
                    if(mBluetoothAdapter != null)
                        mBluetoothAdapter?.stopLeScan(mLeScanCallback)
                } else {
                    if(mLEScanner != null)
                        mLEScanner?.stopScan(mScanCallback)
                }
                scanLeDevice(false)
                removeHandlerBleConnect()
                handlerBleConnectRequest = Handler(Looper.getMainLooper())
                runnableBleConnectRequest = MyRunnableBleConnectRequest(context)
                handlerBleConnectRequest?.postDelayed(runnableBleConnectRequest, AppConstant.DEVICE_SCAN_TIMEOUT)
            }, 1000)
        }
    }

    fun removeHandlerBleRequest() {
        if (handlerBleRequest != null) {
            handlerBleRequest?.removeCallbacks(runnableBleRequest)
            runnableBleRequest = null
            handlerBleRequest = null
        }
    }

    fun removeHandlerBleConnect() {
        if (handlerBleConnectRequest != null) {
            handlerBleConnectRequest?.removeCallbacks(runnableBleRequest)
            runnableBleConnectRequest = null
            handlerBleConnectRequest = null
        }
    }

    fun DisplayWifiDialog(context: Context,ssid: String){
        mDisplayNotifications?.displayErrorDialog(context,context.getString(R.string.wifi_setting_msg,ssid),context.getString(R.string.settings),context.getString(R.string.cancel))
    }
}