{-# LANGUAGE TemplateHaskell, GeneralizedNewtypeDeriving, DeriveDataTypeable, DeriveGeneric, RecordWildCards #-}

-- | The private types
module IdeSession.Types.Private (
    -- * Types without a public counterpart
    FilePathPtr(..)
  , IdPropPtr(..)
  , UseSites
    -- * Types with a public counterpart
  , Public.IdNameSpace(..)
  , IdInfo(..)
  , IdProp(..)
  , IdScope(..)
  , SourceSpan(..)
  , EitherSpan(..)
  , SourceError(..)
  , Public.SourceErrorKind(..)
  , Public.ModuleName
  , ModuleId(..)
  , PackageId(..)
  , IdList
  , IdMap(..)
  , ExpMap(..)
  , SpanInfo(..)
  , ImportEntities(..)
  , Import(..)
  , RunResult(..)
  , BreakInfo(..)
    -- * Cache
  , ExplicitSharingCache(..)
  , unionCache
    -- * Util
  , mkIdMap
  , mkExpMap
  , dominators
  ) where

import Prelude hiding (span, mod)
import Data.Text (Text)
import Data.ByteString (ByteString)
import Control.Applicative ((<$>), (<*>))
import Control.Arrow (first)
import Data.Binary (Binary(..), getWord8, putWord8)
import Data.Typeable (Typeable)
import GHC.Generics (Generic)

import qualified IdeSession.Types.Public as Public
import IdeSession.Strict.Container
import IdeSession.Strict.IntervalMap (StrictIntervalMap, Interval(..))
import qualified IdeSession.Strict.IntervalMap as IntervalMap
import qualified IdeSession.Strict.IntMap      as IntMap
import IdeSession.Util.PrettyVal

newtype FilePathPtr = FilePathPtr { filePathPtr :: Int }
  deriving (Eq, Ord, Show, Generic)

newtype IdPropPtr = IdPropPtr { idPropPtr :: Int }
  deriving (Eq, Ord, Show, Generic)

data IdInfo = IdInfo {
    idProp  :: {-# UNPACK #-} !IdPropPtr
  , idScope :: !IdScope
  }
  deriving (Show, Typeable, Generic)

data IdProp = IdProp {
    idName       :: !Text
  , idSpace      :: !Public.IdNameSpace
  , idType       :: !(Strict Maybe Public.Type)
  , idDefinedIn  :: {-# UNPACK #-} !ModuleId
  , idDefSpan    :: !EitherSpan
  , idHomeModule :: !(Strict Maybe ModuleId)
  }
  deriving (Show, Generic)

data IdScope =
    -- | This is a binding occurrence (@f x = ..@, @\x -> ..@, etc.)
    Binder
    -- | Defined within this module
  | Local
    -- | Imported from a different module
  | Imported {
        idImportedFrom :: {-# UNPACK #-} !ModuleId
      , idImportSpan   :: !EitherSpan
        -- | Qualifier used for the import
        --
        -- > IMPORTED AS                       idImportQual
        -- > import Data.List                  ""
        -- > import qualified Data.List        "Data.List."
        -- > import qualified Data.List as L   "L."
      , idImportQual   :: !Text
      }
    -- | Wired into the compiler (@()@, @True@, etc.)
  | WiredIn
  deriving (Show, Generic)

data SourceSpan = SourceSpan
  { spanFilePath   :: {-# UNPACK #-} !FilePathPtr
  , spanFromLine   :: {-# UNPACK #-} !Int
  , spanFromColumn :: {-# UNPACK #-} !Int
  , spanToLine     :: {-# UNPACK #-} !Int
  , spanToColumn   :: {-# UNPACK #-} !Int
  }
  deriving (Eq, Ord, Show, Generic)

data EitherSpan =
    ProperSpan {-# UNPACK #-} !SourceSpan
  | TextSpan !Text
  deriving (Show, Generic)

data SourceError = SourceError
  { errorKind :: !Public.SourceErrorKind
  , errorSpan :: !EitherSpan
  , errorMsg  :: !Text
  }
  deriving (Show, Generic)

data ModuleId = ModuleId
  { moduleName    :: !Public.ModuleName
  , modulePackage :: {-# UNPACK #-} !PackageId
  }
  deriving (Show, Eq, Generic)

data PackageId = PackageId
  { packageName    :: !Text
  , packageVersion :: !(Strict Maybe Text)
  , packageKey     :: !Text
  }
  deriving (Show, Eq, Ord, Generic)

-- | Used before we convert it to an IdMap
type IdList = [(SourceSpan, SpanInfo)]

data SpanInfo =
   SpanId IdInfo
 | SpanQQ IdInfo
   -- We use 'SpanInSplice' for prioritization only (see 'internalGetSpanInfo').
   -- It gets translated to 'Public.SpanId'
 | SpanInSplice IdInfo
 deriving (Show, Generic)

newtype IdMap = IdMap { idMapToMap :: StrictIntervalMap (FilePathPtr, Int, Int) SpanInfo }
  deriving (Show, Generic)

newtype ExpMap = ExpMap { expMapToMap :: StrictIntervalMap (FilePathPtr, Int, Int) Text }
  deriving (Show, Generic)

type UseSites = Strict (Map IdPropPtr) [SourceSpan]

data ImportEntities =
    ImportOnly   !(Strict [] Text)
  | ImportHiding !(Strict [] Text)
  | ImportAll
  deriving (Show, Eq, Generic)

data Import = Import {
    importModule    :: !ModuleId
  -- | Used only for ghc's PackageImports extension
  , importPackage   :: !(Strict Maybe Text)
  , importQualified :: !Bool
  , importImplicit  :: !Bool
  , importAs        :: !(Strict Maybe Public.ModuleName)
  , importEntities  :: !ImportEntities
  }
  deriving (Show, Eq, Generic)

-- | The outcome of running code
data RunResult =
    -- | The code terminated okay
    RunOk
    -- | The code threw an exception
  | RunProgException String
    -- | GHC itself threw an exception when we tried to run the code
  | RunGhcException String
    -- | Execution was paused because of a breakpoint
  | RunBreak BreakInfo
  deriving (Typeable, Show, Generic)

-- | Information about a triggered breakpoint
data BreakInfo = BreakInfo {
    breakInfoModule      :: Public.ModuleName
  , breakInfoSpan        :: SourceSpan
  , breakInfoResultType  :: Public.Type
  , breakInfoVariableEnv :: Public.VariableEnv
  }
  deriving (Typeable, Show, Generic)

{------------------------------------------------------------------------------
  Cache
------------------------------------------------------------------------------}

-- TODO: Since the ExplicitSharingCache contains internal types, resolving
-- references to the cache means we lose implicit sharing because we need
-- to translate on every lookup. To avoid this, we'd have to introduce two
-- versions of the cache and translate the entire cache first.
data ExplicitSharingCache = ExplicitSharingCache {
    filePathCache :: !(Strict IntMap ByteString)
  , idPropCache   :: !(Strict IntMap IdProp)
  }
  deriving (Show, Generic)

unionCache :: ExplicitSharingCache -> ExplicitSharingCache -> ExplicitSharingCache
unionCache a b = ExplicitSharingCache {
    filePathCache = IntMap.union (filePathCache a) (filePathCache b)
  , idPropCache   = IntMap.union (idPropCache   a) (idPropCache   b)
  }

{------------------------------------------------------------------------------
  Binary instances
------------------------------------------------------------------------------}

instance Binary FilePathPtr where
  put = put . filePathPtr
  get = FilePathPtr <$> get

instance Binary SourceSpan where
  put SourceSpan{..} = do
    put spanFilePath
    put spanFromLine
    put spanFromColumn
    put spanToLine
    put spanToColumn
  get = SourceSpan <$> get <*> get <*> get <*> get <*> get

instance Binary EitherSpan where
  put (ProperSpan span) = putWord8 0 >> put span
  put (TextSpan text)   = putWord8 1 >> put text

  get = do
    header <- getWord8
    case header of
      0 -> ProperSpan <$> get
      1 -> TextSpan <$> get
      _ -> fail "EitherSpan.get: invalid header"

instance Binary SourceError where
  put SourceError{..} = do
    put errorKind
    put errorSpan
    put errorMsg

  get = SourceError <$> get <*> get <*> get

instance Binary IdInfo where
  put IdInfo{..} = put idProp >> put idScope
  get = IdInfo <$> get <*> get

instance Binary IdScope where
  put Binder       = putWord8 0
  put Local        = do putWord8 1
  put Imported{..} = do putWord8 2
                        put idImportedFrom
                        put idImportSpan
                        put idImportQual
  put WiredIn      = putWord8 3

  get = do
    header <- getWord8
    case header of
      0 -> return Binder
      1 -> return Local
      2 -> Imported <$> get <*> get <*> get
      3 -> return WiredIn
      _ -> fail "IdScope.get: invalid header"

instance Binary IdPropPtr where
  put = put . idPropPtr
  get = IdPropPtr <$> get

instance Binary ModuleId where
  put ModuleId{..} = put moduleName >> put modulePackage
  get = ModuleId <$> get <*> get

instance Binary PackageId where
  put PackageId{..} = do
    put packageName
    put packageVersion
    put packageKey
  get = PackageId <$> get <*> get <*> get

instance Binary IdProp where
  put IdProp{..} = do
    put idName
    put idSpace
    put idType
    put idDefinedIn
    put idDefSpan
    put idHomeModule

  get = IdProp <$> get <*> get <*> get <*> get <*> get <*> get

{-
instance Binary IdMap where
  put = put . idMapToMap
  get = IdMap <$> get
-}

instance Binary ImportEntities where
  put (ImportOnly names)   = putWord8 0 >> put names
  put (ImportHiding names) = putWord8 1 >> put names
  put ImportAll            = putWord8 2

  get = do
    header <- getWord8
    case header of
      0 -> ImportOnly   <$> get
      1 -> ImportHiding <$> get
      2 -> return ImportAll
      _ -> fail "ImportEntities.get: invalid header"

instance Binary Import where
  put Import{..} = do
    put importModule
    put importPackage
    put importQualified
    put importImplicit
    put importAs
    put importEntities

  get = Import <$> get <*> get <*> get <*> get <*> get <*> get

instance Binary ExplicitSharingCache where
  put ExplicitSharingCache{..} = do
    put filePathCache
    put idPropCache

  get = ExplicitSharingCache <$> get <*> get

instance Binary SpanInfo where
  put (SpanId idInfo)       = putWord8 0 >> put idInfo
  put (SpanQQ idInfo)       = putWord8 1 >> put idInfo
  put (SpanInSplice idInfo) = putWord8 2 >> put idInfo

  get = do
    header <- getWord8
    case header of
      0 -> SpanId       <$> get
      1 -> SpanQQ       <$> get
      2 -> SpanInSplice <$> get
      _ -> fail "SpanInfo.get: invalid header"

instance Binary RunResult where
  put RunOk                  = putWord8 0
  put (RunProgException str) = putWord8 1 >> put str
  put (RunGhcException str)  = putWord8 2 >> put str
  put (RunBreak info)        = putWord8 3 >> put info

  get = do
    header <- getWord8
    case header of
      0 -> return RunOk
      1 -> RunProgException <$> get
      2 -> RunGhcException <$> get
      3 -> RunBreak <$> get
      _ -> fail "RunResult.get: invalid header"

instance Binary BreakInfo where
  put (BreakInfo{..}) = do
    put breakInfoModule
    put breakInfoSpan
    put breakInfoResultType
    put breakInfoVariableEnv

  get = BreakInfo <$> get <*> get <*> get <*> get

{------------------------------------------------------------------------------
  PrettyVal instances (these rely on Generics)
------------------------------------------------------------------------------}

instance PrettyVal FilePathPtr
instance PrettyVal IdPropPtr
instance PrettyVal IdInfo
instance PrettyVal IdProp
instance PrettyVal IdScope
instance PrettyVal SourceSpan
instance PrettyVal EitherSpan
instance PrettyVal SourceError
instance PrettyVal ModuleId
instance PrettyVal PackageId
instance PrettyVal SpanInfo
instance PrettyVal IdMap
instance PrettyVal ExpMap
instance PrettyVal ImportEntities
instance PrettyVal Import
instance PrettyVal RunResult
instance PrettyVal BreakInfo
instance PrettyVal ExplicitSharingCache

{------------------------------------------------------------------------------
  Util
------------------------------------------------------------------------------}

mkIdMap :: IdList -> IdMap
mkIdMap = IdMap . IntervalMap.fromList . map (first spanToInterval)

mkExpMap :: [(SourceSpan, Text)] -> ExpMap
mkExpMap = ExpMap . IntervalMap.fromList . map (first spanToInterval)

dominators :: SourceSpan -> StrictIntervalMap (FilePathPtr, Int, Int) a -> [(SourceSpan, a)]
dominators span ivalmap =
    map (\(ival, idInfo) -> (intervalToSpan ival, idInfo))
        (IntervalMap.dominators (spanToInterval span) ivalmap)

spanToInterval :: SourceSpan -> Interval (FilePathPtr, Int, Int)
spanToInterval SourceSpan{..} =
  Interval (spanFilePath, spanFromLine, spanFromColumn)
           (spanFilePath, spanToLine, spanToColumn)

intervalToSpan :: Interval (FilePathPtr, Int, Int) -> SourceSpan
intervalToSpan (Interval (spanFilePath, spanFromLine, spanFromColumn)
                         (_,            spanToLine, spanToColumn)) =
  SourceSpan{..}