module Blockchain.Util where

import Data.Bits
import qualified Data.ByteString as B
import Data.ByteString.Internal
import Data.Char
import Data.Word
import Numeric

import Blockchain.ExtWord

showHex4 :: Word256 -> String
showHex4 i = replicate (4 - length rawOutput) '0' ++ rawOutput
  where
    rawOutput = showHex i ""

showHexU :: Integer -> String
showHexU = map toUpper . flip showHex ""

--I hate this, it is an ugly way to create an Integer from its component bytes.
--There should be an easier way....
--See http://stackoverflow.com/questions/25854311/efficient-packing-bytes-into-integers
byteString2Integer :: B.ByteString -> Integer
byteString2Integer x = bytes2Integer $ B.unpack x

bytes2Integer :: [Word8] -> Integer
bytes2Integer [] = 0
bytes2Integer (byte:rest) =
  fromIntegral byte `shift` (8 * length rest) + bytes2Integer rest

integer2Bytes :: Integer -> [Word8]
integer2Bytes 0 = []
integer2Bytes x = integer2Bytes (x `shiftR` 8) ++ [fromInteger (x .&. 255)]

--integer2Bytes1 is integer2Bytes, but with the extra condition that the output be of length 1 or more.
integer2Bytes1 :: Integer -> [Word8]
integer2Bytes1 0 = [0]
integer2Bytes1 x = integer2Bytes x

padZeros :: Int -> String -> String
padZeros n s = replicate (n - length s) '0' ++ s

tab :: String -> String
tab [] = []
tab ('\n':rest) = '\n' : ' ' : ' ' : ' ' : ' ' : tab rest
tab (c:rest) = c : tab rest

showWord8 :: Word8 -> Char
showWord8 c
  | c >= 32 && c < 127 = w2c c
showWord8 _ = '?'

showMem :: Int -> [Word8] -> String
showMem _ x
  | length x > 1000 = " mem size greater than 1000 bytes"
showMem _ [] = ""
showMem p (v1:v2:v3:v4:v5:v6:v7:v8:rest) =
  padZeros 4 (showHex p "") ++
  " " ++
  [showWord8 v1] ++
  [showWord8 v2] ++
  [showWord8 v3] ++
  [showWord8 v4] ++
  [showWord8 v5] ++
  [showWord8 v6] ++
  [showWord8 v7] ++
  [showWord8 v8] ++
  " " ++
  padZeros 2 (showHex v1 "") ++
  " " ++
  padZeros 2 (showHex v2 "") ++
  " " ++
  padZeros 2 (showHex v3 "") ++
  " " ++
  padZeros 2 (showHex v4 "") ++
  " " ++
  padZeros 2 (showHex v5 "") ++
  " " ++
  padZeros 2 (showHex v6 "") ++
  " " ++
  padZeros 2 (showHex v7 "") ++
  " " ++ padZeros 2 (showHex v8 "") ++ "\n" ++ showMem (p + 8) rest
showMem p x =
  padZeros 4 (showHex p "") ++
  " " ++
  (showWord8 <$> x) ++ " " ++ unwords (padZeros 2 . flip showHex "" <$> x)

safeTake :: Word256 -> B.ByteString -> B.ByteString
safeTake i _
  | i > 0x7fffffffffffffff = error "error in call to safeTake: string too long"
safeTake i s
  | i > fromIntegral (B.length s) =
    s `B.append` B.replicate (fromIntegral i - B.length s) 0
safeTake i s = B.take (fromIntegral i) s

safeDrop :: Word256 -> B.ByteString -> B.ByteString
safeDrop i s
  | i > fromIntegral (B.length s) = B.empty
safeDrop i _
  | i > 0x7fffffffffffffff = error "error in call to safeDrop: string too long"
safeDrop i s = B.drop (fromIntegral i) s

isContiguous :: (Eq a, Num a) => [a] -> Bool
isContiguous [] = True
isContiguous [_] = True
isContiguous (x:y:rest)
  | y == x + 1 = isContiguous $ y : rest
isContiguous _ = False