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

package xyz.apiote.bimba.czwek.search

import android.content.Context
import android.location.Location
import android.os.Parcelable
import android.util.Log
import com.google.openlocationcode.OpenLocationCode
import kotlinx.parcelize.Parcelize
import xyz.apiote.bimba.czwek.repo.Position

@Parcelize
class Query(var raw: String, var mode: Mode, var position: Position?) : Parcelable {
	@Parcelize
	enum class Mode : Parcelable {
		LOCATION, POSITION, NAME, LOCATION_PLUS_CODE, UNKNOWN
	}

	constructor(raw: String) : this(raw, Mode.UNKNOWN, null)

	constructor(raw: String, mode: Mode) : this(raw, mode, null)

	constructor(mode: Mode) : this("", mode, null) {
		if (mode != Mode.LOCATION) {
			throw Exception("Cannot initialise Query from bare Mode other than LOCATION")
		}
	}

	constructor(position: Position) : this(position.toString(), Mode.POSITION, position)

	private constructor(mode: Mode, raw: String, position: Position?) : this(raw, mode, position)

	fun parse(context: Context) {
		if (mode != Mode.UNKNOWN) {
			return
		}
		if (OpenLocationCode.isValidCode(raw)) {
			val olc = OpenLocationCode(raw)
			if (!olc.isFull) {
				mode = Mode.LOCATION_PLUS_CODE
			} else {
				val area = olc.decode()
				mode = Mode.POSITION
				position = Position(area.centerLatitude, area.centerLongitude)
			}
		} else if (OpenLocationCode.isValidCode(raw.trim().split(" ").first().trim(',').trim())) {
			mode = Mode.POSITION
			geocode(context)
		} else if (seemsCoordinatesDegrees(raw)) {
			val coords = raw.split(", ", ",", " ")
			try {
				position = Position(Location.convert(coords[0]), Location.convert(coords[1]))
				mode = Mode.POSITION
			} catch (e: Exception) {
				Log.i("Query", "while parsing degrees: $e")
				mode = Mode.NAME
			}
		} else if (seemsCoordinatesDegreesMinutesSeconds(raw)) {
			val coords =
				raw.replace(Regex("° ?"), ":").replace(Regex("' ?"), ":").replace(Regex("""" ?"""), "")
					.split(" ").map { it.replace(",", "") }.toMutableList()
			try {
				val northSouth = if (coords[0].last() == 'N') 1 else -1
				val eastWest = if (coords[1].last() == 'E') 1 else -1
				coords[0] = coords[0].replace(Regex("[NS]"), "")
				coords[1] = coords[1].replace(Regex("[EW]"), "")
				position =
					Position(Location.convert(coords[0]) * northSouth, Location.convert(coords[1]) * eastWest)
				mode = Mode.POSITION
			} catch (e: Exception) {
				Log.i("Query", "while parsing deg min sec: $e")
				mode = Mode.NAME
			}
		} else {
			mode = Mode.NAME
		}
	}

	private fun seemsCoordinatesDegrees(s: String): Boolean {
		return Regex("""[+-]?[0-9]+(\.[0-9]+)?(,| |, )[+-]?[0-9]+(\.[0-9]+)?""").matches(s)
	}

	private fun seemsCoordinatesDegreesMinutesSeconds(s: String): Boolean {
		return Regex("""[0-9]+° ?[0-9]+' ?[0-9]+(\.[0-9]+)?" ?[NS] [0-9]+° ?[0-9]+' ?[0-9]+(\.[0-9]+)?" ?[EW]""").matches(
			s
		)
	}

	private fun geocode(context: Context) {
		val split = raw.trim().split(" ")
		val code = split.first().trim(',').trim()
		val freePart = split.drop(1).joinToString(" ")
		val location = findPlace(context, freePart)
		if (location == null) {
			throw GeocodingException()
		} else {
			val area = OpenLocationCode(code).recover(location.latitude, location.longitude).decode()
			position = Position(area.centerLatitude, area.centerLongitude)
		}
	}

	override fun toString(): String {
		return when (mode) {
			Mode.UNKNOWN -> raw
			Mode.LOCATION -> "here"
			Mode.POSITION -> "%.2f, %.2f".format(
				position!!.latitude,
				position!!.longitude
			)  // TODO settings for position format
			Mode.NAME -> raw
			Mode.LOCATION_PLUS_CODE -> raw
		}
	}
}