/**
 * Copyright (c) 2018 NXP
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * o Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.nxp.vizncompanionapp.viewModel

import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
import android.content.Context
import android.net.ConnectivityManager
import android.net.wifi.WifiManager
import com.nxp.vizncompanionapp.interfaces.DeviceList
import com.nxp.vizncompanionapp.model.Device
import org.json.JSONObject
import android.os.Handler
import android.os.Looper
import android.text.format.Formatter
import android.util.Log
import com.nxp.vizncompanionapp.R
import com.nxp.vizncompanionapp.activities.WifiListingActivity
import com.nxp.vizncompanionapp.utility.*
import java.lang.Exception

class DeviceViewModel : ViewModel() {

    private var mAllDevices: MutableLiveData<List<Device>>? = MutableLiveData()
    private var udpReceiveProtocol: UdpRecieveProtocol? = null
    private var mDeviceList: MutableList<Device>? = ArrayList()
    private var mAddress: String = ""
    private var mDeviceManager: DeviceManagement = DeviceManagement()
    private var mDisplayNotifications: DisplayNotifications = DisplayNotifications()
    private var handler: Handler? = null
    private var handlerSend: Handler? = null
    private var handlerRequest: Handler? = null
    private var handlerWiFiRequest: Handler? = null
    private var handlerResponse: Handler? = null
    var runnable: MyRunnable? = null
    var runnableSend: MyRunnableSend? = null
    var runnableRequest: MyRunnableRequest? = null
    var runnableWiFiRequest: MyRunnableWiFiRequest? = null
    var responseRunnable: MyResponseRunnable? = null
    var prefeernce: PreferencesHelper? = null
    var isbroadcastRunning: Boolean = false


    /**
     * this runnable display dialog when linking timeout is occurred
     *
     * @param context Context to display dialog
     */
    inner class MyResponseRunnable(context: Context) : Runnable {
        var context: Context? = null

        init {
            this.context = context
        }

        override fun run() {
            stopSocket()
            mDisplayNotifications.displayErrorDialog(context,context?.getString(R.string.linking_timeout),context?.getString(R.string.try_again),context?.getString(R.string.cancel))
        }
    }

    /**
     * this runnable display dialog when no device found after broadcast
     *
     * @param context Context to display dialog
     */
    inner class MyRunnable(context: Context) : Runnable {
        var context: Context? = null

        init {
            this.context = context
        }

        override fun run() {
            stopSocket()
            mDisplayNotifications.dismissDialog()
            mDisplayNotifications.emptyDataDialog(context,context?.getString(R.string.empty_list),context?.getString(R.string.try_again),context?.getString(R.string.cancel))
        }
    }

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

        init {
            this.context = context
        }

        override fun run() {
            isbroadcastRunning = false
            if(mDeviceList?.isNotEmpty()!!){
                mDisplayNotifications.dismissDialog()
            }
        }
    }

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

        init {
            this.context = context
        }

        override fun run() {
            mDisplayNotifications.dismissDialog()
            mDisplayNotifications.displayErrorDialog(context, context?.getString(R.string.confirm_request_fail_msg),context?.getString(R.string.try_again),context?.getString(R.string.cancel))
        }
    }

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

        init {
            this.context = context
        }

        override fun run() {
            stopSocket()
            mDisplayNotifications.dismissDialog()
            mDisplayNotifications.displayLinkingProcessDialog(context, context?.getString(R.string.wifi_device_msg)!!)
        }
    }

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

    /**
     * this is the method which stores the device in list coming from UDP listener
     *
     * @param context Context of Activity
     */
    fun loadDevices(context: Context) {
        udpReceiveProtocol = UdpRecieveProtocol(AppConstant.UDP_PORT, object : DeviceList {
            override fun addDevice(device: Device) {
                Log.d("CMLTD", device.mDeviceAddress)
                updateDeviceDetails(device, context)
            }
        })
        udpReceiveProtocol?.start()
    }

    /**
     * this method update the device list when any new device is coming
     *
     * @param device Device which is coming from UDP listener
     * @param context Context of Activity
     */
    fun updateDeviceDetails(device: Device, context: Context) {
        checkDeviceStatus(device,context)
        var mExist: Boolean = mDeviceManager.checkDeviceInList(mDeviceList, device)

        if (!mExist) {
            mDeviceList?.add(device)

            mAllDevices?.postValue(mDeviceList)
            if(!isbroadcastRunning)
            mDisplayNotifications.dismissDialog()
            removeHandler()
            removeHandlerRequest()
            removeHandlerWiFiRequest()
        } else {
            var index: Int = mDeviceManager.getDeviceIndexInList(mDeviceList, device)
            var newDevice: Device? = mDeviceManager.mergeDeviceValueInList(mDeviceList?.get(index), device)
            mDeviceList?.set(index, newDevice!!)
            mAllDevices?.postValue(mDeviceList)
            if(!isbroadcastRunning)
            mDisplayNotifications.dismissDialog()
            removeHandler()
            removeHandlerRequest()
            removeHandlerWiFiRequest()
        }
    }

    fun displayLinkingProcessDialog(context: Context){
        mDisplayNotifications.displayLinkingProcessDialog(context,context.getString(R.string.linking_process))
    }

    fun displayBatterySaverDialog(context: Context){
        mDisplayNotifications.displayBatterySaverDialog(context)
    }

    /**
     * this method is check the device state when linking process is running of device with AVS. we put timer of 1 minute between two response.
     *
     * @param device Device which is coming from UDP listener
     * @param context Context of Activity
     */
    fun checkDeviceStatus(device: Device, context: Context){
        this.prefeernce = PreferencesHelper(context)
        if (device.mDeviceStatus != "") {
            removeHandler()
            if (device.mDeviceStatus == AppConstant.DEVICE_STATUS_CONNECTION_COMPLETE || device.mDeviceStatus == AppConstant.DEVICE_STATUS_CONNECTION_ERROR) {
                removeResponseHandler()
            } else {
                if (handlerResponse == null) {
                    handlerResponse = Handler(Looper.getMainLooper())
                    responseRunnable = MyResponseRunnable(context)
                    handlerResponse?.postDelayed(responseRunnable, AppConstant.DEVICE_RESPONSE_TIMEOUT)
                } else {
                    removeResponseHandler()
                    handlerResponse = Handler(Looper.getMainLooper())
                    responseRunnable = MyResponseRunnable(context)
                    handlerResponse?.postDelayed(responseRunnable, AppConstant.DEVICE_RESPONSE_TIMEOUT)
                }
            }
        }
        if(device.mDeviceStatus == AppConstant.DEVICE_STATUS_CONNECTION_COMPLETE || device.mDeviceStatus == AppConstant.DEVICE_STATUS_CONNECTION_ERROR || device.mDeviceStatus == ""){
            prefeernce?.savePreferences(AppConstant.SHARED_KEY_LINKING_PROCESS,false)
        } else {
            prefeernce?.savePreferences(AppConstant.SHARED_KEY_LINKING_PROCESS,true)
        }
    }

    /**
     * stop UDP listener socket
     */
    fun stopSocket() {
        mDisplayNotifications.dismissDialog()
        removeHandler()
        if (udpReceiveProtocol != null) {
            udpReceiveProtocol?.setRunning(false)
            udpReceiveProtocol = null
        }
    }

    /**
     * remove callbacks of device state check handler
     */
    fun removeResponseHandler() {
        if (handlerResponse != null) {
            handlerResponse?.removeCallbacks(responseRunnable)
            responseRunnable = null
            handlerResponse = null
        }
    }

    /**
     * remove callbacks of empty list handler
     */
    fun removeHandler() {
        if (handler != null) {
            handler?.removeCallbacks(runnable)
            runnable = null
            handler = null
        }
    }

    fun removeHandlerSend() {
        if (handlerSend != null) {
            handlerSend?.removeCallbacks(runnableSend)
            runnableSend = null
            handlerSend = null
        }
    }

    fun removeHandlerRequest() {
        if (handlerRequest != null) {
            handlerRequest?.removeCallbacks(runnableRequest)
            runnableRequest = null
            handlerRequest = null
        }
    }

    fun removeHandlerWiFiRequest() {
        if (handlerWiFiRequest != null) {
            handlerWiFiRequest?.removeCallbacks(runnableWiFiRequest)
            runnableWiFiRequest = null
            handlerWiFiRequest = null
        }
    }

    /**
     * get IP address of device which is trying to link with AVS
     *
     * @param address Address of selected device
     */
    fun getIPAddress(address: String) {
        this.mAddress = address
    }

    /**
     * send UDP broadcast message to get device List which is in LAN
     *
     * @param context Context of Activity
     */
    fun sendBroadcast(context: Context) {
        Log.d("CMLTD", "broadcast")
        this.prefeernce = PreferencesHelper(context)
        mDeviceList?.clear()
        mAllDevices?.postValue(mDeviceList)
        mDisplayNotifications.fetchData(context)
        prefeernce?.savePreferences(AppConstant.SHARED_KEY_LINKING_PROCESS,false)
        isbroadcastRunning = true
        mDeviceManager?.sendBroadcast(context)
        removeHandler()
        handler = Handler(Looper.getMainLooper())
        runnable = MyRunnable(context)
        handler?.postDelayed(runnable, AppConstant.DEVICE_SCAN_TIMEOUT)
        removeHandlerSend()
        handlerSend = Handler(Looper.getMainLooper())
        runnableSend = MyRunnableSend(context)
        handlerSend?.postDelayed(runnableSend,AppConstant.DEVICE_BROADCAST_TIMEOUT)
    }

    /**
     * send Authentication data to particular device which wants to linked with AVS
     *
     * @param authorizationCode AuthCode of device
     * @param redirectUri Redirect URL of device
     * @param clientId ClientID of device
     * @param context Context of Activity
     */
    fun sendAuthCode(authorizationCode: String, redirectUri: String, clientId: String, context: Context) {
        this.prefeernce = PreferencesHelper(context)
        var json = JSONObject()
        json.put(AppConstant.DEVICE_AUTH_CODE, authorizationCode)
        json.put(AppConstant.DEVICE_REDIRECT_URL, redirectUri)
        json.put(AppConstant.DEVICE_CLIENT_ID, clientId)
        prefeernce?.savePreferences(AppConstant.SHARED_KEY_LINKING_PROCESS,true)
        mDeviceManager?.sendAuthCode(json,mAddress)
        updateDeviceDetails(Device("", "", "", "", mAddress, AppConstant.DEVICE_SEND_AUTH_TO_PRODUCT,""),context)
    }

    fun getBoardIpAddress(context: Context) {
        //this.prefeernce = PreferencesHelper(context)
        var json = JSONObject()
        json.put(AppConstant.DEVICE_AUTH_CODE, 2)
        json.put(AppConstant.DEVICE_REDIRECT_URL, "tralala")
        json.put(AppConstant.DEVICE_CLIENT_ID, "rosa")
        //prefeernce?.savePreferences(AppConstant.SHARED_KEY_LINKING_PROCESS,true)
        mDeviceManager?.sendAuthCode(json,mAddress)
        //updateDeviceDetails(Device("", "", "", "", mAddress, AppConstant.DEVICE_SEND_AUTH_TO_PRODUCT,""),context)
    }


    fun sendMsgToWiFiConnectedDevice(context: Context,mAddress: String){
        this.prefeernce = PreferencesHelper(context)
        mDeviceList?.clear()
        mAllDevices?.postValue(mDeviceList)
        var json = JSONObject()
        mDisplayNotifications.fetchData(context)
        json.put("State", AppConstant.BROADCAST_MSG)
        json.put("Mode","AP Mode")
        prefeernce?.savePreferences(AppConstant.SHARED_KEY_LINKING_PROCESS, false)
        mDeviceManager?.sendMsgToWiFiDevice(json, mAddress)
        removeHandlerWiFiRequest()
        handlerWiFiRequest = Handler(Looper.getMainLooper())
        runnableWiFiRequest = MyRunnableWiFiRequest(context)
        handlerWiFiRequest?.postDelayed(runnableWiFiRequest, AppConstant.DEVICE_RESPONSE_TIMEOUT)
    }

    fun stopTimer(){
        mDeviceManager?.stopTimer()
    }

    fun sendWiFiCredentials(context: Context, mAddress: String, ssid: String, password: String, type: String){
        this.prefeernce = PreferencesHelper(context)
        mDisplayNotifications.fetchData(context)
        mDeviceList?.clear()
        mAllDevices?.postValue(mDeviceList)
        var json = JSONObject()
        json.put("SSID",ssid)
        json.put("Password",password)
        prefeernce?.savePreferences(AppConstant.SHARED_KEY_LINKING_PROCESS, false)
        mDeviceManager?.sendCredToWiFiDevice(json,mAddress)
        Handler(Looper.getMainLooper()).postDelayed({
            mDisplayNotifications?.dismissDialog()
            if (context is WifiListingActivity){
                if(type == "update_mode"){
                    mDisplayNotifications?.displayLinkingProcessDialog(context,context.getString(R.string.cred_update_msg))
                } else {
                    mDisplayNotifications?.displayErrorDialog(context, context.getString(R.string.wifi_setting_msg, ssid), context.getString(R.string.settings), context.getString(R.string.cancel))
                }
            }
        }, AppConstant.DEVICE_SEND_CRED_TIMEOUT)

    }

    fun getIpAddOfWiFi(context: Context, s: String) : String {
        return try {
            val wifiMgr = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
            val wifiInfo = wifiMgr.dhcpInfo
            val ip = wifiInfo.gateway
            var ssid = wifiMgr.connectionInfo.ssid
            var ipAddress = Formatter.formatIpAddress(ip)
            Log.e("TAG",ipAddress+"")
            if(s == "ipAddress") {
                ipAddress
            } else {
                ssid
            }
        } catch (e: Exception){
            var ssid = ""
            var ipAddress = "0.0.0.0"
            if(s == "ipAddress") {
                ipAddress
            } else {
                ssid
            }
        }
    }

    fun checkWiFiConnection(context: Context): Boolean {
        val connManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val mWifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI)
        return mWifi.isConnected
    }

    /**
     * send confirmation request to selected device for authentication
     *
     * @param context Context of Activity
     */
    fun sendConfirmationRequest(context: Context){
        mDisplayNotifications.fetchData(context)
        var mExist: Boolean = mDeviceManager.checkDeviceState(mDeviceList, AppConstant.DEVICE_STATE_CONFIRM_RESPONSE)
        if(mExist){
            var index: Int = mDeviceManager.getConfirmDeviceIndexInList(mDeviceList, AppConstant.DEVICE_STATE_CONFIRM_RESPONSE)
            var newDevice: Device? = mDeviceManager.updateStateValueInList(mDeviceList?.get(index)!!)
            mDeviceList?.set(index, newDevice!!)
            mAllDevices?.postValue(mDeviceList)
        }
        Handler(Looper.getMainLooper()).postDelayed({
            this.prefeernce = PreferencesHelper(context)
            var json = JSONObject()
            json.put("State", AppConstant.DEVICE_CONFIRM_REQUEST_MSG)
            prefeernce?.savePreferences(AppConstant.SHARED_KEY_LINKING_PROCESS, false)
            mDeviceManager?.sendConfirmCode(json, mAddress)
            //updateDeviceDetails(Device("", "", "",  AppConstant.DEVICE_STATE_CONFIRM_REQUEST, mAddress,""),context)
            removeHandlerRequest()
            handlerRequest = Handler(Looper.getMainLooper())
            runnableRequest = MyRunnableRequest(context)
            handlerRequest?.postDelayed(runnableRequest, AppConstant.DEVICE_RESPONSE_TIMEOUT)
        }, 500)
    }
}