{-|
Module: Data.Astro.Moon
Description: Calculation characteristics of the Moon
Copyright: Alexander Ignatyev, 2016

Calculation characteristics of the Moon.

= Example

@
import Data.Astro.Time.JulianDate
import Data.Astro.Coordinate
import Data.Astro.Types
import Data.Astro.Effects
import Data.Astro.CelestialObject.RiseSet
import Data.Astro.Moon

ro :: GeographicCoordinates
ro = GeoC (fromDMS 51 28 40) (-(fromDMS 0 0 5))

dt :: LocalCivilTime
dt = lctFromYMDHMS (DH 1) 2017 6 25 10 29 0

today :: LocalCivilDate
today = lcdFromYMD (DH 1) 2017 6 25

jd :: JulianDate
jd = lctUniversalTime dt

-- distance from the Earth to the Moon in kilometres
mdu :: MoonDistanceUnits
mdu = moonDistance1 j2010MoonDetails jd
-- MDU 0.9550170577020396

distance :: Double
distance = mduToKm mdu
-- 367109.51199772174

-- Angular Size
angularSize :: DecimalDegrees
angularSize = moonAngularSize mdu
-- DD 0.5425033990980761

-- The Moon's coordinates
position :: JulianDate -> EquatorialCoordinates1
position = moonPosition1 j2010MoonDetails

ec1 :: EquatorialCoordinates1
ec1 = position jd
-- EC1 {e1Declination = DD 18.706180658927323, e1RightAscension = DH 7.56710547682055}

hc :: HorizonCoordinates
hc = ec1ToHC ro jd ec1
-- HC {hAltitude = DD 34.57694951316064, hAzimuth = DD 103.91119101451832}

-- Rise and Set
riseSet :: RiseSetMB
riseSet = riseAndSet2 0.000001 position ro verticalShift today
-- RiseSet
--    (Just (2017-06-25 06:22:51.4858 +1.0,DD 57.81458864497365))
--    (Just (2017-06-25 22:28:20.3023 +1.0,DD 300.4168238905249))

-- Phase
phase :: Double
phase = moonPhase j2010MoonDetails jd
-- 2.4716141948212922e-2


sunEC1 :: EquatorialCoordinates1
sunEC1 = sunPosition2 jd
-- EC1 {e1Declination = DD 23.37339098989099, e1RightAscension = DH 6.29262026252748}

limbAngle :: DecimalDegrees
limbAngle = moonBrightLimbPositionAngle ec1 sunEC1
-- DD 287.9869373767473
@
-}

module Data.Astro.Moon
(
  moonPosition1
  , moonPosition2
  , moonDistance1
  , moonAngularSize
  , moonHorizontalParallax
  , moonPhase
  , moonBrightLimbPositionAngle
)

where

import qualified Data.Astro.Utils as U
import Data.Astro.Types (DecimalDegrees(..), GeographicCoordinates, toRadians, fromRadians, kmToAU)
import Data.Astro.Time.JulianDate (JulianDate(..), numberOfDays)
import Data.Astro.Coordinate (EquatorialCoordinates1(..), EclipticCoordinates(..), eclipticToEquatorial)
import Data.Astro.Planet (planetBrightLimbPositionAngle)
import Data.Astro.Sun (sunDetails, sunMeanAnomaly2, sunEclipticLongitude2)
import Data.Astro.Moon.MoonDetails (MoonDetails(..), MoonDistanceUnits(..), j2010MoonDetails, mduToKm)
import Data.Astro.Effects (parallax)


-- | Reduce the value to the range [0, 360)
reduceDegrees :: DecimalDegrees -> DecimalDegrees
reduceDegrees = U.reduceToZeroRange 360


-- | Calculate Equatorial Coordinates of the Moon with the given MoonDetails and at the given JulianDate.
-- 
-- It is recommended to use 'j2010MoonDetails' as a first parameter.
moonPosition1 :: MoonDetails -> JulianDate -> EquatorialCoordinates1
moonPosition1 md ut =
  let sd = sunDetails ut
      lambdaS = sunEclipticLongitude2 sd
      ms = sunMeanAnomaly2 sd
      mmq = meanMoonQuantities md ut
      MQ lm'' _ nm' = correctedMoonQuantities lambdaS ms mmq
      a = toRadians $ lm''-nm'
      i = toRadians $ mdI md
      y = (sin a) * (cos i)
      x = cos a
      at = reduceDegrees $ fromRadians $ atan2 y x
      lambdaM = at + nm'
      betaM = fromRadians $ asin $ (sin a) * (sin i)
  in eclipticToEquatorial (EcC betaM lambdaM) ut


-- | Calculate Equatorial Coordinates of the Moon with the given MoonDetails,
-- distance to the Moon, geographic coordinates of the onserver,
-- height above sea-level of the observer measured in metres (20 is a good reasonable value for the height)
-- and at the given JulianDate.
-- 
-- It is recommended to use 'j2010MoonDetails' as a first parameter,
-- to obtain the distance to the Moon you can use `moonDistance1` function.
-- `moonPosition2` takes into account parallax effect.
moonPosition2 :: MoonDetails -> MoonDistanceUnits -> GeographicCoordinates -> Double -> JulianDate -> EquatorialCoordinates1
moonPosition2 md distance coords height jd =
  let p = moonPosition1 md jd
  in parallax coords height (kmToAU $ mduToKm distance) jd p


-- | Calculates the Moon's Distance at the given julian date.
-- Returns distance to the Moon
-- moonDistance1 :: JulianDate -> MoonDistanceUnits
-- you can use 'mduToKm' (defined in "Data.Astro.Moon.MoonDetails") to convert result to kilometers
moonDistance1 :: MoonDetails -> JulianDate -> MoonDistanceUnits
moonDistance1 md ut =
  let sd = sunDetails ut
      lambdaS = sunEclipticLongitude2 sd
      ms = sunMeanAnomaly2 sd
      mmq = meanMoonQuantities md ut
      cmq = correctedMoonQuantities lambdaS ms mmq
      mm' = toRadians $ mqAnomaly cmq
      ec = toRadians $ centreEquation mm'
      e = mdE md
  in MDU $ (1 - e*e)/(1+e*(cos(mm'+ec)))


-- | Calculate the Moon's angular size at the given distance.
moonAngularSize :: MoonDistanceUnits -> DecimalDegrees
moonAngularSize (MDU p) = (mdBigTheta j2010MoonDetails) / (DD p)


-- | Calculates the Moon's horizontal parallax at the given distance.
moonHorizontalParallax :: MoonDistanceUnits -> DecimalDegrees
moonHorizontalParallax (MDU p) = (mdPi j2010MoonDetails) / (DD p)


-- | Calculates the Moon's phase (the area of the visible segment expressed as a fraction of the whole disk)
-- at the given universal time.
moonPhase :: MoonDetails -> JulianDate -> Double
moonPhase md ut =
  let sd = sunDetails ut
      lambdaS = sunEclipticLongitude2 sd
      ms = sunMeanAnomaly2 sd
      mmq = meanMoonQuantities md ut
      MQ ml _ _ = correctedMoonQuantities lambdaS ms mmq
      d = toRadians $ ml - lambdaS
      f = 0.5 * (1 - cos d)
  in f



-- | Calculate the Moon's position-angle of the bright limb.
-- It takes the Moon's coordinates and the Sun's coordinates.
-- Position-angle is the angle of the midpoint of the illuminated limb
-- measured eastwards from the north point of the disk.
moonBrightLimbPositionAngle :: EquatorialCoordinates1 -> EquatorialCoordinates1 -> DecimalDegrees
moonBrightLimbPositionAngle = planetBrightLimbPositionAngle


-- | The Moon's quantities
-- Used to store intermidiate results
data MoonQuantities = MQ {
  mqLongitude :: DecimalDegrees        -- ^ the Moon's longitude
  , mqAnomaly :: DecimalDegrees        -- ^ the Moon's anomaly
  , mqAscendingNode :: DecimalDegrees  -- ^ the Moon's ascending node's longitude
  }


-- | Calculates the Moon's mean quantities on the given date.
-- It takes the Moon's orbita details and julian date
meanMoonQuantities :: MoonDetails -> JulianDate -> MoonQuantities
meanMoonQuantities md ut =
  let d = DD $ numberOfDays (mdEpoch md) ut
      lm = reduceDegrees $ (mdL md) + 13.1763966*d  -- Moon's mean longitude
      mm = reduceDegrees $ lm - 0.1114041*d - (mdP md)  -- Moon's mean anomaly
      nm = reduceDegrees $ (mdN md) - 0.0529539*d  -- ascending node's mean longitude
  in MQ lm mm nm


-- | Calculates correction for the equation of the centre
-- It takes the Moon's corrected anomaly in radians
centreEquation :: Double -> DecimalDegrees
centreEquation mm = DD $ 6.2886 * (sin mm)


-- | Calculates the Moon's corrected longitude, anomaly and asceding node's longitude
-- It takes the Sun's longitude, the Sun's mean anomaly and the Moon's mean quantities
correctedMoonQuantities :: DecimalDegrees -> DecimalDegrees -> MoonQuantities -> MoonQuantities
correctedMoonQuantities lambdaS ms (MQ lm mm nm) =
  let ms' = toRadians ms
      c = lm - lambdaS
      ev = DD $ 1.2739 * (sin $ toRadians $ 2*c - mm)  -- correction for evection
      ae = DD $ 0.1858 * (sin ms')  -- correction for annual equation
      a3 = DD $ 0.37 * (sin ms')  -- third correction
      mm' = mm + (ev - ae - a3) -- Moon's corrected anomaly
      mm'' = toRadians mm'
      ec = centreEquation mm''  -- correction for the equation of the centre
      a4 = DD $ 0.214 * (sin $ 2*mm'') -- fourth correction term
      lm' = lm + (ev + ec -ae + a4) -- Moon's corrected longitude
      v = DD $ 0.6583 * (sin $ toRadians $ 2*(lm' - lambdaS))-- correction for variation
      lm'' = lm' + v -- Moon's true orbital longitude
      nm' = nm - (DD $ 0.16 * (sin ms')) -- ascending node's corrected longitude
  in MQ lm'' mm' nm'