{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

module Database.Bloodhound.Internal.Sort where

import Bloodhound.Import
import Database.Bloodhound.Internal.Newtypes
import Database.Bloodhound.Internal.Query

-- | 'SortMode' prescribes how to handle sorting array/multi-valued fields.
--
-- http://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html#_sort_mode_option
data SortMode
  = SortMin
  | SortMax
  | SortSum
  | SortAvg
  deriving (SortMode -> SortMode -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: SortMode -> SortMode -> Bool
$c/= :: SortMode -> SortMode -> Bool
== :: SortMode -> SortMode -> Bool
$c== :: SortMode -> SortMode -> Bool
Eq, Int -> SortMode -> ShowS
[SortMode] -> ShowS
SortMode -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [SortMode] -> ShowS
$cshowList :: [SortMode] -> ShowS
show :: SortMode -> String
$cshow :: SortMode -> String
showsPrec :: Int -> SortMode -> ShowS
$cshowsPrec :: Int -> SortMode -> ShowS
Show)

instance ToJSON SortMode where
  toJSON :: SortMode -> Value
toJSON SortMode
SortMin = Text -> Value
String Text
"min"
  toJSON SortMode
SortMax = Text -> Value
String Text
"max"
  toJSON SortMode
SortSum = Text -> Value
String Text
"sum"
  toJSON SortMode
SortAvg = Text -> Value
String Text
"avg"

-- | 'mkSort' defaults everything but the 'FieldName' and the 'SortOrder' so
--    that you can concisely describe the usual kind of 'SortSpec's you want.
mkSort :: FieldName -> SortOrder -> DefaultSort
mkSort :: FieldName -> SortOrder -> DefaultSort
mkSort FieldName
fieldName SortOrder
sOrder = FieldName
-> SortOrder
-> Maybe Text
-> Maybe SortMode
-> Maybe Missing
-> Maybe Filter
-> DefaultSort
DefaultSort FieldName
fieldName SortOrder
sOrder forall a. Maybe a
Nothing forall a. Maybe a
Nothing forall a. Maybe a
Nothing forall a. Maybe a
Nothing

-- | 'Sort' is a synonym for a list of 'SortSpec's. Sort behavior is order
--    dependent with later sorts acting as tie-breakers for earlier sorts.
type Sort = [SortSpec]

-- | The two main kinds of 'SortSpec' are 'DefaultSortSpec' and
--    'GeoDistanceSortSpec'. The latter takes a 'SortOrder', 'GeoPoint', and
--    'DistanceUnit' to express "nearness" to a single geographical point as a
--    sort specification.
--
-- <http://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html#search-request-sort>
data SortSpec
  = DefaultSortSpec DefaultSort
  | GeoDistanceSortSpec SortOrder GeoPoint DistanceUnit
  deriving (SortSpec -> SortSpec -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: SortSpec -> SortSpec -> Bool
$c/= :: SortSpec -> SortSpec -> Bool
== :: SortSpec -> SortSpec -> Bool
$c== :: SortSpec -> SortSpec -> Bool
Eq, Int -> SortSpec -> ShowS
[SortSpec] -> ShowS
SortSpec -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [SortSpec] -> ShowS
$cshowList :: [SortSpec] -> ShowS
show :: SortSpec -> String
$cshow :: SortSpec -> String
showsPrec :: Int -> SortSpec -> ShowS
$cshowsPrec :: Int -> SortSpec -> ShowS
Show)

instance ToJSON SortSpec where
  toJSON :: SortSpec -> Value
toJSON
    ( DefaultSortSpec
        ( DefaultSort
            (FieldName Text
dsSortFieldName)
            SortOrder
dsSortOrder
            Maybe Text
dsIgnoreUnmapped
            Maybe SortMode
dsSortMode
            Maybe Missing
dsMissingSort
            Maybe Filter
dsNestedFilter
          )
      ) =
      [Pair] -> Value
object [Text -> Key
fromText Text
dsSortFieldName forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= [Pair] -> Value
omitNulls [Pair]
base]
      where
        base :: [Pair]
base =
          [ Key
"order" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= SortOrder
dsSortOrder,
            Key
"unmapped_type" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Maybe Text
dsIgnoreUnmapped,
            Key
"mode" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Maybe SortMode
dsSortMode,
            Key
"missing" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Maybe Missing
dsMissingSort,
            Key
"nested_filter" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Maybe Filter
dsNestedFilter
          ]
  toJSON (GeoDistanceSortSpec SortOrder
gdsSortOrder (GeoPoint (FieldName Text
field) LatLon
gdsLatLon) DistanceUnit
units) =
    [Pair] -> Value
object
      [ Key
"unit" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= DistanceUnit
units,
        Text -> Key
fromText Text
field forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= LatLon
gdsLatLon,
        Key
"order" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= SortOrder
gdsSortOrder
      ]

-- | 'DefaultSort' is usually the kind of 'SortSpec' you'll want. There's a
--    'mkSort' convenience function for when you want to specify only the most
--    common parameters.
--
--    The `ignoreUnmapped`, when `Just` field is used to set the elastic 'unmapped_type'
--
-- <http://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html#search-request-sort>
data DefaultSort = DefaultSort
  { DefaultSort -> FieldName
sortFieldName :: FieldName,
    DefaultSort -> SortOrder
sortOrder :: SortOrder,
    -- default False
    DefaultSort -> Maybe Text
ignoreUnmapped :: Maybe Text,
    DefaultSort -> Maybe SortMode
sortMode :: Maybe SortMode,
    DefaultSort -> Maybe Missing
missingSort :: Maybe Missing,
    DefaultSort -> Maybe Filter
nestedFilter :: Maybe Filter
  }
  deriving (DefaultSort -> DefaultSort -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: DefaultSort -> DefaultSort -> Bool
$c/= :: DefaultSort -> DefaultSort -> Bool
== :: DefaultSort -> DefaultSort -> Bool
$c== :: DefaultSort -> DefaultSort -> Bool
Eq, Int -> DefaultSort -> ShowS
[DefaultSort] -> ShowS
DefaultSort -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [DefaultSort] -> ShowS
$cshowList :: [DefaultSort] -> ShowS
show :: DefaultSort -> String
$cshow :: DefaultSort -> String
showsPrec :: Int -> DefaultSort -> ShowS
$cshowsPrec :: Int -> DefaultSort -> ShowS
Show)

-- | 'SortOrder' is 'Ascending' or 'Descending', as you might expect. These get
--    encoded into "asc" or "desc" when turned into JSON.
--
-- <http://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html#search-request-sort>
data SortOrder
  = Ascending
  | Descending
  deriving (SortOrder -> SortOrder -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: SortOrder -> SortOrder -> Bool
$c/= :: SortOrder -> SortOrder -> Bool
== :: SortOrder -> SortOrder -> Bool
$c== :: SortOrder -> SortOrder -> Bool
Eq, Int -> SortOrder -> ShowS
[SortOrder] -> ShowS
SortOrder -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [SortOrder] -> ShowS
$cshowList :: [SortOrder] -> ShowS
show :: SortOrder -> String
$cshow :: SortOrder -> String
showsPrec :: Int -> SortOrder -> ShowS
$cshowsPrec :: Int -> SortOrder -> ShowS
Show)

instance ToJSON SortOrder where
  toJSON :: SortOrder -> Value
toJSON SortOrder
Ascending = Text -> Value
String Text
"asc"
  toJSON SortOrder
Descending = Text -> Value
String Text
"desc"

-- | 'Missing' prescribes how to handle missing fields. A missing field can be
--    sorted last, first, or using a custom value as a substitute.
--
-- <http://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html#_missing_values>
data Missing
  = LastMissing
  | FirstMissing
  | CustomMissing Text
  deriving (Missing -> Missing -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Missing -> Missing -> Bool
$c/= :: Missing -> Missing -> Bool
== :: Missing -> Missing -> Bool
$c== :: Missing -> Missing -> Bool
Eq, Int -> Missing -> ShowS
[Missing] -> ShowS
Missing -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Missing] -> ShowS
$cshowList :: [Missing] -> ShowS
show :: Missing -> String
$cshow :: Missing -> String
showsPrec :: Int -> Missing -> ShowS
$cshowsPrec :: Int -> Missing -> ShowS
Show)

instance ToJSON Missing where
  toJSON :: Missing -> Value
toJSON Missing
LastMissing = Text -> Value
String Text
"_last"
  toJSON Missing
FirstMissing = Text -> Value
String Text
"_first"
  toJSON (CustomMissing Text
txt) = Text -> Value
String Text
txt