{-# LANGUAGE NoImplicitPrelude #-}

module Data.Aviation.VFR_Waypoints.Search(
  all_VFR_Waypoint_codes_index
, all_VFR_Waypoint_names_index
, searchIndexCode
, searchIndexName
, searchIndexCodeName
, searchFuzzyCode
, searchFuzzyName
, searchFuzzyCodeName
) where

import Control.Applicative((<|>))
import Control.Category((.))
import Control.Lens(_Wrapped, (^.))
import Data.Aviation.VFR_Waypoints(VFR_Waypoint(VFR_Waypoint), Latitude, Longitude, all_VFR_Waypoint, code, name)
import Data.Bool(Bool)
import Data.Char(isAlpha, toUpper)
import Data.Foldable(foldl')
import Data.Functor((<$>), fmap)
import Data.Function(($))
import Data.List(sortBy, filter)
import Data.Map(Map)
import qualified Data.Map as Map(fromList, lookup, insertWith, toList)
import Data.Maybe(Maybe)
import Data.Monoid.Textual(TextualMonoid)
import Data.Ord(Ord((>), compare))
import Data.String(String)
import qualified Text.Fuzzy as Fuzzy(filter, score)
import Text.Fuzzy(Fuzzy(Fuzzy))

all_VFR_Waypoint_codes_index ::
  Map String (String, Maybe String, Latitude, Longitude)
all_VFR_Waypoint_codes_index =
  Map.fromList ((\(VFR_Waypoint _name _state _code _lat _lon) -> (_code,  (_name, _state, _lat, _lon))) <$> all_VFR_Waypoint ^. _Wrapped)

all_VFR_Waypoint_names_index ::
  Map String (Maybe String, String, Latitude, Longitude)
all_VFR_Waypoint_names_index =
  Map.fromList ((\(VFR_Waypoint _name _state _code _lat _lon) -> (_name,  (_state, _code, _lat, _lon))) <$> all_VFR_Waypoint ^. _Wrapped)

searchIndexCode ::
  String
  -> Maybe VFR_Waypoint
searchIndexCode s =
  let s' = filter isAlpha . fmap toUpper $ s
  in  (\(_name, _state, _lat, _lon) -> VFR_Waypoint _name _state s' _lat _lon) <$> Map.lookup s' all_VFR_Waypoint_codes_index

searchIndexName ::
  String
  -> Maybe VFR_Waypoint
searchIndexName s =
  let s' = filter isAlpha . fmap toUpper $ s
  in  (\(_state, _code, _lat, _lon) -> VFR_Waypoint s' _state _code _lat _lon) <$> Map.lookup s' all_VFR_Waypoint_names_index

searchIndexCodeName ::
  String
  -> Maybe VFR_Waypoint
searchIndexCodeName s =
  searchIndexCode s <|> searchIndexName s

searchFuzzyCode ::
  String
  -> String
  -> String
  -> Bool
  -> [Fuzzy VFR_Waypoint String]
searchFuzzyCode s before after cas =
  Fuzzy.filter s (all_VFR_Waypoint ^. _Wrapped) before after (^. code) cas

searchFuzzyName ::
  String
  -> String
  -> String
  -> Bool
  -> [Fuzzy VFR_Waypoint String]
searchFuzzyName s before after cas =
  Fuzzy.filter s (all_VFR_Waypoint ^. _Wrapped) before after (^. name) cas

searchFuzzyCodeName ::
  String
  -> String
  -> String
  -> Bool
  -> [Fuzzy VFR_Waypoint String]
searchFuzzyCodeName s before after cas =
  filter2 s (all_VFR_Waypoint ^. _Wrapped) before after ((^. code), (^. name)) cas

-- https://hackage.haskell.org/package/fuzzy-0.1.0.0/docs/Text-Fuzzy.html#v:filter
filter2 ::
  (Ord t, TextualMonoid s) =>
  s
  -> [t]
  -> s
  -> s
  -> (t -> s, t -> s)
  -> Bool
  -> [Fuzzy t s]
filter2 pattern values before after (extract1, extract2) cas =
  let x1 = Fuzzy.filter pattern values before after extract1 cas
      x2 = Fuzzy.filter pattern values before after extract2 cas
      x1' = Map.fromList ((\(Fuzzy o r s) -> (o, (r, s))) <$> x1)
      x2' = foldl' (\m (Fuzzy o r s) -> Map.insertWith (\(s1, i1) (s2, i2) -> if i2 > i1 then (s2, i2) else (s1, i1)) o (r, s) m) x1' x2
  in  sortBy (\f1 f2 -> Fuzzy.score f2 `compare` Fuzzy.score f1) ((\(o, (r, s)) -> Fuzzy o r s) <$> Map.toList x2')