// -----------------------
// TypeScript [2.6.2]
// BluetoothLePlugin: https://github.com/randdusing/cordova-plugin-bluetoothle
// Dialogs(Notifications): https://github.com/apache/cordova-plugin-dialogs
// -----------------------

import MSeriesParser from './mSeriesParser'
import ms from 'ms'
const BLE_DEVICE_TIMEOUT = ms('20s')

export default class BLE {
  private callback: ((broadcast: Broadcast) => void)
  private bleDevice: BluetoothlePlugin.Bluetoothle = window.bluetoothle
  private deviceTimeout: number | null = null
  private readonly isAndroid: boolean = device.platform === 'Android'

  constructor (callback: ((broadcast: Broadcast) => void)) {
    this.callback = callback
    document.addEventListener('pause', () => this.onPause())
    document.addEventListener('resume', () => this.onResume())

    this.init()
  }

  private init () {
    if (this.isAndroid) {
      this.initAndroid()
    } else {
      this.initIos()
    }
  }

  private initIos () {
    this.bleDevice.initialize(async (result) => {
      if (result.status === 'disabled') {
        await this.notifyEnableRequired()
      } else {
        this.start()
      }
    }, { request: true, statusReceiver: true })
  }

  private initAndroid () {
    this.bleDevice.initialize(async (result) => {
      if (result.status === 'disabled') {
        this.enable()
      } else {
        await this.getPermissions()
        await this.getLocation()
        this.start()
      }
    }, { statusReceiver: true })
  }

  private start () {
    this.resetTimeout()
    this.bleDevice.startScan(
      result => this.scanReponse(result),
      error => {
        if (error.message === 'Scanning already in progress') {
          this.cycle()
        } else {
          throw new Error(error.message)
        }
      },
      {
        allowDuplicates: true,
        scanMode: 2,    // SCAN_MODE_LOW_LATENCY
        matchMode: 1,   // MATCH_MODE_AGRESSIVE
        matchNum: 3,    // MATCH_NUM_MAX_ADVERTISEMENT
        callbackType: 1 // CALLBACK_TYPE_ALL_MATCHES
      }
    )
  }

  private async stop () {
    if (this.deviceTimeout) {
      clearTimeout(this.deviceTimeout)
    }
    return new Promise((resolve, reject) => {
      this.bleDevice.stopScan(result => {
        result.status === 'scanStopped' ? resolve() : reject()
      }, reject)
    })
  }

  private scanReponse (response: BluetoothlePlugin.ScanStatus) {
    if (response.status === 'scanResult') {
      this.resetTimeout()
      if (response.name === 'M3') {
        this.callback(MSeriesParser(response))
      }
    }
  }

  private async cycle () {
    this.resetTimeout()
    await this.stop()
    this.start()
  }

  private onPause () {
    this.stop()
  }

  private onResume () {
    this.init()
  }

  private timeoutEvent () {
    this.cycle()
  }

  private resetTimeout () {
    if (this.deviceTimeout) {
      clearTimeout(this.deviceTimeout)
    }
    this.deviceTimeout = window.setTimeout(() => { this.timeoutEvent() }, BLE_DEVICE_TIMEOUT)
  }

  private async enable () {
    return new Promise((resolve, reject) => {
      this.bleDevice.enable(resolve, reject)
    })
  }

  private async notifyEnableRequired () {
    await this.sendNotification({
      title: 'Bluetooth Required',
      message: 'This app requires the use of the device\'s Bluetooth radio.\n\nPlease turn on Bluetooth in your devices settings before continuing.',
      buttonName: 'CONTINUE'
    })
  }

  private async getPermissions () {
    if (!(await this.hasPermission())) {
      try {
        await this.notifyForPermission()
        await this.requestPermission()
      } catch (error) {
        await this.getPermissions()
      }
    }
  }

  private async hasPermission () {
    return new Promise((resolve) => {
      this.bleDevice.hasPermission(result => {
        resolve(result.hasPermission)
      })
    })
  }

  private async requestPermission () {
    return new Promise((resolve, reject) => {
      this.bleDevice.requestPermission(result => {
        result.requestPermission ? resolve() : reject()
      })
    })
  }

  private async notifyForPermission () {
    await this.sendNotification({
      title: 'Location Permission Required',
      message: 'For your secruity, Android requires applications using Bluetooth Smart technologies to request "Location Permission" from users.\n\nTo use this app, you must grant location permissions for this app on the next screen.',
      buttonName: 'CONTINUE'
    })
  }

  private async getLocation () {
    if (!(await this.isLocationEnabled())) {
      try {
        await this.notifyForLocation()
        await this.requestLocation()
      } catch (error) {
        await this.getLocation()
      }
    }
  }

  private async isLocationEnabled () {
    return new Promise((resolve, reject) => {
      this.bleDevice.isLocationEnabled(result => {
        resolve(result.isLocationEnabled)
      }, reject)
    })
  }

  private async requestLocation () {
    return new Promise((resolve, reject) => {
      this.bleDevice.requestLocation(result => {
        result.requestLocation ? resolve() : reject()
      }, reject)
    })
  }

  private async notifyForLocation () {
    await this.sendNotification({
      title: 'Location Services Disabled',
      message: 'Your location services are currently disabled.\n\nPlease enable location services in order to use this app.',
      buttonName: 'CONTINUE'
    })
  }

  private sendNotification ({ title, message, buttonName }: { title: string, message: string, buttonName: string }) {
    return new Promise((resolve, reject) => {
      const cb = () => { resolve() }
      navigator.notification.alert(message, cb, title, buttonName)
    })
  }
}