{-# OPTIONS_GHC -fno-cse -fno-full-laziness #-}
{-|
  Network specific constants
-}
module Network.Haskoin.Constants
( -- ** Data
  Network(..)
, prodnet
, testnet3
  -- ** Functions
, switchToTestnet3
, setNetwork
, getNetwork
  -- ** Network parameters
, networkName
, addrPrefix
, scriptPrefix
, secretPrefix
, extPubKeyPrefix
, extSecretPrefix
, networkMagic
, genesisHeader
, maxBlockSize
, maxSatoshi
, haskoinUserAgent
, defaultPort
, allowMinDifficultyBlocks
, powLimit
, targetTimespan
, targetSpacing
, checkpoints
) where

import Data.Bits (shiftR)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as C8 (concat, pack)
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import Data.Version (showVersion)
import Data.Word (Word8, Word32, Word64)
import Data.LargeWord (Word256)
import Network.Haskoin.Block.Types
import System.IO.Unsafe (unsafePerformIO)
import Paths_haskoin_core (version)

data Network = Network
    { getNetworkName                :: !String
    , getAddrPrefix                 :: !Word8
    , getScriptPrefix               :: !Word8
    , getSecretPrefix               :: !Word8
    , getExtPubKeyPrefix            :: !Word32
    , getExtSecretPrefix            :: !Word32
    , getNetworkMagic               :: !Word32
    , getGenesisHeader              :: !BlockHeader
    , getMaxBlockSize               :: !Int
    , getMaxSatoshi                 :: !Word64
    , getHaskoinUserAgent           :: !ByteString
    , getDefaultPort                :: !Int
    , getAllowMinDifficultyBlocks   :: !Bool
    , getPowLimit                   :: !Integer
    , getTargetTimespan             :: !Word32
    , getTargetSpacing              :: !Word32
    , getCheckpoints                :: ![(Int, BlockHash)]
    } deriving (Eq, Show, Read)

-- | Switch to Testnet3.  Do at start of program.
switchToTestnet3 :: IO ()
switchToTestnet3 = setNetwork testnet3

-- | Change network constants manually.  If switching to Testnet3, use
-- switchToTestnet3 instead.
setNetwork :: Network -> IO ()
setNetwork = writeIORef networkRef

{-# NOINLINE networkRef #-}
-- | Use this if you want to change constants to something other than Testnet3.
networkRef :: IORef Network
networkRef = unsafePerformIO $ newIORef prodnet

{-# NOINLINE getNetwork #-}
-- | Read current network constants record
getNetwork :: Network
getNetwork = unsafePerformIO $ readIORef networkRef

-- | Name of the bitcoin network
networkName :: String
networkName = getNetworkName getNetwork

-- | Prefix for base58 PubKey hash address
addrPrefix :: Word8
addrPrefix = getAddrPrefix getNetwork

-- | Prefix for base58 script hash address
scriptPrefix :: Word8
scriptPrefix = getScriptPrefix getNetwork

-- | Prefix for private key WIF format
secretPrefix :: Word8
secretPrefix = getSecretPrefix getNetwork

-- | Prefix for extended public keys (BIP32)
extPubKeyPrefix :: Word32
extPubKeyPrefix = getExtPubKeyPrefix getNetwork

-- | Prefix for extended private keys (BIP32)
extSecretPrefix :: Word32
extSecretPrefix = getExtSecretPrefix getNetwork

-- | Network magic bytes
networkMagic :: Word32
networkMagic = getNetworkMagic getNetwork

-- | Genesis block header information
genesisHeader :: BlockHeader
genesisHeader = getGenesisHeader getNetwork

-- | Maximum size of a block in bytes
maxBlockSize :: Int
maxBlockSize = getMaxBlockSize getNetwork

-- | Maximum number of satoshi
maxSatoshi :: Word64
maxSatoshi = getMaxSatoshi getNetwork

-- | User agent string
haskoinUserAgent :: ByteString
haskoinUserAgent = getHaskoinUserAgent getNetwork

-- | Default port
defaultPort :: Int
defaultPort = getDefaultPort getNetwork

-- | Allow relaxed difficulty transition rules
allowMinDifficultyBlocks :: Bool
allowMinDifficultyBlocks = getAllowMinDifficultyBlocks getNetwork

-- | Lower bound for the proof of work difficulty
powLimit :: Integer
powLimit = getPowLimit getNetwork

-- | Time between difficulty cycles (2 weeks on average)
targetTimespan :: Word32
targetTimespan = getTargetTimespan getNetwork

-- | Time between blocks (10 minutes per block)
targetSpacing :: Word32
targetSpacing = getTargetSpacing getNetwork

-- | Checkpoints to enfore
checkpoints :: [(Int, BlockHash)]
checkpoints = getCheckpoints getNetwork

prodnet :: Network
prodnet = Network
    { getNetworkName = "prodnet"
    , getAddrPrefix = 0
    , getScriptPrefix = 5
    , getSecretPrefix = 128
    , getExtPubKeyPrefix = 0x0488b21e
    , getExtSecretPrefix = 0x0488ade4
    , getNetworkMagic = 0xf9beb4d9
    , getGenesisHeader = BlockHeader
        -- Hash 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
        { blockVersion   = 0x01
        , prevBlock      =
          "0000000000000000000000000000000000000000000000000000000000000000"
        , merkleRoot     =
          "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"
        , blockTimestamp = 1231006505
        , blockBits      = 486604799
        , bhNonce        = 2083236893
        }
    , getMaxBlockSize = 1000000
    , getMaxSatoshi = 2100000000000000
    , getHaskoinUserAgent = C8.concat
        [ "/haskoin:"
        , C8.pack $ showVersion version
        , "/"
        ]
    , getDefaultPort = 8333
    , getAllowMinDifficultyBlocks = False
    , getPowLimit = fromIntegral (maxBound `shiftR` 32 :: Word256)
    , getTargetTimespan = 14 * 24 * 60 * 60
    , getTargetSpacing = 10 * 60
    , getCheckpoints =
        [ ( 11111
          , "0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d"
          )
        , ( 33333
          , "000000002dd5588a74784eaa7ab0507a18ad16a236e7b1ce69f00d7ddfb5d0a6"
          )
        , ( 74000
          , "0000000000573993a3c9e41ce34471c079dcf5f52a0e824a81e7f953b8661a20"
          )
        , ( 105000
          , "00000000000291ce28027faea320c8d2b054b2e0fe44a773f3eefb151d6bdc97"
          )
        , ( 134444
          , "00000000000005b12ffd4cd315cd34ffd4a594f430ac814c91184a0d42d2b0fe"
          )
        , ( 168000
          , "000000000000099e61ea72015e79632f216fe6cb33d7899acb35b75c8303b763"
          )
        , ( 193000
          , "000000000000059f452a5f7340de6682a977387c17010ff6e6c3bd83ca8b1317"
          )
        , ( 210000
          , "000000000000048b95347e83192f69cf0366076336c639f9b7228e9ba171342e"
          )
        , ( 216116
          , "00000000000001b4f4b433e81ee46494af945cf96014816a4e2370f11b23df4e"
          )
        , ( 225430
          , "00000000000001c108384350f74090433e7fcf79a606b8e797f065b130575932"
          )
        , ( 250000
          , "000000000000003887df1f29024b06fc2200b55f8af8f35453d7be294df2d214"
          )
        , ( 279000
          , "0000000000000001ae8c72a0b0c301f67e3afca10e819efa9041e458e9bd7e40"
          )
        ]
    }

testnet3 :: Network
testnet3 = Network
    { getNetworkName = "testnet"
    , getAddrPrefix = 111
    , getScriptPrefix = 196
    , getSecretPrefix = 239
    , getExtPubKeyPrefix = 0x043587cf
    , getExtSecretPrefix = 0x04358394
    , getNetworkMagic = 0x0b110907
    , getGenesisHeader = BlockHeader
        -- Hash 000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943
        { blockVersion   = 0x01
        , prevBlock      =
            "0000000000000000000000000000000000000000000000000000000000000000"
        , merkleRoot     =
            "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"
        , blockTimestamp = 1296688602
        , blockBits      = 486604799
        , bhNonce        = 414098458
        }
    , getMaxBlockSize = 1000000
    , getMaxSatoshi = 2100000000000000
    , getHaskoinUserAgent = C8.concat
        [ "/haskoin-testnet:"
        , C8.pack $ showVersion version
        , "/"
        ]
    , getDefaultPort = 18333
    , getAllowMinDifficultyBlocks = True
    , getPowLimit = fromIntegral (maxBound `shiftR` 32 :: Word256)
    , getTargetTimespan = 14 * 24 * 60 * 60
    , getTargetSpacing = 10 * 60
    , getCheckpoints =
        [ ( 546
          , "000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70"
          )
        ]
    }