packages feed

snowchecked-0.0.2.0: src/Data/Snowchecked/Encoding/Text.hs

{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE TypeApplications     #-}
{-# LANGUAGE RecordWildCards      #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE NamedFieldPuns       #-}
{-# OPTIONS_GHC -Wno-orphans      #-}
{-|
 This module provides a generalized conversion function between a
 'Flake' and all types that are members of both 'FromText' and 'ToText'.
 It is specialized for the strict 'Text' and 'String' types. It is marked as
 incoherent due to the constraint being no smaller than the instance type,
 so it is undecidable.

 To specify how you want the conversion to be performed, you need to wrap the
 text-like type the 'Base16' constructor.  Other encodings (eg: Base64) may
 be added later.

 Note that when converting to a 'Flake', the implementation silently discards
 characters other than digits, 'a'-'f', and 'A'-'F'.  This allows you to
 apply formatting to the Flake.
-}

module Data.Snowchecked.Encoding.Text
  ( module Data.Snowchecked.Encoding.Class
  , module Data.Text.Conversions
  ) where

import qualified Data.List                                 as L
import           Data.Maybe                                (fromMaybe)
import           Data.Snowchecked.Encoding.Integral
import           Data.Snowchecked.Encoding.Class
import           Data.Snowchecked.Internal.Import
import qualified Data.Text                                 as T
import           Data.Text.Conversions
import Data.Snowchecked (snowcheckedConfigBitCount)
import Data.Ratio ((%))
import Data.Char (isHexDigit)

instance {-# INCOHERENT #-} (ToText a, FromText a) => IsFlake (Base16 a) where
  fromFlake flake@Flake{flakeConfig} = Base16 $ convertText str
    where
      hexLength = ceiling $
        snowcheckedConfigBitCount flakeConfig % 4
      pad0 str' = 
        if L.length str' < hexLength then
          pad0 ('0':str')
        else
          str'
      str = pad0 $ showHex (fromFlake @Integer flake) ""
  {-# INLINEABLE fromFlake #-}
  {-# SPECIALIZE fromFlake :: Flake -> Base16 String #-}
  {-# SPECIALIZE fromFlake :: Flake -> Base16 T.Text #-}

  parseFish SnowcheckedConfig{..} (Base16 raw) = 
    calculateN >>= \n ->
      return $ Flakeish
        { fishCheck = fromIntegral $ cutBits n checkBitsInt
        , fishNodeId = fromIntegral $ shiftCutBits n checkBitsInt nodeBitsInt
        , fishCount = fromIntegral $ shiftCutBits n (checkBitsInt + nodeBitsInt) countBitsInt
        , fishTime = fromIntegral $ shiftCutBits n (checkBitsInt + nodeBitsInt + countBitsInt) timeBitsInt
        }
    where
      str = convertText @_ @String raw
      cleaned = 
        L.dropWhile ('0' ==) .
        L.filter isHexDigit $
        fromMaybe str (L.stripPrefix "0x" str)
      calculateN = fst <$> findBestResult (readHex @Integer cleaned)
      findBestResult [] = fail "Could not find any results"
      findBestResult (this@(_,""):_) = return this
      findBestResult [onlyResult] = return onlyResult
      findBestResult (this@(_,nRest):others) =
        findBestResult others >>= \other@(_, mRest) ->
          if L.length nRest < L.length mRest then
            return this
          else
            return other
      checkBitsInt = toInt confCheckBits
      nodeBitsInt = toInt confNodeBits
      timeBitsInt = toInt confTimeBits
      countBitsInt = toInt confCountBits
  {-# INLINEABLE parseFish #-}
  {-# SPECIALIZE parseFish :: (MonadFail m) => SnowcheckedConfig -> Base16 T.Text -> m Flakeish #-}
  {-# SPECIALIZE parseFish :: (MonadFail m) => SnowcheckedConfig -> Base16 String -> m Flakeish #-}