{-# LANGUAGE OverloadedStrings #-}
-- | Here we define providers for the Reverse Geocoding feature,
-- namely how to format the url, the key under which the results are returned,
-- and how to parse the retrived data.
--
-- Currently only Open Stree Map is provided, feel free to add others.
--
module Data.Geolocation.Reverse.Providers
  ( ReverseGeoJsonKey
  , ReverseGeoParser
  , ReverseGeoProvider
  , openStreetMap

  ) where

import Control.Applicative

import Data.Char
import Control.Lens ((.~),(&))
import Data.Monoid
import Data.Maybe
import Data.Aeson
import Data.Aeson.Types hiding (Options)
import Control.Monad (join)
import Network.Wreq (Options, defaults, header)
import qualified Data.Text as T

import Data.Geolocation.Reverse.Types

type ReverseGeoJsonKey = T.Text
type ReverseGeoUrl = Latitude -> Longitude -> Maybe String
type ReverseGeoParser  = Object -> Parser ParsedLocationInfo
type ReverseGeoProvider =
  ( ReverseGeoJsonKey
  , ReverseGeoUrl
  , ReverseGeoParser
  , Maybe Options
  )

openStreetMap :: ReverseGeoProvider
openStreetMap =
  ( "address"
  , openStreetMapUrl
  , openStreetMapParser
  , Just
    ( defaults
    & header "User-Agent"
      .~ ["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"]
    )
  )

openStreetMapUrl :: Latitude -> Longitude -> Maybe String
openStreetMapUrl (Latitude mlat) (Longitude mlon) = do
  lat <- mlat
  lon <- mlon
  return $  "http://nominatim.openstreetmap.org/reverse"
         <> "?format=json"
         <> "&zoom=18"
         <> "&lat=" <> show lat
         <> "&lon=" <> show lon


getPostCodeText :: Suburb -> Maybe Suburb
getPostCodeText (Suburb txt) = let
  rest = T.dropWhile (\c -> isDigit c || isSpace c || (c == '-')) txt
  in if T.null rest then Nothing else Just (Suburb rest)

(<|?>) :: Parser (Maybe a) -> Parser (Maybe a) -> Parser (Maybe a)
pa <|?> pb = do
  a <- pa
  if isJust a then pa else pb
infixl 3 <|?>


-- Not all records have suburb defined, but some have it written in the post code
-- This function tries to extract if from the post code if available.
openStreetMapParser :: Object -> Parser ParsedLocationInfo
openStreetMapParser o =
  ParsedLocationInfo <$> o .:  "country_code"
                     <*> (    o .: "city"
                          <|> o .: "village"
                          <|> o .: "town"
                          <|> o .: "hamlet"
                          <|> o .: "county"
                          )
                     <*> (     (o .:? "suburb")
                          <|?> (o .:? "hamlet")
                          <|?> (fmap (join . fmap getPostCodeText) ( o .:? "postcode"))
                          <|?> (o .:? "town")
                          )
                     <*> (     (o .:? "road")
                          <|?> (o .:? "street")
                         )
                     <*> o .:? "postcode"