{-|
Module      : Language.Rust.Data.Ident
Description : Identifiers
Copyright   : (c) Alec Theriault, 2017-2018
License     : BSD-style
Maintainer  : alec.theriault@gmail.com
Stability   : experimental
Portability : portable

Data structure behind identifiers.
-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}

module Language.Rust.Data.Ident (Ident(..), mkIdent, Name, IdentName(..) ) where

import GHC.Generics       ( Generic )

import Control.DeepSeq    ( NFData )
import Data.Data          ( Data )
import Data.Typeable      ( Typeable )

import Data.List          ( foldl' )
import Data.Char          ( ord )
import Data.String        ( IsString(..) )
--import Data.Semigroup as Sem

-- | An identifier
data Ident
  = Ident { name :: IdentName            -- ^ payload of the identifier
          , raw :: Bool                  -- ^ whether the identifier is raw
          , hash :: {-# UNPACK #-} !Int  -- ^ hash for quick comparision
          } deriving (Data, Typeable, Generic, NFData)

-- | Shows the identifier as a string (for use with @-XOverloadedStrings@)
instance Show Ident where
  show = show . name

instance IsString Ident where
  fromString = mkIdent

-- | Uses 'hash' to short-circuit
instance Eq Ident where
  i1 == i2 = hash i1 == hash i2 && name i1 == name i2 && raw i1 == raw i2
  i1 /= i2 = hash i1 /= hash i2 || name i1 /= name i2 || raw i1 /= raw i2

-- | Uses 'hash' to short-circuit
instance Ord Ident where
  compare i1 i2 = case compare i1 i2 of
                    EQ -> compare (raw i1, name i1) (raw i2, name i2)
                    rt -> rt

-- | "Forgets" about whether either argument was raw
instance Monoid Ident where
  mappend = (<>)
  mempty = mkIdent ""

-- | "Forgets" about whether either argument was raw
-- <<<<<<< HEAD
instance Semigroup Ident where
  Ident (Name n1) _ _ <> Ident (Name n2) _  _               = mkIdent (n1 <> n2)
  Ident (HaskellName n1) _ _ <> Ident (HaskellName n2) _  _ = mkIdent (n1 <> n2)
  Ident (Name n1) _ _ <> Ident (HaskellName n2) _  _        = mkIdent (n1 <> n2)
  Ident (HaskellName n1) _ _ <> Ident (Name n2) _  _        = mkIdent (n1 <> n2)
-- =======
-- instance Sem.Semigroup Ident where
--   Ident n1 _ _ <> Ident n2 _  _ = mkIdent (n1 <> n2)
-- >>>>>>> upstream/master

-- | Smart constructor for making an 'Ident'.
mkIdent :: String -> Ident
mkIdent ('$':'{':'i':'|':ss) = Ident (HaskellName $ init $ init ss) False (hashString $ init $ init ss)
mkIdent s = Ident (Name s) False (hashString s)

-- | Hash a string into an 'Int'
hashString :: String -> Int
hashString = foldl' f golden
   where f m c = fromIntegral (ord c) * magic + m
         magic = 0xdeadbeef
         golden = 1013904242

-- | The payload of an identifier
type Name = String

data IdentName =
    Name        Name
  | HaskellName String
  deriving (Eq, Ord, Data, Typeable, Generic, NFData)

instance Show IdentName where
  show (Name n) = show n
  show (HaskellName n) = "${i|" ++ n ++ "|}"