package com.getbubblenow.android

import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.Icon
import android.os.Build
import android.os.IBinder
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.databinding.Observable
import androidx.databinding.Observable.OnPropertyChangedCallback
import com.getbubblenow.android.activity.MainWireGuardActivity
import com.getbubblenow.android.activity.TunnelToggleActivity
import com.wireguard.android.backend.Tunnel
import com.getbubblenow.android.model.ObservableTunnel
import com.getbubblenow.android.widget.SlashDrawable

/**
 * Service that maintains the application's custom Quick Settings tile. This service is bound by the
 * system framework as necessary to update the appearance of the tile in the system UI, and to
 * forward click events to the application.
 */
@RequiresApi(Build.VERSION_CODES.N)
class QuickTileService : TileService() {
    private val onStateChangedCallback = OnStateChangedCallback()
    private val onTunnelChangedCallback = OnTunnelChangedCallback()
    private var iconOff: Icon? = null
    private var iconOn: Icon? = null
    private var tunnel: ObservableTunnel? = null

    /* This works around an annoying unsolved frameworks bug some people are hitting. */
    override fun onBind(intent: Intent): IBinder? {
        var ret: IBinder? = null
        try {
            ret = super.onBind(intent)
        } catch (e: Exception) {
            Log.d(TAG, "Failed to bind to TileService", e)
        }
        return ret
    }

    override fun onClick() {
        if (tunnel != null) {
            unlockAndRun {
                val tile = qsTile
                if (tile != null) {
                    tile.icon = if (tile.icon == iconOn) iconOff else iconOn
                    tile.updateTile()
                }
                tunnel!!.setStateAsync(Tunnel.State.TOGGLE).whenComplete { _, t ->
                    if (t == null) {
                        updateTile()
                    } else {
                        val toggleIntent = Intent(this, TunnelToggleActivity::class.java)
                        toggleIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                        startActivity(toggleIntent)
                    }
                }
            }
        } else {
            val intent = Intent(this, MainWireGuardActivity::class.java)
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            startActivityAndCollapse(intent)
        }
    }

    override fun onCreate() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            iconOn = Icon.createWithResource(this, R.drawable.ic_tile)
            iconOff = iconOn
            return
        }
        val icon = SlashDrawable(resources.getDrawable(R.drawable.ic_tile, Application.get().theme))
        icon.setAnimationEnabled(false) /* Unfortunately we can't have animations, since Icons are marshaled. */
        icon.setSlashed(false)
        var b = Bitmap.createBitmap(icon.intrinsicWidth, icon.intrinsicHeight, Bitmap.Config.ARGB_8888)
                ?: return
        var c = Canvas(b)
        icon.setBounds(0, 0, c.width, c.height)
        icon.draw(c)
        iconOn = Icon.createWithBitmap(b)
        icon.setSlashed(true)
        b = Bitmap.createBitmap(icon.intrinsicWidth, icon.intrinsicHeight, Bitmap.Config.ARGB_8888)
                ?: return
        c = Canvas(b)
        icon.setBounds(0, 0, c.width, c.height)
        icon.draw(c)
        iconOff = Icon.createWithBitmap(b)
    }

    override fun onStartListening() {
        Application.getTunnelManager().addOnPropertyChangedCallback(onTunnelChangedCallback)
        if (tunnel != null) tunnel!!.addOnPropertyChangedCallback(onStateChangedCallback)
        updateTile()
    }

    override fun onStopListening() {
        if (tunnel != null) tunnel!!.removeOnPropertyChangedCallback(onStateChangedCallback)
        Application.getTunnelManager().removeOnPropertyChangedCallback(onTunnelChangedCallback)
    }

    private fun updateTile() {
        // Update the tunnel.
        val newTunnel = Application.getTunnelManager().lastUsedTunnel
        if (newTunnel != tunnel) {
            if (tunnel != null) tunnel!!.removeOnPropertyChangedCallback(onStateChangedCallback)
            tunnel = newTunnel
            if (tunnel != null) tunnel!!.addOnPropertyChangedCallback(onStateChangedCallback)
        }
        // Update the tile contents.
        val label: String
        val state: Int
        val tile = qsTile
        if (tunnel != null) {
            label = tunnel!!.name
            state = if (tunnel!!.state == Tunnel.State.UP) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE
        } else {
            label = getString(R.string.app_name)
            state = Tile.STATE_INACTIVE
        }
        if (tile == null) return
        tile.label = label
        if (tile.state != state) {
            tile.icon = if (state == Tile.STATE_ACTIVE) iconOn else iconOff
            tile.state = state
        }
        tile.updateTile()
    }

    private inner class OnStateChangedCallback : OnPropertyChangedCallback() {
        override fun onPropertyChanged(sender: Observable, propertyId: Int) {
            if (sender != tunnel) {
                sender.removeOnPropertyChangedCallback(this)
                return
            }
            if (propertyId != 0 && propertyId != BR.state) return
            updateTile()
        }
    }

    private inner class OnTunnelChangedCallback : OnPropertyChangedCallback() {
        override fun onPropertyChanged(sender: Observable, propertyId: Int) {
            if (propertyId != 0 && propertyId != BR.lastUsedTunnel) return
            updateTile()
        }
    }

    companion object {
        private const val TAG = "WireGuard/QuickTileService"
    }
}
