module Data.Blockchain.Types.Transaction
    ( Transaction(..)
    , CoinbaseTransaction(..)
    , TransactionIn(..)
    , TransactionOutRef(..)
    , TransactionOut(..)
    , signTransaction
    , verifyTransactionSignature
    ) where

import qualified Data.Aeson             as Aeson
import qualified Data.ByteString.Lazy   as Lazy
import qualified Data.Hashable          as H
import qualified Data.List.NonEmpty     as NonEmpty
import qualified Data.Word              as Word
import qualified GHC.Generics           as Generic


import qualified Data.Blockchain.Crypto as Crypto

data Transaction = Transaction
    { transactionIn  :: NonEmpty.NonEmpty TransactionIn
    , transactionOut :: NonEmpty.NonEmpty TransactionOut
    -- TODO: arbitrary bytes?
    }
  deriving (Generic.Generic, Eq, Show)

instance Aeson.FromJSON Transaction
instance Aeson.ToJSON  Transaction
instance Crypto.ToHash Transaction

newtype CoinbaseTransaction = CoinbaseTransaction
    { coinbaseTransactionOut :: NonEmpty.NonEmpty TransactionOut
    }
  deriving (Generic.Generic, Eq, Show)

instance Aeson.FromJSON CoinbaseTransaction
instance Aeson.ToJSON CoinbaseTransaction
instance Crypto.ToHash CoinbaseTransaction

data TransactionIn = TransactionIn
    { transactionOutRef :: TransactionOutRef
    , signature         :: Crypto.Signature -- ^ Signature from prev transaction, using pubkey from prev transaction
    }
  deriving (Generic.Generic, Eq, Show)

instance Aeson.ToJSON TransactionIn
instance Aeson.FromJSON TransactionIn
instance Crypto.ToHash TransactionIn

-- | Pointer to a specific TransactionOut
data TransactionOutRef = TransactionOutRef
    { transactionHash     :: Either (Crypto.Hash CoinbaseTransaction) (Crypto.Hash Transaction)
    , transactionOutIndex :: Word.Word
    }
  deriving (Generic.Generic, Eq, Show)

instance H.Hashable TransactionOutRef

instance Aeson.FromJSON TransactionOutRef
instance Aeson.ToJSON TransactionOutRef

data TransactionOut = TransactionOut
    { value           :: Word.Word
    , signaturePubKey :: Crypto.PublicKey -- ^ Address of where funds go
    }
  deriving (Generic.Generic, Eq, Show)

instance Aeson.FromJSON TransactionOut
instance Aeson.ToJSON TransactionOut
instance Crypto.ToHash TransactionOut

signTransaction :: Crypto.PrivateKey -> TransactionOut -> IO Crypto.Signature
signTransaction priv = Crypto.sign priv . Lazy.toStrict . Aeson.encode

verifyTransactionSignature :: Crypto.Signature -> TransactionOut -> Bool
verifyTransactionSignature sig txOut = Crypto.verify pub sig $ Lazy.toStrict (Aeson.encode txOut)
  where
    pub = signaturePubKey txOut