module Aws.Ddb.Commands.JTypes (
  Attribute (..),
  ExpectedAttribute (..),
  AttrValue (..),
  ConsumedCapacity (..),
  ReturnConsumedCapacity (..),
  ReturnCollectionMetrics (..)
  ) where

import qualified Data.Either                 as E
import qualified Data.Vector                 as V
import qualified Data.Text                   as T
import qualified Data.ByteString             as B
import qualified Data.List                   as L
import qualified Data.Text.Read              as TR
import qualified Data.HashMap.Strict         as HM
import Control.Monad                           (mzero)
import Control.Applicative                     ((<$>), (<*>))
import           Data.Aeson                    (Value (Object, Array, String),
                                                ToJSON (..), FromJSON (..), object, (.:), (.=))

data AttrValue = S T.Text       | SS [T.Text]       |
                 B B.ByteString | BS [B.ByteString] |
                 I Int          | IS [Int]          |
                 D Double       | DS [Double]
               deriving Show

data Attribute = Attribute { iname  :: T.Text,
                             ivalue :: AttrValue }
              deriving Show

instance ToJSON Attribute where
  toJSON (Attribute name value) =
    object [ name .= value ]
    
-- instance FromJSON Attribute where
--   parseJSON (Object o) = let keys = HM.keys o
--                          in map parseAttr keys
--     where
--       parseAttr key = Attribute <$>
--                         o .:? "" .!= key <*>
--                         o .: key

data ExpectedAttribute = ExpectedAttribute { ename   :: T.Text,
                                             exists  :: Bool,
                                             evalue  :: AttrValue }
                  deriving Show

instance ToJSON ExpectedAttribute where
  toJSON (ExpectedAttribute name exists value) =
    object [ "Exists" .= exists,
             name .= value ]


instance ToJSON AttrValue where
  toJSON av =
    case av of
      S   txt  -> object [ "S"  .= txt ]
      SS  txts -> object [ "SS" .= txts]
      B   bs   -> object [ "B"  .= bs  ]
      BS  bss  -> object [ "BS" .= bss ]
      I   i    -> object [ "N"  .= show i ]
      IS  is   -> object [ "NS" .= map show is ]
      D   d    -> object [ "N"  .= show d ]
      DS  ds   -> object [ "NS" .= map show ds ]

-- FIXME RPR :: Aeson must have a better way.
isMaybeInt :: Maybe Value -> Bool
isMaybeInt (Just (String s)) = case TR.decimal s :: E.Either String (Integer, T.Text) of
                                 Right _ -> True
                                 Left  _ -> False
isMaybeInt _ = False

-- FIXME RPR - OUCH OUCH!!  parsing the string twice :(
allInts :: Maybe Value -> Bool
allInts (Just (Array vs)) = L.all isInt (V.toList vs)
  where
    isInt (String s) = case TR.decimal s :: E.Either String (Integer, T.Text) of
                         Right _ -> True
                         Left  _ -> False
    isInt _ = False
allInts _ = False

instance FromJSON AttrValue where
  parseJSON (Object o) = let typeCode = L.head (HM.keys o)
                         in
                          case typeCode of
                            "S"  -> S  <$> (o .: "S")
                            "SS" -> SS <$> (o .: "SS")
                            "B"  -> B  <$> (o .: "B")
                            "BS" -> BS <$> (o .: "BB")
                            "N"  -> parseN
                            "NS" -> parseNS
                            _    -> mzero
    where
      parseN  = if isMaybeInt $ HM.lookup "N" o then parseI
                else parseD
      parseNS = if allInts $ HM.lookup "NS" o then parseIS
                else parseDS
      parseI  = I <$> (o .: "N")
      parseD  = D <$> (o .: "N")
      parseIS = IS <$> (o .: "N")
      parseDS = DS <$> (o .: "N")

  parseJSON _ = mzero

newtype ReturnConsumedCapacity = ReturnConsumedCapacity Bool
                               deriving Show

instance ToJSON ReturnConsumedCapacity where
  toJSON (ReturnConsumedCapacity rcc) = if rcc then "TOTAL" else "NONE"

newtype ReturnCollectionMetrics = ReturnCollectionMetrics Bool
                                deriving Show

instance ToJSON ReturnCollectionMetrics where
  toJSON (ReturnCollectionMetrics ricm) = if ricm then "SIZE" else "NONE"

data ConsumedCapacity = ConsumedCapacity { ccTableName :: T.Text,
                                           ccUnits     :: Int }
                      deriving Show

instance FromJSON ConsumedCapacity where
  parseJSON (Object o) = ConsumedCapacity <$>
                         o .: "TableName" <*>
                         o .: "CapacityUnits"
  parseJSON _ = mzero
