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

module Data.Aeson.Result
  ( Ok (..)
  , Err (..)
  , err
  , ok
  , fromOk
  , toOk
  , throwError

  , From
  , Size
  , Total
  , List (..)
  , emptyList
  , merge

  , toList
  , fromList
  ) where

import           Control.Exception (Exception, throwIO)
import           Data.Aeson        (FromJSON (..), Result (..), ToJSON (..),
                                    Value, fromJSON, object, withObject, (.:),
                                    (.=))
import           Data.Aeson.Helper (replace)
import           Data.Int          (Int64)
import           Data.Text         (Text)

type From  = Int64
type Size  = Int64
type Total = Int64

-- | Make ok result look like    '{"data": "data value"}'
newtype Ok a = Ok { getValue :: a }
  deriving (Show)

instance (FromJSON a) => FromJSON (Ok a) where
  parseJSON = withObject "Ok" $ \o -> do
    getValue <- o .: "result"
    return Ok{..}

instance (ToJSON a) => ToJSON (Ok a) where
  toJSON Ok{..} = object [ "result" .= getValue ]

-- | Make error result look like    '{"err": "error message"}'
newtype Err = Err { errMsg :: String }
  deriving (Show, Eq, Ord)

instance Exception Err

-- | Throw error to IO
throwError :: Err -> IO a
throwError = throwIO

instance FromJSON Err where
  parseJSON = withObject "Err" $ \o -> do
    errMsg <- o .: "err"
    return Err{..}

instance ToJSON Err where
  toJSON Err{..} = object [ "err" .= errMsg ]

-- | Initial Err
err :: String -> Err
err = Err


-- | Initial Ok
ok :: a -> Ok a
ok = Ok

-- | Make a JSON result to Ok
toOk :: FromJSON a => Text -> Value -> Maybe (Ok a)
toOk okey v =
  case fromJSON (replace okey "result" v) of
    Success v' -> Just v'
    _          -> Nothing

-- | Make an Ok to JSON
fromOk :: ToJSON a => Text -> Ok a -> Value
fromOk key ret = replace "result" key $ toJSON ret

-- | Make list result look like '{"users": ["user1"], "total": 1, "from": 0, "size": 10}'
data List a = List
  { getFrom   :: From
  , getSize   :: Size
  , getTotal  :: Total
  , getResult :: [a]
  }
  deriving (Show)


instance FromJSON a => FromJSON (List a) where
  parseJSON = withObject "List" $ \o -> do
    getFrom   <- o .: "from"
    getSize   <- o .: "size"
    getTotal  <- o .: "total"
    getResult <- o .: "result"
    return List{..}

instance ToJSON a => ToJSON (List a) where
  toJSON List{..} = object
    [ "from"   .= getFrom
    , "size"   .= getSize
    , "total"  .= getTotal
    , "result" .= getResult
    ]

-- | Empty list
emptyList :: List a
emptyList = List
  { getFrom = 0
  , getSize = 10
  , getTotal = 0
  , getResult = []
  }

-- | Merge two list, from size and total, result is replace.
merge :: [a] -> List b -> List a
merge t List
  { getFrom = from
  , getSize = size
  , getTotal = total
  } = List
  { getFrom = from
  , getSize = size
  , getTotal = total
  , getResult = t
  }

-- | Make a JSON to List
toList :: FromJSON a => Text -> Value -> Maybe (List a)
toList okey v =
  case fromJSON (replace okey "result" v) of
    Success v' -> Just v'
    _          -> Nothing

-- | Make a List to JSON
fromList :: ToJSON a => Text -> List a -> Value
fromList key ret = replace "result" key $ toJSON ret