-- |Netrium is Copyright Anthony Waite, Dave Hewett, Shaun Laurens & Contributors 2009-2018, and files herein are licensed
-- |under the MIT license,  the text of which can be found in license.txt
--
-- The definition of the basic contract language
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Contract (
    -- * Contracts
    -- ** The contract type and primitives
    Contract(..),
    zero, one,
    and, give, party,
    or, cond,
    scale, ScaleFactor,
    when, anytime, until,
    read, letin,

    -- ** Tradable items
    Tradeable(..),
    Commodity(..), Unit(..), Location(..), Duration(..),
    Currency(..), CashFlowType(..), Portfolio(..),

    -- ** Choice identifiers
    ChoiceId, PartyName,

    -- * Observables
    Obs,
    konst, var, primVar, primCond,
    Time,
    at, before, after, between,
    ifthen, negate, max, min, abs,
    (%==),
    (%>), (%>=), (%<), (%<=),
    (%&&), (%||),
    (%+), (%-), (%*), (%/),
  ) where

import Observable
         ( Time
         , Obs, konst, var, primVar, primCond, at, before, after, between
         , (%==), (%>), (%>=), (%<), (%<=)
         , (%&&), (%||), (%+), (%-), (%*), (%/)
         , ifthen, negate, max, min, abs
         , parseObsCond, parseObsReal, printObs )
import Display
import XmlUtils

import Prelude hiding (product, read, until, and, or, min, max, abs, not, negate)
import Control.Monad hiding (when)
import Text.XML.HaXml.Namespaces (localName)
import Text.XML.HaXml.Types (QName(..))
import Text.XML.HaXml.XmlContent

-- * Contract type definition
-- | A canonical tradeable element, physical or financial
data Tradeable = Physical  Commodity Unit Location (Maybe Duration) (Maybe Portfolio)
               | Financial Currency CashFlowType (Maybe Portfolio)
  deriving (Eq, Show)

-- | A duration is a span of time, measured in seconds.
--
newtype Duration  = Duration  Int {- in sec -} deriving (Eq, Show, Num)
-- | Commodity, e.g. Gas, Electricity
newtype Commodity = Commodity String           deriving (Eq, Show)
-- | Unit, e.g. tonnes, MWh
newtype Unit      = Unit      String           deriving (Eq, Show)
-- | Location, e.g. UK, EU
newtype Location  = Location  String           deriving (Eq, Show)
-- | Currency, e.g. EUR, USD, GBP
newtype Currency  = Currency  String           deriving (Eq, Show)
-- | Cashflow type, e.g. cash, premium
newtype CashFlowType = CashFlowType String     deriving (Eq, Show)
-- | Portfolio name
newtype Portfolio = Portfolio String           deriving (Eq, Show)

-- | Scaling factor (used to scale the 'One' contract)
type ScaleFactor  = Double

-- | Choice label, used for options
type ChoiceId = String

-- | Name of a third party mentioned in a contract
type PartyName = String

-- | The main contract data type
--
data Contract
   = Zero
   | One  Tradeable

   | Give Contract
   | Party PartyName Contract
   | And  Contract Contract

   | Or      ChoiceId     Contract Contract
   | Cond    (Obs Bool)   Contract Contract

   | Scale    (Obs Double) Contract
   | Read Var (Obs Double) Contract

   | When             (Obs Bool)   Contract
   | Anytime ChoiceId (Obs Bool)   Contract
   | Until            (Obs Bool)   Contract
  deriving (Eq, Show)

-- | A variable
type Var = String

-- | The @zero@ contract has no rights and no obligations.
zero :: Contract
zero = Zero

-- | If you acquire @one t@ you immediately recieve one unit of the
-- 'Tradeable' @t@.
one :: Tradeable -> Contract
one = One

-- | Swap the rights and obligations of the party and counterparty.
give :: Contract -> Contract
give = Give

-- | Make a contract with a named 3rd party as the counterparty.
party :: PartyName -> Contract -> Contract
party = Party

-- | If you acquire @c1 `and` c2@ you immediately acquire /both/ @c1@ and @c2@.
and :: Contract -> Contract -> Contract
and = And

-- | If you acquire @c1 `or` c2@ you immediately acquire your choice of
-- /either/ @c1@ or @c2@.
or :: ChoiceId -> Contract -> Contract -> Contract
or = Or
--TODO: document the ChoiceId

-- | If you acquire @cond obs c1 c2@ then you acquire @c1@ if the observable
-- @obs@ is true /at the moment of acquistion/, and @c2@ otherwise.
cond :: Obs Bool -> Contract -> Contract -> Contract
cond = Cond

-- | If you acquire @scale obs c@, then you acquire @c@ at the same moment
-- except that all the subsequent trades of @c@ are multiplied by the value
-- of the observable @obs@ /at the moment of acquistion/.
scale :: Obs ScaleFactor -> Contract -> Contract
scale = Scale

read :: Var -> Obs Double -> Contract -> Contract
read = Read
{-# DEPRECATED read "Use 'letin' instead." #-}

-- | If you acquire @when obs c@, you must acquire @c@ as soon as observable
-- @obs@ subsequently becomes true.
when :: Obs Bool -> Contract -> Contract
when = When

-- | Once you acquire @anytime obs c@, you /may/ acquire @c@ at any time the
-- observable @obs@ is true.
anytime :: ChoiceId -> Obs Bool -> Contract -> Contract
anytime = Anytime

-- | Once acquired, @until obs c@ is exactly like @c@ except that it /must be
-- abandoned/ when observable @obs@ becomes true.
until :: Obs Bool -> Contract -> Contract
until = Until


-- | Observe the value of an observable now and save its value to use later.
--
-- Currently this requires a unique variable name.
--
-- Example:
--
-- > letin "count" (count-1) $ \count' ->
-- >   ...
--
letin :: String                    -- ^ A unique variable name
      -> Obs Double                -- ^ The observable to observe now
      -> (Obs Double -> Contract)  -- ^ The contract using the observed value
      -> Contract
letin vname obs c = read vname obs (c (var vname))


-- Display tree instances
instance Display Contract where
  toTree Zero           = Node "zero"         []
  toTree (One  t)       = Node "one"          [Node (show t) []]
  toTree (Give c)       = Node "give"         [toTree c]
  toTree (Party p c)    = Node ("party " ++ p)[toTree c]
  toTree (And c1 c2)    = Node "and"          [toTree c1, toTree c2]
  toTree (Or cid c1 c2) = Node ("or " ++ cid) [toTree c1, toTree c2]
  toTree (Cond o c1 c2) = Node "cond"         [toTree o, toTree c1, toTree c2]
  toTree (Scale       o c)  = Node "scale"            [toTree o, toTree c]
  toTree (Read      n o c)  = Node ("read " ++  n)    [toTree o, toTree c]
  toTree (When        o c)  = Node "when"             [toTree o, toTree c]
  toTree (Anytime cid o c)  = Node ("anytime" ++ cid) [toTree o, toTree c]
  toTree (Until       o c)  = Node "until"            [toTree o, toTree c]

-- XML instances
instance HTypeable Tradeable where
    toHType _ = Defined "Tradeable" [] []

instance XmlContent Tradeable where
  parseContents = do
    e@(Elem t _ _) <- element ["Physical","Financial"]
    commit $ interior e $ case localName t of
      "Physical"  -> liftM5 Physical  parseContents parseContents
                                      parseContents parseContents
                                      parseContents
      "Financial" -> liftM3 Financial parseContents parseContents
                                      parseContents
      x           -> fail $ "cannot parse " ++ x

  toContents (Physical c u l d p) =
    [mkElemC "Physical"  (toContents c ++ toContents u
                       ++ toContents l ++ toContents d
                       ++ toContents p)]
  toContents (Financial c t p) =
    [mkElemC "Financial" (toContents c ++ toContents t
                       ++ toContents p)]

instance HTypeable Duration where
    toHType _ = Defined "Duration" [] []

instance XmlContent Duration where
  parseContents = inElement "Duration" (liftM Duration readText)
  toContents (Duration sec) = [mkElemC "Duration" (toText (show sec))]

instance HTypeable Commodity where
    toHType _ = Defined "Commodity" [] []

instance XmlContent Commodity where
  parseContents = inElement "Commodity" (liftM Commodity text)
  toContents (Commodity name) = [mkElemC "Commodity" (toText name)]

instance HTypeable Unit where
    toHType _ = Defined "Unit" [] []

instance XmlContent Unit where
  parseContents = inElement "Unit" (liftM Unit text)
  toContents (Unit name) = [mkElemC "Unit" (toText name)]

instance HTypeable Location where
    toHType _ = Defined "Location" [] []

instance XmlContent Location where
  parseContents = inElement "Location" (liftM Location text)
  toContents (Location name) = [mkElemC "Location" (toText name)]

instance HTypeable Currency where
    toHType _ = Defined "Currency" [] []

instance XmlContent Currency where
  parseContents = inElement "Currency" (liftM Currency text)
  toContents (Currency name) = [mkElemC "Currency" (toText name)]

instance HTypeable CashFlowType where
    toHType _ = Defined "CashFlowType" [] []

instance XmlContent CashFlowType where
  parseContents = inElement "CashFlowType" (liftM CashFlowType text)
  toContents (CashFlowType name) = [mkElemC "CashFlowType" (toText name)]

instance HTypeable Portfolio where
    toHType _ = Defined "Portfolio" [] []

instance XmlContent Portfolio where
  parseContents = inElement "Portfolio" (liftM Portfolio text)
  toContents (Portfolio name) = [mkElemC "Portfolio" (toText name)]

instance HTypeable Contract where
  toHType _ = Defined "Contract" [] []

instance XmlContent Contract where
  parseContents = do
    e@(Elem t _ _) <- element ["Zero","When","Until","Scale","Read"
                              ,"Or","One","Give","Party","Cond","Anytime","And"]
    commit $ interior e $ case localName t of
      "Zero"    -> return Zero
      "One"     -> liftM  One     parseContents
      "Give"    -> liftM  Give    parseContents
      "Party"   -> liftM2 Party   (attrStr (N "name") e) parseContents
      "And"     -> liftM2 And     parseContents parseContents
      "Or"      -> liftM3 Or      (attrStr (N "choiceid") e) parseContents parseContents
      "Cond"    -> liftM3 Cond    parseObsCond  parseContents parseContents
      "Scale"   -> liftM2 Scale   parseObsReal  parseContents
      "Read"    -> liftM3 Read    (attrStr (N "var") e) parseObsReal  parseContents
      "When"    -> liftM2 When    parseObsCond  parseContents
      "Anytime" -> liftM3 Anytime (attrStr (N "choiceid") e) parseObsCond  parseContents
      "Until"   -> liftM2 Until   parseObsCond  parseContents
      x         -> fail $ "cannot parse " ++ x

  toContents Zero           = [mkElemC  "Zero" []]
  toContents (One t)        = [mkElemC  "One"  (toContents t)]
  toContents (Give c)       = [mkElemC  "Give" (toContents c)]
  toContents (Party p c)    = [mkElemAC (N "Party")   [(N "name", str2attr p)]
                                                      (toContents c)]
  toContents (And    c1 c2) = [mkElemC  "And"  (toContents c1 ++ toContents c2)]
  toContents (Or cid c1 c2) = [mkElemAC (N "Or")      [(N "choiceid", str2attr cid)]
                                                      (toContents c1 ++ toContents c2)]
  toContents (Cond o c1 c2) = [mkElemC  "Cond" (printObs o : toContents c1 ++ toContents c2)]
  toContents (Scale  o c)      = [mkElemC  "Scale"    (printObs o : toContents c)]
  toContents (Read n o c)      = [mkElemAC (N "Read") [(N "var", str2attr n)]
                                                      (printObs o : toContents c)]
  toContents (When   o c)      = [mkElemC  "When"     (printObs o : toContents c)]
  toContents (Anytime cid o c) = [mkElemAC (N "Anytime") [(N "choiceid", str2attr cid)]
                                                         (printObs o : toContents c)]
  toContents (Until  o c)      = [mkElemC  "Until"    (printObs o : toContents c)]
