-- 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. An additional grant
-- of patent rights can be found in the PATENTS file in the same directory.


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

module Duckling.Quantity.Types where

import Control.DeepSeq
import Data.Aeson
import Data.Hashable
import Data.Text (Text)
import GHC.Generics
import Prelude
import Duckling.Resolve (Resolve(..))
import qualified Data.HashMap.Strict as H
import qualified Data.Text as Text

data Unit
  = Bowl
  | Cup
  | Custom Text
  | Dish
  | Gram
  | Ounce
  | Pint
  | Pound
  | Quart
  | Tablespoon
  | Teaspoon
  | Unnamed
  deriving (Eq, Generic, Hashable, Ord, Show, NFData)

instance ToJSON Unit where
  toJSON (Custom x) = String $ Text.toLower x
  toJSON x = String . Text.toLower . Text.pack $ show x

data QuantityData = QuantityData
  { unit :: Maybe Unit
  , value :: Maybe Double
  , aproduct :: Maybe Text
  , minValue :: Maybe Double
  , maxValue :: Maybe Double
  } deriving (Eq, Generic, Hashable, Ord, Show, NFData)


instance Resolve QuantityData where
  type ResolvedValue QuantityData = QuantityValue

  resolve _ _ QuantityData {value = Just value
                         , unit = Just unit
                         , aproduct = aproduct}
   = Just (simple unit value aproduct, False)

  resolve _ _ QuantityData {value = Nothing
                         , unit = Just unit
                         , aproduct = aproduct
                         , minValue = Just from
                         , maxValue = Just to}
   = Just (between unit (from, to) aproduct, False)

  resolve _ _ QuantityData {value = Nothing
                         , unit = Just unit
                         , aproduct = aproduct
                         , minValue = Just from
                         , maxValue = Nothing}
   = Just (above unit from aproduct, False)

  resolve _ _ QuantityData {value = Nothing
                         , unit = Just unit
                         , aproduct = aproduct
                         , minValue = Nothing
                         , maxValue = Just to}
   = Just (under unit to aproduct, False)

  resolve _ _ _ = Nothing

data IntervalDirection = Above | Under
  deriving (Eq, Generic, Hashable, Ord, Show, NFData)

data SingleValue = SingleValue
    { vUnit :: Unit
    , vValue :: Double
    , vProduct :: Maybe Text
    }
    deriving (Eq, Generic, Hashable, Ord, Show, NFData)

instance ToJSON SingleValue where
    toJSON (SingleValue unit value aproduct) = object $
      [ "value" .= value
      , "unit" .= unit
      ]
      ++ [ "product" .= p | Just p <- [aproduct] ]


data QuantityValue
  = SimpleValue SingleValue
  | IntervalValue (SingleValue, SingleValue)
  | OpenIntervalValue (SingleValue, IntervalDirection)
  deriving (Eq, Ord, Show)

instance ToJSON QuantityValue where
  toJSON (SimpleValue value) = case toJSON value of
    Object o -> Object $ H.insert "type" (toJSON ("value" :: Text)) o
    _ -> Object H.empty
  toJSON (IntervalValue (from, to)) = object
    [ "type" .= ("interval" :: Text)
    , "from" .= toJSON from
    , "to" .= toJSON to
    ]
  toJSON (OpenIntervalValue (from, Above)) = object
    [ "type" .= ("interval" :: Text)
    , "from" .= toJSON from
    ]
  toJSON (OpenIntervalValue (to, Under)) = object
    [ "type" .= ("interval" :: Text)
    , "to" .= toJSON to
    ]
-- -----------------------------------------------------------------
-- Value helpers

simple :: Unit -> Double -> Maybe Text -> QuantityValue
simple u v p = SimpleValue $ single u v p

between :: Unit -> (Double, Double) -> Maybe Text -> QuantityValue
between u (from,to) p = IntervalValue (single u from p, single u to p)

above :: Unit -> Double -> Maybe Text -> QuantityValue
above = openInterval Above

under :: Unit -> Double -> Maybe Text -> QuantityValue
under = openInterval Under

openInterval :: IntervalDirection
                     -> Unit
                     -> Double
                     -> Maybe Text
                     -> QuantityValue
openInterval direction u v p = OpenIntervalValue (single u v p, direction)

single :: Unit -> Double -> Maybe Text -> SingleValue
single u v p = SingleValue {vUnit = u, vValue = v, vProduct = p}