module Data.Bitcoin.PaymentChannel.Internal.Payment where

import Data.Bitcoin.PaymentChannel.Internal.Types
import Data.Bitcoin.PaymentChannel.Internal.State
import Data.Bitcoin.PaymentChannel.Internal.Script
import Data.Bitcoin.PaymentChannel.Internal.Util
import Data.Bitcoin.PaymentChannel.Internal.Error

import qualified  Network.Haskoin.Transaction as HT
import qualified  Network.Haskoin.Internals as HI
import qualified  Network.Haskoin.Crypto as HC
import qualified  Network.Haskoin.Script as HS
import Data.Word (Word32, Word64, Word8)
import Data.Maybe (fromJust, isJust)
import qualified Data.ByteString as B

---Payment Tx builer---
buildEmptyPaymentTx :: FundingTxInfo -> HT.Tx
buildEmptyPaymentTx (CFundingTxInfo hash idx _) =
    HT.Tx 1 --version 1
    -- Redeems payment channel output from blockchain Tx
    [HT.TxIn
        (HT.OutPoint hash idx)
        B.empty
        maxBound]
    [] --no outputs
    0 --lockTime 0

paymentTxAddOutput :: HT.TxOut -> HT.Tx -> (HT.Tx, HS.SigHash)
paymentTxAddOutput addOut tx@(HT.Tx _ _ outs _)
    | HT.outValue addOut >= fromIntegral dUST_LIMIT =
        (tx { HT.txOut = outs ++ [addOut] }, HS.SigSingle True)
    | otherwise =
        (tx, HS.SigNone True)
---Payment Tx builer---


getPaymentTxForSigning ::
    PaymentChannelState
    -> BitcoinAmount      -- ^New sender value (newValueLeft)
    -> (HT.Tx, HS.SigHash)
getPaymentTxForSigning st@(CPaymentChannelState _ fti
    (CPaymentTxConfig sendAddr) chanValLeft _) newValueLeft =
        paymentTxAddOutput senderOut $ buildEmptyPaymentTx fti
            where senderOut = HT.TxOut (fromIntegral . toInteger $ newValueLeft) (addressToScriptPubKeyBS sendAddr)

getPaymentTxHashForSigning ::
    PaymentChannelState
    -> BitcoinAmount -- ^New sender value (newValueLeft)
    -> (HC.Hash256, HS.SigHash)  -- ^Hash of payment transaction with a single output paying to senderPK
getPaymentTxHashForSigning st@(CPaymentChannelState cp _ _ _ _) newValueLeft =
        (HS.txSigHash tx (getRedeemScript cp) 0 sigHash, sigHash)
            where (tx,sigHash) = getPaymentTxForSigning st newValueLeft

---Payment build/verify---
verifyPaymentSig ::
    PaymentChannelState
    -> Payment
    -> (HC.Hash256 -> SendPubKey -> HC.Signature -> Bool)
    -> Bool
verifyPaymentSig pcs
    (CPayment newSenderVal (CPaymentSignature sig sigHash)) verifyFunc =
        let (hash,_) = case sigHash of --SigHash in signature overrides newSenderVal
                HS.SigSingle True   -> getPaymentTxHashForSigning pcs newSenderVal
                --sender has relinquished remaining channel value
                HS.SigNone   True   -> getPaymentTxHashForSigning pcs 0
                unknownSigHash      -> (dummyHash256,sigHash) --will fail verification
        in
            verifyFunc hash (pcsClientPubKey pcs) sig

createPayment ::
    PaymentChannelState
    -> BitcoinAmount -- ^ newSenderVal
    -> (HC.Hash256 -> HC.Signature) -- ^ signing function
    -> Payment
createPayment pcs@(CPaymentChannelState _ _ _ currSenderVal _)
    newSenderVal signFunc =
    let
        (hash,sigHash) = getPaymentTxHashForSigning pcs newSenderVal
        sig = signFunc hash
        pSig = CPaymentSignature sig sigHash
    in
        case sigHash of
            HS.SigSingle    _ -> CPayment newSenderVal pSig
            --sender has reliquished the remaining channel value,
            -- due to it being below the "dust" limit.
            HS.SigNone      _ -> CPayment 0 pSig
            _                 -> error
                "BUG: unsupported SigHash created by 'getPaymentTxHashForSigning'"
---Payment build/verify---