-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.


{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NoRebindableSyntax #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}

module Duckling.Duration.Types where

import Control.DeepSeq
import Data.Aeson
import Data.Hashable
import Data.Semigroup
import Data.Text (Text)
import Data.Tuple.Extra (both)
import GHC.Generics
import TextShow (showt)
import Prelude

import Duckling.Resolve (Resolve(..))
import Duckling.TimeGrain.Types (Grain(..), inSeconds)

data DurationData = DurationData
  { DurationData -> Int
value :: Int
  , DurationData -> Grain
grain :: Grain
  }
  deriving (DurationData -> DurationData -> Bool
(DurationData -> DurationData -> Bool)
-> (DurationData -> DurationData -> Bool) -> Eq DurationData
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: DurationData -> DurationData -> Bool
$c/= :: DurationData -> DurationData -> Bool
== :: DurationData -> DurationData -> Bool
$c== :: DurationData -> DurationData -> Bool
Eq, (forall x. DurationData -> Rep DurationData x)
-> (forall x. Rep DurationData x -> DurationData)
-> Generic DurationData
forall x. Rep DurationData x -> DurationData
forall x. DurationData -> Rep DurationData x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep DurationData x -> DurationData
$cfrom :: forall x. DurationData -> Rep DurationData x
Generic, Int -> DurationData -> Int
DurationData -> Int
(Int -> DurationData -> Int)
-> (DurationData -> Int) -> Hashable DurationData
forall a. (Int -> a -> Int) -> (a -> Int) -> Hashable a
hash :: DurationData -> Int
$chash :: DurationData -> Int
hashWithSalt :: Int -> DurationData -> Int
$chashWithSalt :: Int -> DurationData -> Int
Hashable, Int -> DurationData -> ShowS
[DurationData] -> ShowS
DurationData -> String
(Int -> DurationData -> ShowS)
-> (DurationData -> String)
-> ([DurationData] -> ShowS)
-> Show DurationData
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [DurationData] -> ShowS
$cshowList :: [DurationData] -> ShowS
show :: DurationData -> String
$cshow :: DurationData -> String
showsPrec :: Int -> DurationData -> ShowS
$cshowsPrec :: Int -> DurationData -> ShowS
Show, Eq DurationData
Eq DurationData
-> (DurationData -> DurationData -> Ordering)
-> (DurationData -> DurationData -> Bool)
-> (DurationData -> DurationData -> Bool)
-> (DurationData -> DurationData -> Bool)
-> (DurationData -> DurationData -> Bool)
-> (DurationData -> DurationData -> DurationData)
-> (DurationData -> DurationData -> DurationData)
-> Ord DurationData
DurationData -> DurationData -> Bool
DurationData -> DurationData -> Ordering
DurationData -> DurationData -> DurationData
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: DurationData -> DurationData -> DurationData
$cmin :: DurationData -> DurationData -> DurationData
max :: DurationData -> DurationData -> DurationData
$cmax :: DurationData -> DurationData -> DurationData
>= :: DurationData -> DurationData -> Bool
$c>= :: DurationData -> DurationData -> Bool
> :: DurationData -> DurationData -> Bool
$c> :: DurationData -> DurationData -> Bool
<= :: DurationData -> DurationData -> Bool
$c<= :: DurationData -> DurationData -> Bool
< :: DurationData -> DurationData -> Bool
$c< :: DurationData -> DurationData -> Bool
compare :: DurationData -> DurationData -> Ordering
$ccompare :: DurationData -> DurationData -> Ordering
$cp1Ord :: Eq DurationData
Ord, DurationData -> ()
(DurationData -> ()) -> NFData DurationData
forall a. (a -> ()) -> NFData a
rnf :: DurationData -> ()
$crnf :: DurationData -> ()
NFData)

instance Resolve DurationData where
  type ResolvedValue DurationData = DurationData
  resolve :: Context
-> Options
-> DurationData
-> Maybe (ResolvedValue DurationData, Bool)
resolve Context
_ Options
_ DurationData
x = (DurationData, Bool) -> Maybe (DurationData, Bool)
forall a. a -> Maybe a
Just (DurationData
x, Bool
False)

instance Semigroup DurationData where
  d1 :: DurationData
d1@(DurationData Int
_ Grain
g1) <> :: DurationData -> DurationData -> DurationData
<> d2 :: DurationData
d2@(DurationData Int
_ Grain
g2) = Int -> Grain -> DurationData
DurationData (Int
v1Int -> Int -> Int
forall a. Num a => a -> a -> a
+Int
v2) Grain
g
    where
    g :: Grain
g = Grain
g1 Grain -> Grain -> Grain
forall a. Ord a => a -> a -> a
`min` Grain
g2
    (DurationData Int
v1 Grain
_, DurationData Int
v2 Grain
_) = (DurationData -> DurationData)
-> (DurationData, DurationData) -> (DurationData, DurationData)
forall a b. (a -> b) -> (a, a) -> (b, b)
both (Grain -> DurationData -> DurationData
withGrain Grain
g) (DurationData
d1,DurationData
d2)

instance ToJSON DurationData where
  toJSON :: DurationData -> Value
toJSON DurationData {Int
value :: Int
value :: DurationData -> Int
value, Grain
grain :: Grain
grain :: DurationData -> Grain
grain} = [Pair] -> Value
object
    [ Text
"type"       Text -> Text -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Text -> v -> kv
.= (Text
"value" :: Text)
    , Text
"value"      Text -> Int -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Text -> v -> kv
.= Int
value
    , Text
"unit"       Text -> Grain -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Text -> v -> kv
.= Grain
grain
    , Grain -> Text
forall a. TextShow a => a -> Text
showt Grain
grain  Text -> Int -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Text -> v -> kv
.= Int
value
    , Text
"normalized" Text -> Value -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Text -> v -> kv
.= [Pair] -> Value
object
      [ Text
"unit"  Text -> Text -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Text -> v -> kv
.= (Text
"second" :: Text)
      , Text
"value" Text -> Int -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Text -> v -> kv
.= Grain -> Int -> Int
forall a. Num a => Grain -> a -> a
inSeconds Grain
grain Int
value
      ]
    ]

-- | Convert a duration to the given grain, rounded to the
-- nearest integer. For example, 1 month is converted to 4 weeks.
withGrain :: Grain -> DurationData -> DurationData
withGrain :: Grain -> DurationData -> DurationData
withGrain Grain
g d :: DurationData
d@(DurationData Int
v1 Grain
g1)
  | Grain
g Grain -> Grain -> Bool
forall a. Eq a => a -> a -> Bool
== Grain
g1 = DurationData
d
  | Bool
otherwise = Int -> Grain -> DurationData
DurationData Int
v Grain
g
      where
      v :: Int
v = Double -> Int
forall a b. (RealFrac a, Integral b) => a -> b
round (Double -> Int) -> Double -> Int
forall a b. (a -> b) -> a -> b
$ Grain -> Double -> Double
forall a. Num a => Grain -> a -> a
inSeconds Grain
g1 (Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
v1 :: Double) Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Grain -> Double -> Double
forall a. Num a => Grain -> a -> a
inSeconds Grain
g Double
1