// SPDX-FileCopyrightText: Adam Evyčędo
//
// SPDX-License-Identifier: GPL-3.0-or-later

package xyz.apiote.bimba.czwek.repo

import android.content.Context
import android.text.format.DateUtils
import xyz.apiote.bimba.czwek.R
import xyz.apiote.bimba.czwek.api.AlertCauseV1
import xyz.apiote.bimba.czwek.api.AlertEffectV1
import xyz.apiote.bimba.czwek.api.AlertV1
import xyz.apiote.bimba.czwek.api.DepartureV1
import xyz.apiote.bimba.czwek.api.DepartureV2
import xyz.apiote.bimba.czwek.api.DepartureV3
import xyz.apiote.bimba.czwek.api.DepartureV4
import xyz.apiote.bimba.czwek.api.DepartureV5
import xyz.apiote.bimba.czwek.api.Time
import xyz.apiote.bimba.czwek.api.UnknownResourceVersionException
import xyz.apiote.bimba.czwek.units.Second
import xyz.apiote.bimba.czwek.units.TGM
import xyz.apiote.bimba.czwek.units.UnitSystem
import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit


class EventItem {
	private constructor(d: Event?, a: List<Alert>) {
		event = d
		alert = a
	}

	constructor(d: Event) : this(d, emptyList())
	constructor(a: List<Alert>) : this(null, a)

	val event: Event?
	val alert: List<Alert>
}

enum class AlertCause {
	UNKNOWN, OTHER, TECHNICAL_PROBLEM, STRIKE, DEMONSTRATION, ACCIDENT, HOLIDAY, WEATHER, MAINTENANCE,
	CONSTRUCTION, POLICE_ACTIVITY, MEDICAL_EMERGENCY;

	companion object {
		fun of(type: AlertCauseV1): AlertCause {
			return when (type) {
				AlertCauseV1.UNKNOWN -> valueOf("UNKNOWN")
				AlertCauseV1.OTHER -> valueOf("OTHER")
				AlertCauseV1.TECHNICAL_PROBLEM -> valueOf("TECHNICAL_PROBLEM")
				AlertCauseV1.STRIKE -> valueOf("STRIKE")
				AlertCauseV1.DEMONSTRATION -> valueOf("DEMONSTRATION")
				AlertCauseV1.ACCIDENT -> valueOf("ACCIDENT")
				AlertCauseV1.HOLIDAY -> valueOf("HOLIDAY")
				AlertCauseV1.WEATHER -> valueOf("WEATHER")
				AlertCauseV1.MAINTENANCE -> valueOf("MAINTENANCE")
				AlertCauseV1.CONSTRUCTION -> valueOf("CONSTRUCTION")
				AlertCauseV1.POLICE_ACTIVITY -> valueOf("POLICE_ACTIVITY")
				AlertCauseV1.MEDICAL_EMERGENCY -> valueOf("MEDICAL_EMERGENCY")
			}
		}
	}
}

enum class AlertEffect {
	UNKNOWN, OTHER, NO_SERVICE, REDUCED_SERVICE, SIGNIFICANT_DELAYS, DETOUR, ADDITIONAL_SERVICE,
	MODIFIED_SERVICE, STOP_MOVED, NONE, ACCESSIBILITY_ISSUE;

	companion object {
		fun of(type: AlertEffectV1): AlertEffect {
			return when (type) {
				AlertEffectV1.UNKNOWN -> valueOf("UNKNOWN")
				AlertEffectV1.OTHER -> valueOf("OTHER")
				AlertEffectV1.NO_SERVICE -> valueOf("NO_SERVICE")
				AlertEffectV1.REDUCED_SERVICE -> valueOf("REDUCED_SERVICE")
				AlertEffectV1.SIGNIFICANT_DELAYS -> valueOf("SIGNIFICANT_DELAYS")
				AlertEffectV1.DETOUR -> valueOf("DETOUR")
				AlertEffectV1.ADDITIONAL_SERVICE -> valueOf("ADDITIONAL_SERVICE")
				AlertEffectV1.MODIFIED_SERVICE -> valueOf("MODIFIED_SERVICE")
				AlertEffectV1.STOP_MOVED -> valueOf("STOP_MOVED")
				AlertEffectV1.NONE -> valueOf("NONE")
				AlertEffectV1.ACCESSIBILITY_ISSUE -> valueOf("ACCESSIBILITY_ISSUE")
			}
		}
	}
}

data class Alert(
	val header: String,
	val description: String,
	val url: String,
	val cause: AlertCause,
	val effect: AlertEffect
) {
	constructor(a: AlertV1) : this(
		a.header,
		a.Description,
		a.Url,
		AlertCause.of(a.Cause),
		AlertEffect.of(a.Effect)
	)
}

data class StopEvents(
	val events: List<Event>,
	val stop: Stop,
	val alerts: List<Alert>
)

data class Event(
	val id: String,
	val arrivalTime: Time?,
	val departureTime: Time?,
	val status: ULong,
	val isRealtime: Boolean,
	val vehicle: Vehicle,
	val boarding: UByte,
	val alerts: List<Alert>,
	val exact: Boolean,
	val terminusArrival: Boolean  // TODO origin, middle, terminus; if origin -> only departure, if terminus -> only arrival
) {

	constructor(d: DepartureV1) : this(
		d.ID,
		d.time,
		d.time,
		d.status,
		d.isRealtime,
		Vehicle(d.vehicle),
		d.boarding,
		emptyList(),
		true,
		false
	)

	constructor(d: DepartureV2) : this(
		d.ID,
		d.time,
		d.time,
		d.status,
		d.isRealtime,
		Vehicle(d.vehicle),
		d.boarding,
		emptyList(),
		true,
		false
	)

	constructor(d: DepartureV3) : this(
		d.ID,
		d.time,
		d.time,
		d.status.ordinal.toULong(), // TODO VehicleStatus
		d.isRealtime,
		Vehicle(d.vehicle),
		d.boarding,
		emptyList(),
		true,
		false
	)

	constructor(d: DepartureV4) : this(
		d.ID,
		d.time,
		d.time,
		d.status.ordinal.toULong(), // TODO VehicleStatus
		d.isRealtime,
		Vehicle(d.vehicle),
		d.boarding,
		d.alerts.map { Alert(it) },
		true,
		false
	)

	constructor(d: DepartureV5) : this(
		d.ID,
		d.time,
		d.time,
		d.status.ordinal.toULong(), // TODO VehicleStatus
		d.isRealtime,
		Vehicle(d.vehicle),
		d.boarding,
		d.alerts.map { Alert(it) },
		d.exact,
		d.terminusArrival
	)

	fun timeZone() = (arrivalTime ?: departureTime)!!.Zone

	fun filterTime() = (arrivalTime ?: departureTime)!!

	fun statusText(
		context: Context?,
		showAsTime: Boolean,
		at: ZonedDateTime? = null
	): Pair<String?, String?> {
		val now = at ?: Instant.now().atZone(ZoneId.systemDefault())
		return Pair(
			statusText(context, showAsTime, now, arrivalTime, R.string.departure_arrived),
			statusText(context, showAsTime, now, departureTime, R.string.departure_departed)
		)
	}

	private fun statusText(
		context: Context?,
		showAsTime: Boolean,
		now: ZonedDateTime,
		time: Time?,
		pastString: Int
	): String? {
		val r = status.toUInt()
		return time?.let {
			ZonedDateTime.of(
				now.year,
				now.monthValue,
				now.dayOfMonth,
				it.Hour.toInt(),
				it.Minute.toInt(),
				it.Second.toInt(),
				0,
				ZoneId.of(
					it.Zone
				)
			)
				.plus(it.DayOffset.toLong(), ChronoUnit.DAYS)
				.let {
					if (showAsTime) {
						it.format(DateTimeFormatter.ofPattern("HH:mm"))
					} else {
						when {
							// TODO why this condition
							r == 0u || (it.isBefore(now) && r < 3u) -> if (context != null && UnitSystem.getSelected(
									context
								) is TGM
							) {
								val us = UnitSystem.getSelected(context)
								us.toString(
									context,
									us.timeUnit(Second((it.toEpochSecond() - now.toEpochSecond()).toInt()))
								)
							} else {
								DateUtils.getRelativeTimeSpanString(
									it.toEpochSecond() * 1000,
									now.toEpochSecond() * 1000,
									DateUtils.MINUTE_IN_MILLIS,
									DateUtils.FORMAT_ABBREV_RELATIVE
								).toString()
							}

							r == 1u -> context?.getString(R.string.departure_momentarily) ?: "momentarily"
							r == 2u -> context?.getString(R.string.departure_now) ?: "now"
							r == 3u -> context?.getString(pastString) ?: "passed"
							else -> throw UnknownResourceVersionException("VehicleStatus/$r", 1u)
						}
					}
				}
		}
	}

	fun departureTimeString(context: Context): String? = timeString(context, departureTime)

	fun arrivalTimeString(context: Context): String? = timeString(context, arrivalTime)

	private fun timeString(context: Context, time: Time?): String? {
		return when {
			time == null -> null
			isRealtime -> context.getString(
				R.string.at_time_realtime,
				time.Hour.toInt(),
				time.Minute.toInt(),
				time.Second.toInt()
			)

			exact -> context.getString(
				R.string.at_time,
				time.Hour.toInt(),
				time.Minute.toInt()
			)

			else -> context.getString(
				R.string.about_time,
				time.Hour.toInt(),
				time.Minute.toInt()
			)
		}

	}

	fun boardingText(context: Context): String {
		// todo [3.x] probably should take into account (on|off)-boarding only, on demand
		return when {
			boarding == (0b0000_0000).toUByte() -> context.getString(R.string.no_boarding)
			boarding == (0b1111_1111).toUByte() -> "" // unknown
			boarding.and(0b0011_0011u) == (0b0000_0001).toUByte() -> context.getString(R.string.on_boarding)
			boarding.and(0b0011_0011u) == (0b0001_0000).toUByte() -> context.getString(R.string.off_boarding)
			boarding.and(0b0011_0011u) == (0b0001_0001).toUByte() -> context.getString(R.string.boarding)
			else -> context.getString(R.string.on_demand)
		}
	}
}
