{-|
Module: Data.Astro.Types
Description: Common Types
Copyright: Alexander Ignatyev, 2016

Common Types are usfull across all subsystems like Time and Coordinate.

= Examples

== /Decimal hours and Decimal degrees/

@
import Data.Astro.Types

-- 10h 15m 19.7s
dh :: DecimalHours
dh = fromHMS 10 15 19.7
-- DH 10.255472222222222

(h, m, s) = toHMS dh
-- (10,15,19.699999999999562)


-- 51°28′40″
dd :: DecimalDegrees
dd = fromDMS 51 28 40
-- DD 51.477777777777774

(d, m, s) = toDMS dd
-- (51,28,39.999999999987494)
@

== /Geographic Coordinates/
@
import Data.Astro.Types

-- the Royal Observatory, Greenwich
ro :: GeographicCoordinates
ro = GeoC (fromDMS 51 28 40) (-(fromDMS 0 0 5))
-- GeoC {geoLatitude = DD 51.4778, geoLongitude = DD (-0.0014)}
@
-}

module Data.Astro.Types
(
  DecimalDegrees(..)
  , DecimalHours (..)
  , GeographicCoordinates(..)
  , AstronomicalUnits(..)
  , lightTravelTime
  , toDecimalHours
  , fromDecimalHours
  , toRadians
  , fromRadians
  , fromDMS
  , toDMS
  , fromHMS
  , toHMS
)

where

import qualified Data.Astro.Utils as U


newtype DecimalDegrees = DD Double
                         deriving (Show, Eq, Ord)


instance Num DecimalDegrees where
  (+) (DD d1) (DD d2) = DD (d1+d2)
  (-) (DD d1) (DD d2) = DD (d1-d2)
  (*) (DD d1) (DD d2) = DD (d1*d2)
  negate (DD d) = DD (negate d)
  abs (DD d) = DD (abs d)
  signum (DD d) = DD (signum d)
  fromInteger int = DD (fromInteger int)

instance Real DecimalDegrees where
  toRational (DD d) = toRational d

instance Fractional DecimalDegrees where
  (/) (DD d1) (DD d2) = DD (d1/d2)
  recip (DD d) = DD (recip d)
  fromRational r = DD (fromRational r)

instance RealFrac DecimalDegrees where
  properFraction (DD d) =
    let (i, f) = properFraction d
    in (i, DD f)


newtype DecimalHours = DH Double
                       deriving (Show, Eq, Ord)


instance Num DecimalHours where
  (+) (DH d1) (DH d2) = DH (d1+d2)
  (-) (DH d1) (DH d2) = DH (d1-d2)
  (*) (DH d1) (DH d2) = DH (d1*d2)
  negate (DH d) = DH (negate d)
  abs (DH d) = DH (abs d)
  signum (DH d) = DH (signum d)
  fromInteger int = DH (fromInteger int)

instance Real DecimalHours where
  toRational (DH d) = toRational d

instance Fractional DecimalHours where
  (/) (DH d1) (DH d2) = DH (d1/d2)
  recip (DH d) = DH (recip d)
  fromRational r = DH (fromRational r)

instance RealFrac DecimalHours where
  properFraction (DH d) =
    let (i, f) = properFraction d
    in (i, DH f)


-- | Convert decimal degrees to decimal hours
toDecimalHours :: DecimalDegrees -> DecimalHours
toDecimalHours (DD d) = DH $ d/15  -- 360 / 24 = 15

-- | Convert decimal hours to decimal degrees
fromDecimalHours :: DecimalHours -> DecimalDegrees
fromDecimalHours (DH h) = DD $ h*15


-- | Geographic Coordinates
data GeographicCoordinates = GeoC {
  geoLatitude :: DecimalDegrees
  , geoLongitude :: DecimalDegrees
  } deriving (Show, Eq)


-- | Astronomical Units, 1AU = 1.4960×1011 m
-- (originally, the average distance of Earth's aphelion and perihelion).
newtype AstronomicalUnits = AU Double deriving (Show, Eq, Ord)


instance Num AstronomicalUnits where
  (+) (AU d1) (AU d2) = AU (d1+d2)
  (-) (AU d1) (AU d2) = AU (d1-d2)
  (*) (AU d1) (AU d2) = AU (d1*d2)
  negate (AU d) = AU (negate d)
  abs (AU d) = AU (abs d)
  signum (AU d) = AU (signum d)
  fromInteger int = AU (fromInteger int)

instance Real AstronomicalUnits where
  toRational (AU d) = toRational d

instance Fractional AstronomicalUnits where
  (/) (AU d1) (AU d2) = AU (d1/d2)
  recip (AU d) = AU (recip d)
  fromRational r = AU (fromRational r)

instance RealFrac AstronomicalUnits where
  properFraction (AU d) =
    let (i, f) = properFraction d
    in (i, AU f)


-- | Light travel time of the distance in Astronomical Units
lightTravelTime :: AstronomicalUnits -> DecimalHours
lightTravelTime (AU ro) = DH $ 0.1386*ro

-- | Convert from DecimalDegrees to Radians
toRadians (DD deg) = U.toRadians deg


-- | Convert from Radians to DecimalDegrees
fromRadians rad = DD $ U.fromRadians rad


-- | Convert Degrees, Minutes, Seconds to DecimalDegrees
fromDMS :: RealFrac a => Int -> Int -> a -> DecimalDegrees
fromDMS d m s =
  let d' = fromIntegral d
      m' = fromIntegral m
      s' = realToFrac s
  in DD $ d'+(m'+(s'/60))/60


-- | Convert DecimalDegrees to Degrees, Minutes, Seconds
toDMS (DD dd) =
  let (d, rm) = properFraction dd
      (m, rs) = properFraction $ 60 * rm
      s = 60 * rs
  in (d, m, s)


-- | Comvert Hours, Minutes, Seconds to DecimalHours
fromHMS :: RealFrac a => Int -> Int -> a -> DecimalHours
fromHMS h m s =
  let h' = fromIntegral h
      m' = fromIntegral m
      s' = realToFrac s
  in DH $ h'+(m'+(s'/60))/60


-- | Convert DecimalDegrees to Degrees, Minutes, Seconds
toHMS (DH dh) =
  let (h, rm) = properFraction dh
      (m, rs) = properFraction $ 60 * rm
      s = 60 * rs
  in (h, m, s)