module Network.EasyBitcoin.Internal.Script 
 where



import Data.Word
import qualified Data.ByteString as BS
import Data.List (nub) -- not to use nub!! TODO


import Data.Binary (Binary, get, put)
import Data.Binary.Get ( getWord64be
                       , getWord32be
                       , getWord8
                       , getWord16le
                       , getWord32le
                       , getByteString
                       , Get
                       , isEmpty    
                       )
import Data.Binary.Put( putWord64be
                      , putWord32be
                      , putWord32le
                      , putWord16le
                      , putWord8
                      , putByteString
                      )
import Control.Monad                   (unless, guard,replicateM,forM_,liftM2)
import Control.Applicative((<$>))



newtype Script = Script{scriptOps::[ScriptOp]} 
                 deriving (Eq, Show, Ord,Read)


-- | Data type representing all of the operators allowed inside a 'Script'.
data ScriptOp   = OP_PUSHDATA { pushContent :: !BS.ByteString
                              , pushOpCode  :: !PushDataType
                              }
              --   OP__ 0
                
                | OP_1NEGATE
                | OP_RESERVED
                | OP__ Word8 -- from 0 to 16 inclusive
                
                -- ^ Flow control
                | OP_NOP
                | OP_VER -- reserved
                | OP_IF
                | OP_NOTIF
                | OP_VERIF -- resreved
                | OP_VERNOTIF -- reserved
                | OP_ELSE
                | OP_ENDIF
                | OP_VERIFY
                | OP_RETURN
                
                -- ^Stack operations
                | OP_TOALTSTACK
                | OP_FROMALTSTACK
                | OP_IFDUP
                | OP_DEPTH
                | OP_DROP
                | OP_DUP
                | OP_NIP
                | OP_OVER
                | OP_PICK
                | OP_ROLL
                | OP_ROT
                | OP_SWAP
                | OP_TUCK
                | OP_2DROP
                | OP_2DUP
                | OP_3DUP
                | OP_2OVER
                | OP_2ROT
                | OP_2SWAP
                
                -- ^ Splice
                | OP_CAT
                | OP_SUBSTR
                | OP_LEFT
                | OP_RIGHT
                | OP_SIZE
                
                -- ^ Bitwise logic
                | OP_INVERT
                | OP_AND
                | OP_OR
                | OP_XOR
                | OP_EQUAL
                | OP_EQUALVERIFY
                | OP_RESERVED1
                | OP_RESERVED2
                
                -- ^ Arithmetic
                | OP_1ADD
                | OP_1SUB
                | OP_2MUL
                | OP_2DIV
                | OP_NEGATE
                | OP_ABS
                | OP_NOT
                | OP_0NOTEQUAL
                | OP_ADD
                | OP_SUB
                | OP_MUL
                | OP_DIV
                | OP_MOD
                | OP_LSHIFT
                | OP_RSHIFT
                | OP_BOOLAND
                | OP_BOOLOR
                | OP_NUMEQUAL
                | OP_NUMEQUALVERIFY
                | OP_NUMNOTEQUAL
                | OP_LESSTHAN
                | OP_GREATERTHAN
                | OP_LESSTHANOREQUAL
                | OP_GREATERTHANOREQUAL
                | OP_MIN
                | OP_MAX
                | OP_WITHIN
                
                -- ^ Crypto
                | OP_RIPEMD160
                | OP_SHA1
                | OP_SHA256
                | OP_HASH160
                | OP_HASH256
                | OP_CODESEPARATOR
                | OP_CHECKSIG
                | OP_CHECKSIGVERIFY
                | OP_CHECKMULTISIG
                | OP_CHECKMULTISIGVERIFY
                
                -- ^ Expansion
                | OP_NOP1 | OP_NOP2 | OP_NOP3 | OP_NOP4 | OP_NOP5
                | OP_NOP6 | OP_NOP7 | OP_NOP8 | OP_NOP9 | OP_NOP10
                
                -- ^ Other
                | OP_PUBKEYHASH
                | OP_PUBKEY
                | OP_INVALIDOPCODE  !Word8 
                deriving (Show, Read, Ord,Eq)



-- | Data type representing the type of an OP_PUSHDATA opcode.
data PushDataType = OPCODE      -- ^ The next opcode bytes is data to be pushed onto the stack

                                -- | The next byte contains the number of bytes to be pushed onto
                                -- the stack
                  | OPDATA1
                 
                                -- | The next two bytes contains the number of bytes to be pushed onto
                                -- the stack
                  | OPDATA2
                                -- | The next four bytes contains the number of bytes to be pushed onto
                                -- the stack
                  | OPDATA4
                  deriving (Show, Ord,Read, Eq)





----------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------

op:: Int -> ScriptOp
op n = let n_   = min 16 (max 0 n) 
        in OP__ (fromIntegral n) 



opPushData :: BS.ByteString -> ScriptOp
opPushData bs = case BS.length bs of 
                  len | len <= 0x4b       -> OP_PUSHDATA bs OPCODE
                      | len <= 0xff       -> OP_PUSHDATA bs OPDATA1
                      | len <= 0xffff     -> OP_PUSHDATA bs OPDATA2
                      | len <= 0xffffffff -> OP_PUSHDATA bs OPDATA4
                      | otherwise         -> error "opPushData: payload size too big"




opNumber :: ScriptOp -> Maybe Int
opNumber (OP__ n) = Just (fromIntegral n)
opNumber _        = Nothing 

opContent :: ScriptOp -> Maybe BS.ByteString
opContent (OP_PUSHDATA content _) = Just content
opContent _                       = Nothing 


---------------------------------------------------------------------------------------------------

instance Binary Script where
   
   get = Script <$> getScriptOps
    where 
      getScriptOps = do empty <- isEmpty
                        if empty
                          then return []
                          else liftM2 (:) get getScriptOps
   
   put (Script ops) = forM_ ops put




instance Binary ScriptOp where
        get = do op <- getWord8
                 case op of
                  0x00           -> return $ OP__ 0

                  _ | op <= 0x4b -> do payload <- getByteString (fromIntegral op)
                                       return $ OP_PUSHDATA payload OPCODE
                  
                  0x4c           -> do len     <- getWord8
                                       payload <- getByteString (fromIntegral len)
                                       return   $ OP_PUSHDATA payload OPDATA1
                  
                  0x4d           -> do len     <- getWord16le
                                       payload <- getByteString (fromIntegral len)
                                       return   $ OP_PUSHDATA payload OPDATA2
                  
                  0x4e           -> do len     <- getWord32le
                                       payload <- getByteString (fromIntegral len)
                                       return   $ OP_PUSHDATA payload OPDATA4


                  0x4f           -> return $ OP_1NEGATE
                  0x50           -> return $ OP_RESERVED
                  _ | op < 0x61  -> return $ OP__ (op - 0x50)
                -- Flow control
                  0x61           -> return $ OP_NOP
                  0x62           -> return $ OP_VER -- reserved
                  0x63           -> return $ OP_IF
                  0x64           -> return $ OP_NOTIF
                  0x65           -> return $ OP_VERIF -- reserved
                  0x66           -> return $ OP_VERNOTIF -- reserved
                  0x67           -> return $ OP_ELSE
                  0x68           -> return $ OP_ENDIF
                  0x69           -> return $ OP_VERIFY
                  0x6a           -> return $ OP_RETURN
                -- Stack
                  0x6b           -> return $ OP_TOALTSTACK
                  0x6c           -> return $ OP_FROMALTSTACK
                  0x6d           -> return $ OP_2DROP
                  0x6e           -> return $ OP_2DUP
                  0x6f           -> return $ OP_3DUP
                  0x70           -> return $ OP_2OVER
                  0x71           -> return $ OP_2ROT
                  0x72           -> return $ OP_2SWAP
                  0x73           -> return $ OP_IFDUP
                  0x74           -> return $ OP_DEPTH
                  0x75           -> return $ OP_DROP
                  0x76           -> return $ OP_DUP
                  0x77           -> return $ OP_NIP
                  0x78           -> return $ OP_OVER
                  0x79           -> return $ OP_PICK
                  0x7a           -> return $ OP_ROLL
                  0x7b           -> return $ OP_ROT
                  0x7c           -> return $ OP_SWAP
                  0x7d           -> return $ OP_TUCK
                -- Splice
                  0x7e           -> return $ OP_CAT
                  0x7f           -> return $ OP_SUBSTR
                  0x80           -> return $ OP_LEFT
                  0x81           -> return $ OP_RIGHT
                  0x82           -> return $ OP_SIZE
                -- Bitwise logic
                  0x83           -> return $ OP_INVERT
                  0x84           -> return $ OP_AND
                  0x85           -> return $ OP_OR
                  0x86           -> return $ OP_XOR
                  0x87           -> return $ OP_EQUAL
                  0x88           -> return $ OP_EQUALVERIFY
                  0x89           -> return $ OP_RESERVED1
                  0x8a           -> return $ OP_RESERVED2
                -- Arithmetic
                  0x8b           -> return $ OP_1ADD
                  0x8c           -> return $ OP_1SUB
                  0x8d           -> return $ OP_2MUL
                  0x8e           -> return $ OP_2DIV
                  0x8f           -> return $ OP_NEGATE
                  0x90           -> return $ OP_ABS
                  0x91           -> return $ OP_NOT
                  0x92           -> return $ OP_0NOTEQUAL
                  0x93           -> return $ OP_ADD
                  0x94           -> return $ OP_SUB
                  0x95           -> return $ OP_MUL
                  0x96           -> return $ OP_DIV
                  0x97           -> return $ OP_MOD
                  0x98           -> return $ OP_LSHIFT
                  0x99           -> return $ OP_RSHIFT
                  0x9a           -> return $ OP_BOOLAND
                  0x9b           -> return $ OP_BOOLOR
                  0x9c           -> return $ OP_NUMEQUAL
                  0x9d           -> return $ OP_NUMEQUALVERIFY
                  0x9e           -> return $ OP_NUMNOTEQUAL
                  0x9f           -> return $ OP_LESSTHAN
                  0xa0           -> return $ OP_GREATERTHAN
                  0xa1           -> return $ OP_LESSTHANOREQUAL
                  0xa2           -> return $ OP_GREATERTHANOREQUAL
                  0xa3           -> return $ OP_MIN
                  0xa4           -> return $ OP_MAX
                  0xa5           -> return $ OP_WITHIN
                -- Crypto
                  0xa6           -> return $ OP_RIPEMD160
                  0xa7           -> return $ OP_SHA1
                  0xa8           -> return $ OP_SHA256
                  0xa9           -> return $ OP_HASH160
                  0xaa           -> return $ OP_HASH256
                  0xab           -> return $ OP_CODESEPARATOR
                  0xac           -> return $ OP_CHECKSIG
                  0xad           -> return $ OP_CHECKSIGVERIFY
                  0xae           -> return $ OP_CHECKMULTISIG
                  0xaf           -> return $ OP_CHECKMULTISIGVERIFY
                -- More NOPs
                  0xb0           -> return $ OP_NOP1
                  0xb1           -> return $ OP_NOP2
                  0xb2           -> return $ OP_NOP3
                  0xb3           -> return $ OP_NOP4
                  0xb4           -> return $ OP_NOP5
                  0xb5           -> return $ OP_NOP6
                  0xb6           -> return $ OP_NOP7
                  0xb7           -> return $ OP_NOP8
                  0xb8           -> return $ OP_NOP9
                  0xb9           -> return $ OP_NOP10
                -- Constants
                  0xfd           -> return $ OP_PUBKEYHASH
                  0xfe           -> return $ OP_PUBKEY
                  _              -> return $ OP_INVALIDOPCODE op

        put op = case op of
                (OP_PUSHDATA payload optype)-> do let len = BS.length payload
                                                  case optype of
                                                    OPCODE -> do unless (len <= 0x4b) $ fail        "OP_PUSHDATA OPCODE: Payload size too big"
                                                                 putWord8 $ fromIntegral len
                
                                                    OPDATA1 -> do unless (len <= 0xff) $ fail       "OP_PUSHDATA OPDATA1: Payload size too big"
                                                                  putWord8 0x4c
                                                                  putWord8 $ fromIntegral len
                                                    
                                                    OPDATA2 -> do unless (len <= 0xffff) $ fail     "OP_PUSHDATA OPDATA2: Payload size too big"
                                                                  putWord8 0x4d
                                                                  putWord16le $ fromIntegral len
                                                    
                                                    OPDATA4 -> do unless (len <= 0x7fffffff) $ fail "OP_PUSHDATA OPDATA4: Payload size too big"
                                                                  putWord8 0x4e
                                                                  putWord32le $ fromIntegral len
                                                  putByteString payload
                -- Constants
                OP__ 0      -> putWord8 0x00
                OP_1NEGATE  -> putWord8 0x4f
                OP_RESERVED -> putWord8 0x50
                OP__ n -> putWord8 (0x50+n) -- n should be between 1 and 16 inclusive
                -- Crypto Constants
                OP_PUBKEY -> putWord8 0xfe
                OP_PUBKEYHASH -> putWord8 0xfd
                -- Invalid Opcodes
                (OP_INVALIDOPCODE x) -> putWord8 x
                -- Flow Control
                OP_NOP -> putWord8 0x61
                OP_VER -> putWord8 0x62
                OP_IF -> putWord8 0x63
                OP_NOTIF -> putWord8 0x64
                OP_VERIF -> putWord8 0x65
                OP_VERNOTIF -> putWord8 0x66
                OP_ELSE -> putWord8 0x67
                OP_ENDIF -> putWord8 0x68
                OP_VERIFY -> putWord8 0x69
                OP_RETURN -> putWord8 0x6a
                -- Stack Operations
                OP_TOALTSTACK -> putWord8 0x6b
                OP_FROMALTSTACK -> putWord8 0x6c
                OP_2DROP -> putWord8 0x6d
                OP_2DUP -> putWord8 0x6e
                OP_3DUP -> putWord8 0x6f
                OP_2OVER -> putWord8 0x70
                OP_2ROT -> putWord8 0x71
                OP_2SWAP -> putWord8 0x72
                OP_IFDUP -> putWord8 0x73
                OP_DEPTH -> putWord8 0x74
                OP_DROP -> putWord8 0x75
                OP_DUP -> putWord8 0x76
                OP_NIP -> putWord8 0x77
                OP_OVER -> putWord8 0x78
                OP_PICK -> putWord8 0x79
                OP_ROLL -> putWord8 0x7a
                OP_ROT -> putWord8 0x7b
                OP_SWAP -> putWord8 0x7c
                OP_TUCK -> putWord8 0x7d
                -- Splice
                OP_CAT -> putWord8 0x7e
                OP_SUBSTR -> putWord8 0x7f
                OP_LEFT -> putWord8 0x80
                OP_RIGHT -> putWord8 0x81
                OP_SIZE -> putWord8 0x82
                -- Bitwise Logic
                OP_INVERT -> putWord8 0x83
                OP_AND -> putWord8 0x84
                OP_OR -> putWord8 0x85
                OP_XOR -> putWord8 0x86
                OP_EQUAL -> putWord8 0x87
                OP_EQUALVERIFY -> putWord8 0x88
                OP_RESERVED1 -> putWord8 0x89
                OP_RESERVED2 -> putWord8 0x8a
                -- Arithmetic
                OP_1ADD -> putWord8 0x8b
                OP_1SUB -> putWord8 0x8c
                OP_2MUL -> putWord8 0x8d
                OP_2DIV -> putWord8 0x8e
                OP_NEGATE -> putWord8 0x8f
                OP_ABS -> putWord8 0x90
                OP_NOT -> putWord8 0x91
                OP_0NOTEQUAL -> putWord8 0x92
                OP_ADD -> putWord8 0x93
                OP_SUB -> putWord8 0x94
                OP_MUL -> putWord8 0x95
                OP_DIV -> putWord8 0x96
                OP_MOD -> putWord8 0x97
                OP_LSHIFT -> putWord8 0x98
                OP_RSHIFT -> putWord8 0x99
                OP_BOOLAND -> putWord8 0x9a
                OP_BOOLOR -> putWord8 0x9b
                OP_NUMEQUAL -> putWord8 0x9c
                OP_NUMEQUALVERIFY -> putWord8 0x9d
                OP_NUMNOTEQUAL -> putWord8 0x9e
                OP_LESSTHAN -> putWord8 0x9f
                OP_GREATERTHAN -> putWord8 0xa0
                OP_LESSTHANOREQUAL -> putWord8 0xa1
                OP_GREATERTHANOREQUAL-> putWord8 0xa2
                OP_MIN -> putWord8 0xa3
                OP_MAX -> putWord8 0xa4
                OP_WITHIN -> putWord8 0xa5
                -- Crypto
                OP_RIPEMD160 -> putWord8 0xa6
                OP_SHA1 -> putWord8 0xa7
                OP_SHA256 -> putWord8 0xa8
                OP_HASH160 -> putWord8 0xa9
                OP_HASH256 -> putWord8 0xaa
                OP_CODESEPARATOR -> putWord8 0xab
                OP_CHECKSIG -> putWord8 0xac
                OP_CHECKSIGVERIFY -> putWord8 0xad
                OP_CHECKMULTISIG -> putWord8 0xae
                OP_CHECKMULTISIGVERIFY -> putWord8 0xaf
                -- More NOPs
                OP_NOP1 -> putWord8 0xb0
                OP_NOP2 -> putWord8 0xb1
                OP_NOP3 -> putWord8 0xb2
                OP_NOP4 -> putWord8 0xb3
                OP_NOP5 -> putWord8 0xb4
                OP_NOP6 -> putWord8 0xb5
                OP_NOP7 -> putWord8 0xb6
                OP_NOP8 -> putWord8 0xb7
                OP_NOP9 -> putWord8 0xb8
                OP_NOP10 -> putWord8 0xb9