-- | An implementation of @Int@-mapped @ByteString@s with internalization.
-- Wrap a @ByteString@ with 'ibs' to receive a @IBS@. This internalizes the
-- given @ByteString@, meaning that two bytestring inputs @x@ and @y@ will
-- yield the same @IBS@ if they have the same bytestring representation.
--
-- For convenience, conversion from and to text is possible as well and
-- uses @UTF8@ encoding.
--
-- Since internalized @ByteString@ values are never released, be sure to
-- use it sparingly. I.e. to internalize words, not full sentences.
--
-- NOTE Currently, we use a @ByteString@ internally and can not @compact@
-- the structure. The code remains, though as comments in case we switch to
-- another structure.

module Data.ByteString.Interned
  ( module Data.ByteString.Interned
  ) where

import           Control.Applicative
import           Control.DeepSeq (NFData(..))
import           Data.Aeson as A
import           Data.Binary      as DB
import           Data.ByteString (ByteString)
import           Data.Hashable
import           Data.Serialize   as DS
import           Data.Serialize.Text
import           Data.String as IS
import           Data.String.Conversions
import           Data.Text.Binary
import           Data.Text.Encoding (decodeUtf8,encodeUtf8)
import           Data.Text (Text)
import           Data.Vector.Unboxed.Deriving
import           GHC.Generics

import           Data.ByteString.Interned.Internal



-- | An @IBS@ behaves much like a @ByteString@, but is represented as an
-- @Int@ internally. Its phantom type is polykinded, since we might want to
-- use type-level strings to name things.

newtype IBS k = IBS { getIBS :: Int }
  deriving (Eq,Generic)

derivingUnbox "IBS"
  [t| forall k . IBS k  Int |]
  [|  getIBS                 |]
  [|  IBS                    |]

instance Ord (IBS k) where
  IBS l `compare` IBS r = ibsBimapLookupInt l `compare` ibsBimapLookupInt r
  {-# Inline compare #-}

ibs  ByteString  IBS k
ibs s = IBS $! ibsBimapAdd s
{-# Inline ibs #-}

-- | Handy wrapper to internalize a @Text@ and get a 'IBS'.

ibsText  Text  IBS k
ibsText s = IBS $! ibsBimapAdd $ encodeUtf8 s
{-# Inline ibsText #-}

instance IsString (IBS k) where
  fromString = ibsText . IS.fromString
  {-# Inline fromString #-}

instance Show (IBS k) where
  showsPrec p i r = showsPrec p (ibsTo i :: String) r
  {-# Inline showsPrec #-}

instance Read (IBS k) where
  readsPrec p str = [ (ibsText $ IS.fromString s, y) | (s,y) <- readsPrec p str ]
  {-# Inline readsPrec #-}

instance Hashable (IBS k)

-- | Convert into an @IBS@, using a @Text@ intermediate for proper UTF8
-- conversion.

ibsFrom  ConvertibleStrings x Text  x  IBS k
ibsFrom = ibsText . convertString
{-# Inline ibsFrom #-}

ibsTo  ConvertibleStrings Text x  IBS k  x
ibsTo = convertString . ibsToText
{-# Inline ibsTo #-}

ibsToText  IBS k  Text
ibsToText = decodeUtf8 . ibsToUtf8
{-# Inline ibsToText #-}

ibsToUtf8  IBS k  ByteString
ibsToUtf8 = ibsBimapLookupInt . getIBS
{-# Inline ibsToUtf8 #-}

instance NFData (IBS k) where
  rnf = rnf . getIBS
  {-# Inline rnf #-}

instance Binary (IBS k) where
  put = DB.put . ibsToText
  get = ibs <$> DB.get
  {-# Inline put #-}
  {-# Inline get #-}

instance Serialize (IBS k) where
  put = DS.put . ibsToText
  get = ibs <$> DS.get
  {-# Inline put #-}
  {-# Inline get #-}

instance FromJSON (IBS k) where
  parseJSON s = ibsText <$> parseJSON s
  {-# Inline parseJSON #-}

instance ToJSON (IBS k) where
  toJSON = toJSON . ibsToText
  {-# Inline toJSON #-}