{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE Safe              #-}
{-|
  Module        : StringUtils
  Description   : String utilities for NewHope.
  Copyright     : © Jeremy Bornstein 2019
  License       : Apache 2.0
  Maintainer    : jeremy@bornstein.org
  Stability     : experimental
  Portability   : portable

  String utilities for NewHope.

-}

module StringUtils where

import qualified Data.ByteString            as BS
import           Data.ByteString.Builder
import qualified Data.ByteString.Char8      as BSC
import qualified Data.ByteString.Lazy       as BSL
import qualified Data.ByteString.Lazy.Char8 as BSLC
import           Data.Semigroup             ((<>))
import           Numeric

import MiscUtils


-- | Pad a string to a given length
class Paddable a
  where
    pad :: Char -> Int -> a -> a

instance Paddable String
  where
    pad char len str = let
        remainder = mod (len - Prelude.length str) len
        extra = replicate remainder char
        in str ++ extra

instance Paddable BS.ByteString
  where
    pad char len str = let
        remainder = mod (len - BS.length str) len
        extra = replicate remainder char
        in str <> BSC.pack extra


-- | Convert from a readable hex representation to binary representation and vice-versa.
class HexStringable a where
  hexStringToByteString :: a -> BS.ByteString
  byteStringToHexString :: BS.ByteString -> a
  stringToHexString :: String -> a


instance HexStringable BS.ByteString where
  hexStringToByteString s = BSLC.toStrict . toLazyByteString $ builder
    where
      builder = foldr go (byteString BS.empty) $ BSC.unpack <$> chunk 2 s
      go a builder' = newbuilder <> builder'
        where
          newbuilder = word8 . fst . head . readHex . take 2 $ a

  byteStringToHexString = BSL.toStrict . toLazyByteString . byteStringHex
  stringToHexString = byteStringToHexString . BSC.pack


instance HexStringable BSL.ByteString where
  hexStringToByteString s = BSLC.toStrict . toLazyByteString $ builder
    where
      strict = BSL.toStrict s
      builder = foldr go (byteString BS.empty) $ BSC.unpack <$> chunk 2 strict
      go a builder' = newbuilder <> builder'
        where
          newbuilder = word8 . fst . head . readHex . take 2 $ a

  byteStringToHexString = toLazyByteString . byteStringHex
  stringToHexString = byteStringToHexString . BSC.pack


instance HexStringable String where

  hexStringToByteString s = BSL.toStrict . toLazyByteString $ builder
    where
      builder = foldr go (byteString BS.empty) $ chunk 2 s
      go a builder' = newbuilder <> builder'
        where
          newbuilder = word8 . fst . head . readHex . take 2 $ a

  byteStringToHexString = BSLC.unpack . toLazyByteString . byteStringHex
  stringToHexString = BSLC.unpack . toLazyByteString . byteStringHex . BSC.pack


-- | Extract a range of bytes from a ByteString
bsRange :: BS.ByteString -> Int -> Int -> BS.ByteString
bsRange source offset len = (BS.take len . BS.drop offset) source

-- | Compute a modification of input with a replacement overlaying the data at offset.
bsReplace :: BS.ByteString -> Int -> BS.ByteString -> BS.ByteString
bsReplace source offset new
    | BS.length result /= BS.length source = error errorMsg
    | otherwise = result
  where
    (initial, initialRest) = BS.splitAt offset source
    final = BS.drop (BS.length new) initialRest
    result = BS.append (BS.append initial new) final
    sourceLength = BS.length source
    newLength = BS.length new
    errorMsg = "Source length " ++ show sourceLength
               ++ "; replacement length: " ++ show newLength
               ++ " ...looks like source is shorter than required"  -- constants should prevent this