package com.example.aprs

import kotlin.math.floor
import kotlin.math.abs
import kotlin.math.sin
import kotlin.math.cos
import kotlin.math.atan
import kotlin.math.sqrt
import kotlin.math.PI
import kotlin.math.ln

object LocationUtils {
    // Convert decimal degrees to APRS position format: DDMM.MM N / DDDMM.MM E
    fun toAprs(lat: Double, lon: Double): String {
        val latHem = if (lat >= 0) "N" else "S"
        val lonHem = if (lon >= 0) "E" else "W"

        val latAbs = abs(lat)
        val lonAbs = abs(lon)

        val latDeg = floor(latAbs).toInt()
        val latMin = (latAbs - latDeg) * 60.0

        val lonDeg = floor(lonAbs).toInt()
        val lonMin = (lonAbs - lonDeg) * 60.0

        return String.format(
            "%02d%05.2f%s/%03d%05.2f%s",
            latDeg,
            latMin,
            latHem,
            lonDeg,
            lonMin,
            lonHem
        )
    }

    // Maidenhead locator (6 chars) from lat/lon
    fun toMaidenhead(lat: Double, lon: Double): String {
        var adjLon = lon + 180.0
        var adjLat = lat + 90.0

        val fieldLon = (adjLon / 20.0).toInt()
        val fieldLat = (adjLat / 10.0).toInt()

        val squareLon = ((adjLon % 20) / 2).toInt()
        val squareLat = ((adjLat % 10) / 1).toInt()

        val subsLon = (((adjLon - fieldLon * 20 - squareLon * 2) * 60) / 5).toInt()
        val subsLat = (((adjLat - fieldLat * 10 - squareLat * 1) * 60) / 2.5).toInt()

        val a = 'A'.code

        val fieldChars = charArrayOf((a + fieldLon).toChar(), (a + fieldLat).toChar())
        val squareChars = charArrayOf(('0'.code + squareLon).toChar(), ('0'.code + squareLat).toChar())
        val subsChars = charArrayOf((a + subsLon).toChar(), (a + subsLat).toChar())

        return String(charArrayOf(fieldChars[0], fieldChars[1], squareChars[0], squareChars[1], subsChars[0], subsChars[1]))
    }

    // Convert lat/lon to UTM coordinates
    fun toUTM(lat: Double, lon: Double): String {
        val k0 = 0.9996
        val a = 6378137.0
        val eSquared = 0.00669438
        val e = sqrt(eSquared)
        val ePrimeSquared = eSquared / (1 - eSquared)

        val latRad = lat * PI / 180.0
        val lonRad = lon * PI / 180.0

        val zone = ((lon + 180) / 6).toInt() + 1

        val lonOrigin = (zone - 1) * 6 - 180 + 3
        val lonOriginRad = lonOrigin * PI / 180.0

        val n = a / sqrt(1 - eSquared * sin(latRad) * sin(latRad))
        val T = tan(latRad) * tan(latRad)
        val C = ePrimeSquared * cos(latRad) * cos(latRad)
        val A = cos(latRad) * (lonRad - lonOriginRad)

        val M = a * ((1 - eSquared / 4 - 3 * eSquared * eSquared / 64 - 5 * eSquared * eSquared * eSquared / 256) * latRad
                - (3 * eSquared / 8 + 3 * eSquared * eSquared / 32 + 45 * eSquared * eSquared * eSquared / 1024) * sin(2 * latRad)
                + (15 * eSquared * eSquared / 256 + 45 * eSquared * eSquared * eSquared / 1024) * sin(4 * latRad)
                - (35 * eSquared * eSquared * eSquared / 3072) * sin(6 * latRad))

        val UTMEasting = (k0 * n * (A + (1 - T + C) * A * A * A / 6
                + (5 - 18 * T + T * T + 72 * C - 58 * ePrimeSquared) * A * A * A * A * A / 120)
                + 500000.0).toLong()

        val UTMNorthing = (k0 * (M + n * tan(latRad) * (A * A / 2 + (5 - T + 9 * C + 4 * C * C) * A * A * A * A / 24
                + (61 - 58 * T + T * T + 600 * C - 330 * ePrimeSquared) * A * A * A * A * A * A / 720))
                + (if (lat < 0) 10000000.0 else 0.0)).toLong()

        val zoneLetter = getUtmZoneLetter(lat, lon)

        return String.format("%d%s %06d %07d", zone, zoneLetter, UTMEasting, UTMNorthing)
    }

    private fun tan(rad: Double): Double = sin(rad) / cos(rad)

    private fun getUtmZoneLetter(lat: Double, lon: Double): String {
        val letters = "CDEFGHJKLMNPQRSTUVWXX"
        val latIndex = ((lat + 80) / 8).toInt().coerceIn(0, letters.length - 1)
        return letters[latIndex].toString()
    }
}
