package org.eichelberger.sfc.examples

import com.typesafe.scalalogging.slf4j.LazyLogging
import org.eichelberger.sfc.SpaceFillingCurve._
import org.eichelberger.sfc.study.composition.CompositionSampleData._
import org.eichelberger.sfc.utils.Timing
import org.eichelberger.sfc.{DefaultDimensions, Dimension}
import org.junit.runner.RunWith
import org.specs2.mutable.Specification
import org.specs2.runner.JUnitRunner

@RunWith(classOf[JUnitRunner])
class GeohashTest extends Specification with LazyLogging {
  val xCville = -78.488407
  val yCville = 38.038668

  "Geohash example" should {
    val geohash = new Geohash(35)

    "encode/decode round-trip for an interior point" >> {
      // encode
      val hash = geohash.pointToHash(Seq(xCville, yCville))
      hash must equalTo("dqb0muw")

      // decode
      val cell = geohash.hashToCell(hash)
      println(s"[Geohash example, Charlottesville] POINT($xCville $yCville) -> $hash -> $cell")
      cell(0).containsAny(xCville) must beTrue
      cell(1).containsAny(yCville) must beTrue
    }

    "encode/decode properly at the four corners and the center" >> {
      for (x <- Seq(-180.0, 0.0, 180.0); y <- Seq(-90.0, 0.0, 90.0)) {
        // encode
        val hash = geohash.pointToHash(Seq(x, y))

        // decode
        val cell = geohash.hashToCell(hash)
        println(s"[Geohash example, extrema] POINT($x $y) -> $hash -> $cell")
        cell(0).containsAny(x) must beTrue
        cell(1).containsAny(y) must beTrue
      }

      // degenerate test outcome
      1 must equalTo(1)
    }

    def getCvilleRanges(curve: Geohash): (OrdinalPair, OrdinalPair, Iterator[OrdinalPair]) = {
      val lonIdxRange = OrdinalPair(
        curve.children(0).asInstanceOf[Dimension[Double]].index(bboxCville._1),
        curve.children(1).asInstanceOf[Dimension[Double]].index(bboxCville._3)
      )
      val latIdxRange = OrdinalPair(
        curve.children(0).asInstanceOf[Dimension[Double]].index(bboxCville._2),
        curve.children(1).asInstanceOf[Dimension[Double]].index(bboxCville._4)
      )
      val query = Query(Seq(OrdinalRanges(lonIdxRange), OrdinalRanges(latIdxRange)))
      val cellQuery = Cell(Seq(
        DefaultDimensions.createDimension("x", bboxCville._1, bboxCville._3, 0),
        DefaultDimensions.createDimension("y", bboxCville._2, bboxCville._4, 0)
      ))
      (lonIdxRange, latIdxRange, curve.getRangesCoveringCell(cellQuery))
    }
    
    "generate valid selection indexes" >> {
      val (_, _, ranges) = getCvilleRanges(geohash)

      ranges.size must equalTo(90)
    }
    
    "report range efficiency" >> {
      def atPrecision(xBits: OrdinalNumber, yBits: OrdinalNumber): (Long, Long) = {
        val curve = new Geohash(xBits + yBits)
        val (lonRange, latRange, ranges) = getCvilleRanges(curve)
        (lonRange.size * latRange.size, ranges.size.toLong)
      }

      for (dimPrec <- 10 to 25) {
        val ((numCells, numRanges), ms) = Timing.time{ () => atPrecision(dimPrec, dimPrec - 1) }
        println(s"[ranges across scales, Charlottesville] precision ($dimPrec, ${dimPrec - 1}) -> $numCells / $numRanges = ${numCells / numRanges} in $ms milliseconds")
      }

      1 must equalTo(1)
    }
  }

}