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

package xyz.apiote.bimba.czwek.api

import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import xyz.apiote.bimba.czwek.R
import xyz.apiote.bimba.czwek.api.transitous.api.RoutingApi
import xyz.apiote.bimba.czwek.api.transitous.model.PedestrianProfile
import xyz.apiote.bimba.czwek.repo.Colour
import xyz.apiote.bimba.czwek.repo.CongestionLevel
import xyz.apiote.bimba.czwek.repo.Event
import xyz.apiote.bimba.czwek.repo.Journey
import xyz.apiote.bimba.czwek.repo.JourneyParams
import xyz.apiote.bimba.czwek.repo.Leg
import xyz.apiote.bimba.czwek.repo.LineStub
import xyz.apiote.bimba.czwek.repo.LineType
import xyz.apiote.bimba.czwek.repo.OccupancyStatus
import xyz.apiote.bimba.czwek.repo.Place
import xyz.apiote.bimba.czwek.repo.Position
import xyz.apiote.bimba.czwek.repo.TimeReference
import xyz.apiote.bimba.czwek.repo.TrafficResponseException
import xyz.apiote.bimba.czwek.repo.Vehicle
import xyz.apiote.bimba.czwek.units.Metre
import xyz.apiote.bimba.czwek.units.Mps
import xyz.apiote.bimba.czwek.units.Second
import java.time.Duration
import java.time.ZoneId
import java.time.ZonedDateTime

suspend fun getJourney(
	from: Place,
	to: Place,
	params: JourneyParams,
	context: Context
): List<Journey> {
	if (!isNetworkAvailable(context)) {
		throw TrafficResponseException(0, "", Error(0, R.string.error_offline, R.drawable.error_net))
	}

	return withContext(Dispatchers.IO) {
		val client = OkHttpClient.Builder()
			.callTimeout(Duration.ofSeconds(60))
			.readTimeout(Duration.ofSeconds(60))
			.writeTimeout(Duration.ofSeconds(60))
			.connectTimeout(Duration.ofSeconds(60)).build()
		val response = RoutingApi(client = client).plan(
			from.planString(),
			to.planString(),
			maxTransfers = null,
			maxTravelTime = null,
			time = ZonedDateTime.of(params.getSafeDate(), params.getSafeTime(), ZoneId.systemDefault())
				.toOffsetDateTime(),
			arriveBy = params.timeReference == TimeReference.ARRIVE_BY,
			requireBikeTransport = params.bicycle,
			pedestrianProfile = if (params.wheelchairAccessible) PedestrianProfile.WHEELCHAIR else PedestrianProfile.FOOT,
		)
		val journeys = response.itineraries.map {
			val legs: List<Leg> = it.legs.map {
				Leg(
					Event(
						it.tripId ?: "",
						null,
						Time.fromOffsetTime(it.startTime, ZoneId.systemDefault()),
						0u,
						it.realTime,
						Vehicle(
							it.tripId ?: "",
							Position(0.0, 0.0),
							0u,
							Mps(0),
							LineStub(
								it.routeShortName ?: "",
								LineType.fromTransitous2(it.mode),
								Colour.fromHex(it.routeColor)
							),
							it.headsign ?: "",
							CongestionLevel.UNKNOWN,
							OccupancyStatus.UNKNOWN
						),
						boarding = 0xffu,
						alerts = emptyList(),
						exact = true,
						terminusArrival = false
					),
					Event(
						it.tripId ?: "",
						Time.fromOffsetTime(it.endTime, ZoneId.systemDefault()),
						null,
						0u,
						it.realTime,
						Vehicle(
							it.tripId ?: "",
							Position(0.0, 0.0),
							0u,
							Mps(0),
							LineStub(
								it.routeShortName ?: "",
								LineType.fromTransitous2(it.mode),
								Colour.fromHex(it.routeColor)
							),
							it.headsign ?: "",
							CongestionLevel.UNKNOWN,
							OccupancyStatus.UNKNOWN
						),
						boarding = 0xffu,
						alerts = emptyList(),
						exact = true,
						terminusArrival = false
					),
					Place(it.from),
					Place(it.to),
					it.agencyName,
					it.distance?.toDouble()?.let { Metre(it) },
					Second(it.duration),
					(it.intermediateStops ?: emptyList()).map { Place(it) },
					decode(it.legGeometry.points)
					/*it.rental,
					it.steps,*/
				)
			}
			Journey(
				it.startTime.atZoneSameInstant(ZoneId.systemDefault()), it.endTime.atZoneSameInstant(
					ZoneId.systemDefault()
				), legs
			)
		}
		if (params.timeReference == TimeReference.ARRIVE_BY) {
			journeys.reversed()
		} else {
			journeys
		}
	}
}

/*
The following piece of code © Google, Apache License, Version 2.0
from https://github.com/googlemaps/android-maps-utils/blob/main/library/src/main/java/com/google/maps/android/PolyUtil.java
with changes to decode with precision 7
*/
fun decode(encodedPath: String): MutableList<Position> {
	val len = encodedPath.length

	val path: MutableList<Position> = ArrayList<Position>()
	var index = 0
	var lat = 0
	var lng = 0

	while (index < len) {
		var result = 1
		var shift = 0
		var b: Int
		do {
			b = encodedPath[index++].code - 63 - 1
			result += b shl shift
			shift += 5
		} while (b >= 0x1f)
		lat += if ((result and 1) != 0) (result shr 1).inv() else (result shr 1)

		result = 1
		shift = 0
		do {
			b = encodedPath[index++].code - 63 - 1
			result += b shl shift
			shift += 5
		} while (b >= 0x1f)
		lng += if ((result and 1) != 0) (result shr 1).inv() else (result shr 1)

		path.add(Position(lat * 1e-7, lng * 1e-7))
	}

	return path
}
