module Factory (
        Con(..), mkUniqueConId,
        Sub(..), mkUniqueSubId,
        Tx (..), mkUniqueTxId,
        Rec(..), mkUniqueRecc, parseRec)
where

  ------------------------------------------------------------------------
  -- To-do:
  --   - Currently, we use running numbers to uniquely identify 
  --     subscription ids, receipts, transactions, etc.
  --   - A better approach is to use random numbers
  ------------------------------------------------------------------------

  import System.IO.Unsafe
  import Control.Concurrent
  import Data.Char (isDigit)

  ------------------------------------------------------------------------
  -- | Opaque Connection handle.
  --   Only valid within the action passed to /withConnection/. 
  ------------------------------------------------------------------------
  newtype Con = Con Int
    deriving (Eq)

  instance Show Con where
    show (Con i) = show i

  ------------------------------------------------------------------------
  -- Subscription Identifier
  ------------------------------------------------------------------------
  data Sub = Sub Int | NoSub
    deriving (Eq)

  instance Show Sub where
    show (Sub i) = show i
    show (NoSub) = ""

  ------------------------------------------------------------------------
  -- Transaction Identifier
  ------------------------------------------------------------------------
  data Tx = Tx Int | NoTx
    deriving (Eq)

  instance Show Tx where
    show (Tx i) = show i
    show (NoTx) = ""

  ------------------------------------------------------------------------
  -- | This is a receipt.
  ------------------------------------------------------------------------
  data Rec = 
           -- | A valid receipt
           Rec Int 
           -- | No receipt was sent with this interaction.
           --   Receiving a 'NoRec' is not an error,
           --   but the result of an inconsistent - but harmless -
           --   use of /writeQWith/ on a queue that does not
           --   send receipts. An application should, of course,
           --   not try to wait for a 'NoRec'. It will never be confirmed.
           | NoRec
    deriving (Eq)

  instance Show Rec where
    show (Rec i) = show i
    show  NoRec  = ""

  parseRec :: String -> Maybe Rec
  parseRec s = 
    if numeric s then Just (Rec $ read s) else Nothing

  numeric :: String -> Bool
  numeric = all isDigit

  ------------------------------------------------------------------------
  -- Source for unique connection identifiers
  ------------------------------------------------------------------------
  {-# NOINLINE conid #-}
  conid :: MVar Con
  conid = unsafePerformIO $ newMVar (Con 1)

  ------------------------------------------------------------------------
  -- Source for unique subscription identifiers
  ------------------------------------------------------------------------
  {-# NOINLINE subid #-}
  subid :: MVar Sub
  subid = unsafePerformIO $ newMVar (Sub 1)

  ------------------------------------------------------------------------
  -- Source for unique transaction identifiers
  ------------------------------------------------------------------------
  {-# NOINLINE txid #-}
  txid :: MVar Tx
  txid = unsafePerformIO $ newMVar (Tx 1)

  ------------------------------------------------------------------------
  -- Source for unique receipts
  ------------------------------------------------------------------------
  {-# NOINLINE recc #-}
  recc :: MVar Rec
  recc = unsafePerformIO $ newMVar (Rec 1)

  ------------------------------------------------------------------------
  -- Interfaces
  ------------------------------------------------------------------------
  mkUniqueConId :: IO Con
  mkUniqueConId = mkUniqueId conid incCon

  mkUniqueSubId :: IO Sub
  mkUniqueSubId = mkUniqueId subid incSub

  mkUniqueTxId :: IO Tx
  mkUniqueTxId = mkUniqueId txid incTx

  mkUniqueRecc :: IO Rec
  mkUniqueRecc = mkUniqueId recc incRecc

  mkUniqueId :: MVar a -> (a -> a) -> IO a
  mkUniqueId v f = modifyMVar v $ \x -> 
    let x' = f x in return (x', x')

  incCon :: Con -> Con
  incCon (Con n) = Con (incX n)

  incSub :: Sub -> Sub
  incSub (Sub n) = Sub (incX n)
  incSub NoSub   = NoSub

  incTx :: Tx -> Tx
  incTx (Tx n) = Tx (incX n)
  incTx NoTx   = NoTx

  incRecc :: Rec -> Rec
  incRecc (Rec n) = Rec (incX n)
  incRecc (NoRec) = NoRec

  incX :: Int -> Int
  incX i = if i == 99999999 then 1 else i+1