package ch.threema.app.voip.groupcall.sfu

import androidx.annotation.UiThread
import ch.threema.app.voip.groupcall.sfu.webrtc.LocalCtx
import ch.threema.app.voip.groupcall.sfu.webrtc.RemoteCtx
import ch.threema.app.webrtc.Camera
import ch.threema.base.utils.getThreemaLogger
import ch.threema.data.datatypes.ContactNameFormat
import ch.threema.domain.types.Identity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.webrtc.SurfaceViewRenderer

private val logger = getThreemaLogger("Participant")

@JvmInline
value class ParticipantId(val id: UInt) {
    init {
        if (id >= MIDS_MAX.toUInt()) {
            throw Error("Not a valid participant id: $id")
        }
    }
}

typealias DetachSinkFn = () -> Unit

interface ParticipantDescription {
    val id: ParticipantId
}

interface NormalParticipantDescription : ParticipantDescription {
    val identity: Identity
    val nickname: String
}

data class SimpleNormalParticipantDescription(
    override val id: ParticipantId,
    override val identity: Identity,
    override val nickname: String,
) : NormalParticipantDescription {
    override fun toString(): String {
        return "SimpleNormalParticipantDescription(id=$id)"
    }
}

interface GuestParticipantDescription : ParticipantDescription {
    val name: String
}

data class SimpleGuestParticipantDescription(
    override val id: ParticipantId,
    override val name: String,
) : GuestParticipantDescription

abstract class Participant(override val id: ParticipantId) : ParticipantDescription {
    open val mirrorRenderer: Boolean = false

    abstract val type: String

    abstract fun getDisplayName(contactNameFormat: ContactNameFormat): String

    open var microphoneActive: Boolean = false

    open var cameraActive: Boolean = false

    open var screenShareActive: Boolean = false

    @UiThread
    abstract fun subscribeCamera(
        renderer: SurfaceViewRenderer,
        width: Int,
        height: Int,
        fps: Int = 30,
    ): DetachSinkFn

    @UiThread
    abstract fun unsubscribeCamera()

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is Participant) return false

        if (id != other.id) return false

        return true
    }

    override fun hashCode(): Int {
        return id.hashCode()
    }
}

private interface RemoteParticipant {
    var remoteCtx: RemoteCtx?
}

abstract class NormalParticipant(
    id: ParticipantId,
    private val displayableParticipant: DisplayableParticipant,
) : Participant(id), NormalParticipantDescription {

    override val identity = displayableParticipant.identity
    override val nickname = displayableParticipant.nickname

    override fun getDisplayName(contactNameFormat: ContactNameFormat) = displayableParticipant.getDisplayName(contactNameFormat)
}

abstract class NormalRemoteParticipant(
    id: ParticipantId,
    displayableParticipant: DisplayableParticipant,
) : RemoteParticipant, NormalParticipant(
    id = id,
    displayableParticipant = displayableParticipant,
) {
    override var remoteCtx: RemoteCtx? = null
}

class LocalParticipant internal constructor(
    id: ParticipantId,
    displayableParticipant: DisplayableParticipant,
    private val localCtx: LocalCtx,
) : NormalParticipant(id, displayableParticipant) {
    override val type = "LocalParticipant"

    override val mirrorRenderer: Boolean
        get() = localCtx.cameraVideoContext.currentCamera?.facing == Camera.Facing.FRONT

    override var microphoneActive: Boolean = false
        set(value) {
            logger.info("Set microphone active={}", value)
            field = value
            localCtx.microphoneAudioContext.active = field
        }

    override var cameraActive: Boolean = false
        set(value) {
            logger.info("Set camera active={}", value)
            field = value
            CoroutineScope(Dispatchers.Main).launch {
                if (field) {
                    localCtx.cameraVideoContext.startCapturing()
                } else {
                    localCtx.cameraVideoContext.stopCapturing()
                }
            }
        }

    // TODO(ANDR-4127): Implement screensharing on android
    override var screenShareActive: Boolean = false

    @UiThread
    override fun subscribeCamera(
        renderer: SurfaceViewRenderer,
        width: Int,
        height: Int,
        fps: Int,
    ): DetachSinkFn {
        logger.trace("Subscribe local camera")
        return localCtx.cameraVideoContext.renderTo(renderer)
    }

    @UiThread
    override fun unsubscribeCamera() {
        // no-op: Detach is performed in [DetachSinkFn] returned from [#subscribeCamera]
    }

    @UiThread
    suspend fun flipCamera() {
        try {
            localCtx.cameraVideoContext.flipCamera()
        } catch (e: Exception) {
            logger.warn("Could not toggle front/back camera", e)
        }
    }
}

// Not relevant in V1
// abstract class GuestParticipant(
//         id: ParticipantId,
//         override val name: String
// ) : Participant(id), RemoteParticipant, GuestParticipantDescription
