/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.amnezia.vpn
import android.os.Binder
import android.os.DeadObjectException
import android.os.IBinder
import android.os.Parcel
import com.wireguard.config.*
import org.json.JSONObject
import java.lang.Exception

class VPNServiceBinder(service: VPNService) : Binder() {

    private val mService = service
    private val tag = "VPNServiceBinder"
    private var mListener: IBinder? = null
    private var mResumeConfig: JSONObject? = null
    private var mImportedConfig: String? = null

    /**
    * The codes this Binder does accept in [onTransact]
    */
    object ACTIONS {
        const val activate = 1
        const val deactivate = 2
        const val registerEventListener = 3
        const val requestStatistic = 4
        const val requestGetLog = 5
        const val requestCleanupLog = 6
        const val resumeActivate = 7
        const val setNotificationText = 8
        const val setFallBackNotification = 9
        const val importConfig = 11
    }

    /**
    * Gets called when the VPNServiceBinder gets a request from a Client.
    * The [code] determines what action is requested. - see [ACTIONS]
    * [data] may contain a utf-8 encoded json string with optional args or is null.
    * [reply] is a pointer to a buffer in the clients memory, to reply results.
    * we use this to send result data.
    *
    * returns true if the [code] was accepted
    */
    override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
        Log.i(tag, "GOT TRANSACTION " + code)

        when (code) {
            ACTIONS.activate -> {
                try {
                    Log.i(tag, "Activation Requested, parsing Config")
                    // [data] is here a json containing the wireguard/openvpn conf
                    val buffer = data.createByteArray()
                    val json = buffer?.let { String(it) }
                    val config = JSONObject(json)
                    Log.v(tag, "Stored new Tunnel config in Service")
                    Log.i(tag, "Config: $config")
                    if (!mService.checkPermissions()) {
                        mResumeConfig = config
                        // The Permission prompt was already
                        // send, in case it's accepted we will
                        // receive ACTIONS.resumeActivate
                        return true
                    }
                    this.mService.turnOn(config)
                } catch (e: Exception) {
                    Log.e(tag, "An Error occurred while enabling the VPN: ${e.localizedMessage}")
                    dispatchEvent(EVENTS.activationError, e.localizedMessage)
                }
                return true
            }

            ACTIONS.resumeActivate -> {
            // [data] is empty
            // Activate the current tunnel
                Log.i(tag, "resume activate")
                try {
                    mResumeConfig?.let { this.mService.turnOn(it) }
                } catch (e: Exception) {
                    Log.e(tag, "An Error occurred while enabling the VPN: ${e.localizedMessage}")
                }
                return true
        }

            ACTIONS.deactivate -> {
                // [data] here is empty
                this.mService.turnOff()
                return true
            }

            ACTIONS.registerEventListener -> {
                Log.i(tag, "register: start")
                // [data] contains the Binder that we need to dispatch the Events
                val binder = data.readStrongBinder()
                mListener = binder
                val obj = JSONObject()
                obj.put("connected", mService.isUp)
                obj.put("time", mService.connectionTime)
                dispatchEvent(EVENTS.init, obj.toString())

                ////
                if (mImportedConfig != null) {
                    Log.i(tag, "register: config not null")
                    dispatchEvent(EVENTS.configImport, mImportedConfig)
                    mImportedConfig = null
                } else {
                    Log.i(tag, "register: config is null")
                }

                return true
            }

            ACTIONS.requestStatistic -> {
                dispatchEvent(EVENTS.statisticUpdate, mService.status.toString())
                return true
            }

            ACTIONS.requestGetLog -> {
                // Grabs all the Logs and dispatch new Log Event
                dispatchEvent(EVENTS.backendLogs, Log.getContent())
                return true
            }

            ACTIONS.requestCleanupLog -> {
                Log.clearFile()
                return true
            }

            ACTIONS.setNotificationText -> {
                NotificationUtil.update(data)
                return true
            }

            ACTIONS.setFallBackNotification -> {
                NotificationUtil.saveFallBackMessage(data, mService)
                return true
            }

            ACTIONS.importConfig -> {
                val buffer = data.readString()

                val obj = JSONObject()
                obj.put("config", buffer)

                val resultString = obj.toString()

                Log.i(tag, "Transact import config request")

                if (mListener != null) {
                    dispatchEvent(EVENTS.configImport, resultString)
                } else {
                    mImportedConfig = resultString
                }
            }

            IBinder.LAST_CALL_TRANSACTION -> {
                Log.e(tag, "The OS Requested to shut down the VPN")
                this.mService.turnOff()
                return true
            }

            else -> {
                Log.e(tag, "Received invalid bind request \t Code -> $code")
                // If we're hitting this there is probably something wrong in the client.
                return false
            }
        }

        return false
    }

    /**
    * Dispatches an Event to all registered Binders
    * [code] the Event that happened - see [EVENTS]
    * To register an Eventhandler use [onTransact] with
    * [ACTIONS.registerEventListener]
    */
    fun dispatchEvent(code: Int, payload: String?) {
        try {
            mListener?.let {
                if (it.isBinderAlive) {
                    val data = Parcel.obtain()
                    data.writeByteArray(payload?.toByteArray(charset("UTF-8")))
                    it.transact(code, data, Parcel.obtain(), 0)
                } else {
                    Log.i(tag, "Dispatching event: binder NOT alive")
                }
            }
        } catch (e: DeadObjectException) {
            // If the QT Process is killed (not just inactive)
            // we cant access isBinderAlive, so nothing to do here.
        }
    }

    /**
    *  The codes we Are Using in case of [dispatchEvent]
    *  Qt codes in the androidvpnactivity.h
    */
    object EVENTS {
        const val init = 0
        const val connected = 1
        const val disconnected = 2
        const val statisticUpdate = 3
        const val backendLogs = 4
        const val activationError = 5
        const val permissionRequired = 6
        const val configImport = 7
    }
}
