-- |
-- ICAO Field Type 18 - Other information.
-- This field is a collection of switches all optional, use the withXXXX function to
-- build a new instance starting from 'emptyOtherInformation'.
module Data.Icao.OtherInformation
    (
    -- * Data
      OtherInformation(..)
    , PbnCapabilityCode(..)
    , SpecicalHandlingReason(..)
    , EstimatedElapsedTime(..)
    , SelCalCode
    -- * Builders
    , emptyOtherInformation
    , withSpecialHandlingReason
    , withPbnCapabilities
    , withNavigationEquipments
    , withCommunicationEquipments
    , withDataCommunicationEquipments
    , withSurveillanceEquipments
    , withDeparture
    , withDestination
    , withDof
    , withRegistration
    , withEstimatedElapsedTimes
    , withSelCalCode
    -- * Smart constructors
    , mkSelCalCode
    ) where

import Control.Monad.Fail
import Data.Char
import Data.Icao.Lang
import Data.Icao.Location
import Data.Icao.Time
import Data.List
import Data.Maybe ()
import Prelude hiding (fail)

-- | Code describing PBN (Performance Base Navigation) capabilities.
-- Provides both RNAV (Area Navigation) and RNP (Required Navigation Performance) capabilities.
-- <https://en.wikipedia.org/wiki/Area_navigation>
-- <https://en.wikipedia.org/wiki/Required_navigation_performance>
data PbnCapabilityCode
    -- | RNAV
    = A1 -- ^ RNAV 10 (RNP 10)
    | B1 -- ^ RNAV 5 all permitted sensors
    | B2 -- ^ RNAV 5 GNSS
    | B4 -- ^ RNAV 5 VOR/DME
    | B3 -- ^ RNAV 5 DME/DME
    | B5 -- ^ RNAV 5 INS or IRS
    | B6 -- ^ RNAV 5 LORANC
    | C1 -- ^ RNAV 2 all permitted sensors
    | C2 -- ^ RNAV 2 GNSS
    | C3 -- ^ RNAV 2 DME/DME
    | C4 -- ^ RNAV 2 DME/DME/IRU
    | D1 -- ^ RNAV 1 all permitted sensors
    | D2 -- ^ RNAV 1 GNSS
    | D3 -- ^ RNAV 1 DME/DME
    | D4 -- ^ RNAV 1 DME/DME/IRU
    -- | RNP
    | L1 -- ^ RNP 4
    | O1 -- ^ Basic RNP 1 all permitted sensors
    | O2 -- ^ Basic RNP 1 GNSS
    | O3 -- ^ Basic RNP 1 DME/DME
    | O4 -- ^ Basic RNP 1 DME/DME/IRU
    | S1 -- ^ RNP APCH
    | S2 -- ^ RNP APCH with BAR-VNAV
    | T1 -- ^ RNP AR APCH with RF (special authorization required)
    | T2 -- ^ RNP AR APCH without RF (special authorization required
    deriving (Bounded, Enum, Eq, Read, Show)

-- | Reason for special handling.
data SpecicalHandlingReason
    = ALTRV -- ^ for a flight operated in accordance with an altitude reservation.
    | ATFMX -- ^ for a flight approved for exemption from ATFM measures by the appropriate ATS authority.
    | FFR -- ^ fire-fighting.
    | FLTCK -- ^ flight check for calibration of navaids.
    | HAZMAT -- ^ for a flight carrying hazardous material.
    | HEAD -- ^ a flight with Head of State status.
    | HOSP -- ^ for a medical flight declared by medical authorities.
    | HUM -- ^ for a flight operating on a humanitarian mission.
    | MARSA -- ^ for a flight for which a military entity assumes responsibility for separation of military aircraft.
    | MEDEVAC -- ^ for a life critical medical emergency evacuation.
    | NONRVSM -- ^ for a non-RVSM capable flight intending to operate in RVSM airspace.
    | SAR -- ^ for a flight engaged in a search and rescue mission.
    | STATE -- ^ for a flight engaged in military, customs or police services.
    deriving (Bounded, Enum, Eq, Read, Show)

-- | A Significant point or FIR boundary designator and the accumulated estimated
-- elapsed time from take-off.
data EstimatedElapsedTime = EstimatedElapsedTime
    { location :: SignificantPoint -- ^ Significant point or FIR boundary designator
    , duration :: Hhmm -- time from take-off to location
    } deriving (Eq, Show)

-- | Selective radio calling (SELCAL or SelCal) Code.
-- The code is a sequence of four letters, written or transmitted as
-- an ordered two sets of two letters each (e.g., AB-CD).
-- Valid letter are A through S, excluding I, N and O. The letters within a given
-- pair are written or transmitted in alphabetical order (e.g., AB-CD is an allowable
-- distinct SELCAL code, as is CD-AB, but CD-BA is not).
-- A given letter can be used only once in a SELCAL code; letters may not be repeated
-- (e.g., AB-CD is allowable, but AA-BC and AB-BC are not).
newtype SelCalCode =
    SelCalCode String
    deriving (Eq, Show)

-- | Other information.
data OtherInformation = OtherInformation
    { specicalHandlingReason :: Maybe SpecicalHandlingReason -- ^ Reason for special handling by ATS, e.g. a search and rescue mission.
    , pbnCapabilities :: [PbnCapabilityCode] -- ^ RNAV and RNP capabilites.
    , navigationEquipments :: Maybe FreeText -- ^ Significant data related to navigation equipment, other than specified in PBN/,
                                             -- as required by the appropriate ATS authority. Indicate GNSS augmentation
                                             -- under this indicator, with a space between two or more methods of
                                             -- augmentation, e.g. NAV/GBAS SBAS.
    , communicationEquipments :: Maybe FreeText -- ^ Indicate communication equipment and capabilities not specified in Item 10 a).
    , dataCommunicationEquipments :: Maybe FreeText -- ^ Indicate data communication equipment and capabilities not specified in 10 a).
    , surveillanceEquipments :: Maybe FreeText -- ^ Indicate surveillance equipment and capabilities not specified in Item 10 b).
                                               -- Indicate as many RSP specification(s) as apply to the flight, using designator(s) with no space.
                                               -- Multiple RSP specifications are separated by a space. Example: RSP180 RSP400.
    , departure :: Maybe SignificantPoint -- ^ Name and location of departure aerodrome, if ZZZZ is inserted in Item 13, or the ATS unit from
                                          -- which supplementary flight plan data can be obtained, if AFIL is inserted in Item 13.
    , destination :: Maybe SignificantPoint -- ^ Name and location of destination aerodrome, if ZZZZ is inserted in Item 16.
    , dof :: Maybe Date -- ^ The date of flight departure in a six-figure format.
    , registration :: Maybe FreeText -- ^ The nationality or common mark and registration mark of the aircraft,
                                     -- if different from the aircraft identification in Item 7.
    , eets :: [EstimatedElapsedTime] -- Significant points or FIR boundary designators and accumulated estimated elapsed times
                                     -- from take-off to such points or FIR boundaries, when so prescribed on the basis of regional
                                     -- air navigation agreements, or by the appropriate ATS authority.
    , selCalCode :: Maybe SelCalCode -- ^ SELCAL Code, for aircraft so equipped.
    } deriving (Show, Eq)

allowedLetter :: Char -> Bool
allowedLetter l = l `elem` "ABCDEFGHJKLMPQRS"

isValidPair :: String -> Bool
isValidPair s = allowedLetter h && allowedLetter t && h < t
  where
    h = head s
    t = last s

-- | 'SelCalCode' smart constructor. Fails if given string is not a valid
-- SELCAL code.
mkSelCalCode
    :: (MonadFail m)
    => String -> m SelCalCode
mkSelCalCode s
    | isValid s = return (SelCalCode s)
    | otherwise = fail ("invalid SELCAL code=" ++ s)
  where
    isValid :: String -> Bool
    isValid c =
        all isUpper c && -- all upper
        length c == 4 && -- 4 char
        length (nub c) == 4 && -- no duplicate
        isValidPair (take 2 c) && -- first pair is valid
        isValidPair (drop 2 c) -- second pair is valid

-- | Returns empty 'OtherInformation'.
emptyOtherInformation :: OtherInformation
emptyOtherInformation =
    OtherInformation
        Nothing
        []
        Nothing
        Nothing
        Nothing
        Nothing
        Nothing
        Nothing
        Nothing
        Nothing
        []
        Nothing

-- | Sets the reason for special handling.
withSpecialHandlingReason :: SpecicalHandlingReason -> OtherInformation -> OtherInformation
withSpecialHandlingReason s oi = oi {specicalHandlingReason = Just s}

-- | Sets the performance based capabilities.
withPbnCapabilities :: [PbnCapabilityCode] -> OtherInformation -> OtherInformation
withPbnCapabilities p oi = oi {pbnCapabilities = p}

-- | Sets the navigation equipments.
withNavigationEquipments :: FreeText -> OtherInformation -> OtherInformation
withNavigationEquipments n oi = oi {navigationEquipments = Just n}

-- | Sets the communication equipments.
withCommunicationEquipments :: FreeText -> OtherInformation -> OtherInformation
withCommunicationEquipments c oi = oi {navigationEquipments = Just c}

-- | Sets the data communication equipments.
withDataCommunicationEquipments :: FreeText -> OtherInformation -> OtherInformation
withDataCommunicationEquipments d oi = oi {navigationEquipments = Just d}

-- | Sets the surveillance equipments.
withSurveillanceEquipments :: FreeText -> OtherInformation -> OtherInformation
withSurveillanceEquipments s oi = oi {navigationEquipments = Just s}

-- | Sets the departure location.
withDeparture :: SignificantPoint -> OtherInformation -> OtherInformation
withDeparture d oi = oi {departure = Just d}

-- | Sets the destination location.
withDestination :: SignificantPoint -> OtherInformation -> OtherInformation
withDestination d oi = oi {destination = Just d}

-- | Sets the date of flight.
withDof :: Date -> OtherInformation -> OtherInformation
withDof d oi = oi {dof = Just d}

-- | Sets the aircraft registration.
withRegistration :: FreeText -> OtherInformation -> OtherInformation
withRegistration r oi = oi {registration = Just r}

-- | Sets the accumulated estimated elapsed times to location and/or FIR boundaries.
withEstimatedElapsedTimes :: [EstimatedElapsedTime] -> OtherInformation -> OtherInformation
withEstimatedElapsedTimes e oi = oi {eets = e}

-- | Sets the SELCAL code.
withSelCalCode :: SelCalCode -> OtherInformation -> OtherInformation
withSelCalCode c oi = oi {selCalCode = Just c}