module TileMap exposing (tiles
                        , ZoomLevel(..)
                        , Coord
                        , TileMap(..)
                        , toCoord
                        , toZoom
                        , translate
                        , translatePixels
                        , incZoom
                        , bounds
                        , pixelsToCoord
                        , pixelFromCoord
                        , zoomStep)

import Html exposing (img, div)
import Html.Attributes as H exposing (src, style, width, height)

import Pos exposing (Pos)

type alias TileNumber = { x: Int, y: Int }

-- Coordinates in a Mercator projection
type alias Coord = { x: Float, y: Float }

-- zoom level
type alias TileZoomLevel = Int
type ZoomLevel = ZoomLevel Int


type TileMap = TileMap Coord ZoomLevel Int Int

zoomStep = 8

toZoom : ZoomLevel -> TileZoomLevel
toZoom (ZoomLevel f) = f // zoomStep


incZoom : ZoomLevel -> Int -> ZoomLevel
incZoom (ZoomLevel z) delta =
    ZoomLevel (clamp 0 (20 * zoomStep) (z + delta))


-- project lat/long to co-ordinates based on pseudocode at
-- https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Zoom_levels

sec x = 1 / (cos x)

toCoord : Pos -> Coord
toCoord pos =
    let
        lat_rad = pos.lat * pi / 180
        x = (pos.lon + 180) / 360
        y = (1 - (logBase e ((tan lat_rad) + (sec lat_rad))) / pi) / 2
    in
        Coord x y

pixelsToCoord z_ (x,y) =
    let scale = 8 + toZoom z_
        x_float = toFloat x / toFloat ( 2 ^ scale)
        y_float = toFloat y / toFloat ( 2 ^ scale)
    in Coord x_float y_float

reflect : Coord -> Coord
reflect c = Coord -c.x -c.y

-- used for Coords and for TileNumbers
translate base offset =
    { x = (base.x + offset.x), y = (base.y + offset.y) }

translatePixels : Coord -> ZoomLevel -> (Int, Int) -> Coord
translatePixels old z_ (x, y) =
    translate old (pixelsToCoord z_ (x, y))

tileCovering : Coord -> TileZoomLevel -> TileNumber
tileCovering c z =
    TileNumber (truncate (toFloat (2 ^ z) * c.x)) (truncate (toFloat (2 ^ z) * c.y))

pixelFromCoord : Coord -> ZoomLevel -> (Int, Int)
pixelFromCoord c z_ =
    let z = toZoom z_
        {x,y} = tileCovering c (z + 8)
    in (x,y)

boundingTiles : TileMap -> (TileNumber, TileNumber)
boundingTiles (TileMap centre z1 width height) =
    -- find the tiles needed to cover the area (`width` x `height`)
    -- about the point at `centre`
    let z = toZoom z1
        delta = pixelsToCoord z1 ((width // 2), (height // 2))
        minCoord = translate centre (reflect delta)
        maxCoord = translate centre delta
    in ((tileCovering minCoord z),
        (translate (tileCovering maxCoord z) (TileNumber 1 1)))

bounds : TileMap -> { left : Int, top : Int, width: Int, height: Int }
bounds tmap =
    let (mintile, maxtile) = boundingTiles tmap
    in {
        left = mintile.x * 256,
        top = mintile.y * 256,
        width = (1 + maxtile.x - mintile.x) * 256,
        height = (1 + maxtile.y - mintile.y) * 256
    }


tileUrl : TileNumber -> TileZoomLevel -> String
tileUrl {x,y} z =
    String.concat ["https://a.tile.openstreetmap.org",
                       "/", String.fromInt z,
                       "/", String.fromInt x,
                       "/", String.fromInt y,
                       ".png" ]

tileImg zoom tilenumber = img [ width 256,
                                height 256,
                                src (tileUrl tilenumber zoom) ] []

tiles tmap =
    let (TileMap centre zoom width height) = tmap
        (mintile, maxtile) = boundingTiles tmap
        xs = List.range mintile.x maxtile.x
        ys = List.range mintile.y maxtile.y
        zoom_ = toZoom zoom
    in
    List.map
        (\ y -> div []
             (List.map (\ x -> tileImg zoom_ (TileNumber x y)) xs))
        ys
