-- | Names for biological things.
--
-- Species names are internalized and represented as an @Int@. This allows
-- using them in structures like an @IntMap@.
--
-- For other names, we newtype-wrap normal text internalization.
--

module Biobase.Types.Names where

import Control.Applicative
import Control.DeepSeq (NFData(..))
import Data.Aeson as A
import Data.Binary      as DB
import Data.Hashable
import Data.Interned
import Data.Interned.Text
import Data.Serialize   as DS
import Data.Serialize.Text
import Data.String as IS
import Data.String.Conversions (ConvertibleStrings(..), cs)
import Data.String.Conversions.Monomorphic (toST, fromST)
import Data.Text.Binary
import Data.Text (Text, pack, unpack)
import Data.Vector.Unboxed.Deriving
import GHC.Generics

import Biobase.Types.Names.Internal



-- * Int-internalized species names.

-- | A species name. Represented with an @Int@, but behaves like a @Text@.

newtype SpeciesName = SpeciesName { getSpeciesNameRep :: Int }
  deriving (Eq,Generic)

derivingUnbox "SpeciesName"
  [t| SpeciesName -> Int |]
  [|  getSpeciesNameRep  |]
  [|  SpeciesName        |]

instance Ord SpeciesName where
  SpeciesName l `compare` SpeciesName r = speciesNameBimapLookupInt l `compare` speciesNameBimapLookupInt r
  {-# Inline compare #-}

-- | Smart constructor that performs the correct internalization.

speciesName :: Text -> SpeciesName
speciesName = SpeciesName . speciesNameBimapAdd
{-# Inline speciesName #-}

instance IsString SpeciesName where
  fromString = speciesName . IS.fromString
  {-# Inline fromString #-}

instance Show SpeciesName where
  showsPrec p i r = showsPrec p (unpack $ toST i) r
  {-# Inline showsPrec #-}

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

instance Hashable SpeciesName

instance ConvertibleStrings Text SpeciesName where
  convertString = speciesName

instance ConvertibleStrings SpeciesName Text where
  convertString = speciesNameBimapLookupInt . getSpeciesNameRep

instance NFData SpeciesName

instance Binary SpeciesName where
  put = DB.put . toST
  get = fromST <$> DB.get
  {-# Inline put #-}
  {-# Inline get #-}

instance Serialize SpeciesName where
  put = DS.put . toST
  get = fromST <$> DS.get
  {-# Inline put #-}
  {-# Inline get #-}

instance FromJSON SpeciesName where
  parseJSON s = fromST <$> parseJSON s
  {-# Inline parseJSON #-}

instance ToJSON SpeciesName where
  toJSON = toJSON . toST
  {-# Inline toJSON #-}



-- * Internalize taxonomic rank names

-- | The taxonomic rank. This encodes the name for a given rank.

newtype TaxonomicRank = TaxonomicRank { getTaxonomicRank :: InternedText }
  deriving (IsString,Eq,Ord,Show,Generic)

instance NFData TaxonomicRank where
  rnf (TaxonomicRank it) = rnf (internedTextId it)
  {-# Inline rnf #-}

instance ConvertibleStrings Text TaxonomicRank where
  convertString = TaxonomicRank . intern

instance ConvertibleStrings TaxonomicRank Text where
  convertString = unintern . getTaxonomicRank

instance Hashable TaxonomicRank where
  hashWithSalt s (TaxonomicRank it) = hashWithSalt s (internedTextId it)
  {-# Inline hashWithSalt #-}

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

instance Binary TaxonomicRank where
  put = DB.put . toST
  get = fromST <$> DB.get
  {-# Inline put #-}
  {-# Inline get #-}

instance Serialize TaxonomicRank where
  put = DS.put . toST
  get = fromST <$> DS.get
  {-# Inline put #-}
  {-# Inline get #-}

instance FromJSON TaxonomicRank where
  parseJSON s = fromST <$> parseJSON s
  {-# Inline parseJSON #-}

instance ToJSON TaxonomicRank where
  toJSON = toJSON . toST
  {-# Inline toJSON #-}