@file:Suppress("unused")

package de.sebse.fuplanner2.utils

import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import android.text.Html
import android.text.Spanned
import android.text.format.DateFormat
import android.util.Log
import androidx.annotation.ColorInt
import androidx.annotation.PluralsRes
import androidx.annotation.StringRes
import androidx.lifecycle.LiveData
import androidx.work.*
import de.sebse.fuplanner2.R
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.roundToInt
import kotlin.random.Random


object console {
    fun log(vararg obj: Any?) {
        largeLog({ tag, msg -> Log.d(tag, msg) }, "KTConsole", obj.joinToString(" "))
    }

    fun warn(vararg obj: Any?) {
        largeLog({ tag, msg -> Log.w(tag, msg) }, "KTConsole", obj.joinToString(" "))
    }

    fun error(vararg obj: Any?) {
        largeLog({ tag, msg -> Log.e(tag, msg) }, "KTConsole", obj.joinToString(" "))
    }

    private fun largeLog(
        method: (String?, String) -> Unit,
        tag: String,
        content: String
    ) {
        if (content.length > 4000) {
            method(tag, content.substring(0, 4000))
            largeLog(method, tag, content.substring(4000))
        } else {
            method(tag, content)
        }
    }
}

object xml {
    fun decode(xml: String): String {
        return if (Build.VERSION.SDK_INT >= 24) {
            Html.fromHtml(xml , Html.FROM_HTML_MODE_LEGACY).toString()
        } else {
            @Suppress("DEPRECATION")
            Html.fromHtml(xml).toString()
        }
    }
}

object color {
    @ColorInt
    fun getColor(actCtx: Context, seed: Long, highContrast: Boolean = false): Int {
        var h = Random(seed).nextInt(0xFFFF)
        h = h * 360 / 0xffff
        //int s = 0xff & encodedHash[2];
        //s = s * 100 / 0xffff;
        //int v = 0xff & encodedHash[3];
        //v = v * 100 / 0xffff;

        // range for more beautiful colors
        h = h / 30 * 30
        val isNightMode = actCtx.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
        val doDarkColors = if (highContrast) !isNightMode else isNightMode
        val (s, v) =
            if (doDarkColors)
                Pair(100, 50)//Pair(100, 80)
            else
                Pair(100, 70)
        val (r, g, b) = hsvToRgb(h / 360.0, s / 100.0, v / 100.0)
        return rgbToColorInt(r, g, b)
    }

    @ColorInt
    private fun rgbToColorInt(
        r: Double,
        g: Double,
        b: Double
    ): Int {
        return 0xFF000000.toInt() + (r*0xFF).roundToInt() * 0x10000 + (g*0xFF).roundToInt() * 0x100 + (b*0xFF).roundToInt()
    }

    private fun hsvToRgb(
        hue: Double,
        saturation: Double,
        value: Double
    ): Triple<Double, Double, Double> {
        val h = (hue * 6).toInt()
        val f = hue * 6 - h
        val p = value * (1 - saturation)
        val q = value * (1 - f * saturation)
        val t = value * (1 - (1 - f) * saturation)
        return when (h) {
            0 -> Triple(value, t, p)
            1 -> Triple(q, value, p)
            2 -> Triple(p, value, t)
            3 -> Triple(p, q, value)
            4 -> Triple(t, p, value)
            5 -> Triple(value, p, q)
            else -> Triple(0.0, 0.0, 0.0)
        }
    }
}

inline fun <reified T: ListenableWorker> enqueueOneTimeWork(appCtx: Context, workBuilder: (OneTimeWorkRequest.Builder) -> OneTimeWorkRequest.Builder): LiveData<WorkInfo> {
    val work = workBuilder(OneTimeWorkRequestBuilder<T>()).build()
    val workManager = WorkManager.getInstance(appCtx)
    workManager.enqueue(work)
    return workManager.getWorkInfoByIdLiveData(work.id)
}

fun <A, B>List<A>.pmap(f: suspend (A) -> B): List<B> = runBlocking {
    map { async { f(it) } }.map { it.await() }
}

fun hashCodeOf(vararg elements: Any?): Int {
    var code = 0
    elements.forEach { code = code*31 + (it?.hashCode() ?: 0) }
    return code
}

inline fun <reified T> cast(any: Any?) : T? = any as? T?


fun String.toHtmlSpan(): Spanned = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    Html.fromHtml(this, Html.FROM_HTML_MODE_LEGACY)
} else {
    @Suppress("DEPRECATION")
    Html.fromHtml(this)
}

fun Context.getHtmlSpannedString(@StringRes id: Int): Spanned = getString(id).toHtmlSpan()

fun Context.getHtmlSpannedString(@StringRes id: Int, vararg formatArgs: Any?): Spanned = getString(id, *formatArgs).toHtmlSpan()

fun Context.getQuantityString(@PluralsRes id: Int, quantity: Int): String = resources.getQuantityString(id, quantity)

fun Context.getQuantityString(@PluralsRes id: Int, quantity: Int, vararg formatArgs: Any?): String = resources.getQuantityString(id, quantity, *formatArgs)

fun Context.getQuantityHtmlSpannedString(@PluralsRes id: Int, quantity: Int): Spanned = getQuantityString(id, quantity).toHtmlSpan()

fun Context.getQuantityHtmlSpannedString(@PluralsRes id: Int, quantity: Int, vararg formatArgs: Any?): Spanned = getQuantityString(id, quantity, *formatArgs).toHtmlSpan()




fun Long.timeAgoString(actCtx: Context): String {
    val SECOND_MILLIS = 1000
    val MINUTE_MILLIS = 60 * SECOND_MILLIS
    val HOUR_MILLIS = 60 * MINUTE_MILLIS
    val DAY_MILLIS = 24 * HOUR_MILLIS

    val now = System.currentTimeMillis()
    if (this > now || this <= 0) {
        return ""
    }
    val diff = now - this
    return when {
        diff < MINUTE_MILLIS -> actCtx.getString(R.string.time_just_now)
        diff < 60 * MINUTE_MILLIS -> actCtx.getQuantityString(R.plurals.time_minutes_ago, (diff / MINUTE_MILLIS).toInt(), diff / MINUTE_MILLIS)
        diff < 24 * HOUR_MILLIS -> actCtx.getQuantityString(R.plurals.time_hours_ago, (diff / HOUR_MILLIS).toInt(), diff / HOUR_MILLIS)
        else -> actCtx.getQuantityString(R.plurals.time_days_ago, (diff / DAY_MILLIS).toInt(), diff / DAY_MILLIS)
    }
}


fun Long.toDateTimeString(context: Context): String? {
    return toDateString(context, "dd.MM.yy hh:mm")
}

fun Long.toTimeString(context: Context): String? {
    return toDateString(context, "hh:mm")
}

fun Long.toDateString(context: Context): String? {
    return toDateString(context, "dd.MM.yy")
}

fun Long.toDateString(
    context: Context,
    skeleton: String
): String? {
    return this.toDateString(context, Locale.getDefault(), skeleton)
}

@SuppressLint("SimpleDateFormat")
fun Long.toDateString(
    context: Context,
    locale: Locale,
    skeleton: String
): String? {
    val userSkeleton = if (DateFormat.is24HourFormat(context))
        skeleton.replace("h".toRegex(), "H")
    else
        skeleton.replace("H".toRegex(), "h")
    return getDateFormat(locale, userSkeleton)
        ?.let { SimpleDateFormat(it) }
        ?.format(Date(this))
}

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
fun getDateFormat(locale: Locale, skeleton: String): String? {
    return DateFormat.getBestDateTimePattern(locale, skeleton)
}

fun dateEquals(a: Long, b: Long): Boolean {
    return a / 86400000 == b / 86400000
}

fun String.dateStringToLong(format: String): Long? {
    return this.dateStringToLong(format, Locale.getDefault())
}

fun String.dateStringToLong(format: String, locale: Locale): Long? {
    return try {
        SimpleDateFormat(format, locale)
            .parse(this)
            ?.time
    } catch (e: ParseException) {
        e.printStackTrace()
        null
    }
}