{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}


-- | An azimuth in degrees between 0 and 360.
module Data.Geo.Geodetic.Azimuth(
  Azimuth
, AsAzimuth(..)
, modAzimuth
) where

import Control.Applicative(Applicative)
import Control.Category(Category(id))
import Data.Bool(bool, (&&))
import Data.Eq(Eq)
import Data.List((++))
import Data.Maybe(Maybe(Just, Nothing))
import Data.Ord(Ord((>), (>=), (<)))
import Control.Lens(Choice, Optic', prism')
import Data.Fixed(mod')
import Prelude(Double, Show(showsPrec), showParen, showString)
import Text.Printf(printf)

-- $setup
-- >>> import Control.Lens((#), (^?))
-- >>> import Data.Foldable(all)
-- >>> import Prelude(Eq((==)))

newtype Azimuth =
  Azimuth Double
  deriving (Eq, Ord)

-- | A show instance that prints to 4 decimal places.
-- This is to take floating-point rounding errors into account.
instance Show Azimuth where
  showsPrec n (Azimuth d) =
    showParen (n > 10) (showString ("Azimuth " ++ printf "%0.4f" d))

-- | Construct an azimuth such that if the given value is out of bounds,
-- a modulus is taken to keep it within 0 inclusive and 360 exclusive.
--
-- >>> modAzimuth 7
-- Azimuth 7.0000
--
-- >>> modAzimuth 0
-- Azimuth 0.0000
--
-- >>> modAzimuth 360
-- Azimuth 0.0000
--
-- >>> modAzimuth 361
-- Azimuth 1.0000
--
-- >>> modAzimuth 359.999
-- Azimuth 359.9990
modAzimuth ::
  Double
  -> Azimuth
modAzimuth x =
  Azimuth (x `mod'` 360)

class AsAzimuth p f s where
  _Azimuth ::
    Optic' p f s Azimuth

instance AsAzimuth p f Azimuth where
  _Azimuth =
    id

-- | A prism on azimuth to an integer between 0 and 359 inclusive.
--
-- >> 7 ^? _Azimuth
-- Just (Azimuth 7.0000)
--
-- >> 0 ^? _Azimuth
-- Just (Azimuth 0.0000)
--
-- >> 359.999 ^? _Azimuth
-- Just (Azimuth 359.9990)
--
-- >> 360 ^? _Azimuth
-- Nothing
--
-- prlop> all (\m -> _Azimuth # m == n) (n ^? _Azimuth)
instance (Choice p, Applicative f) => AsAzimuth p f Double where
  _Azimuth =
    prism'
      (\(Azimuth i) -> i)
      (\i -> bool Nothing (Just (Azimuth i)) (i >= 0 && i < 360))