{-# LANGUAGE TypeFamilies, ScopedTypeVariables, TypeSynonymInstances, FlexibleInstances, FlexibleContexts #-}
-- | Translation from the private to the public types
module IdeSession.Types.Translation (
    XShared
  , ExplicitSharing(..)
  , IntroduceSharing(..)
  , showNormalized
  , dereferenceFilePathPtr
  ) where

import Prelude hiding (mod, span)
import Data.Proxy
import qualified Data.ByteString.Char8 as BSSC
import qualified Data.Text as Text
import Data.Binary (Binary)

import IdeSession.Strict.Container
import qualified IdeSession.Types.Public  as Public
import qualified IdeSession.Types.Private as Private
import qualified IdeSession.Strict.IntMap as StrictIntMap
import qualified IdeSession.Strict.Maybe  as StrictMaybe

-- | The associated type with explicit sharing
type family XShared a

-- | The inverse of MShared, only for decidability of type checking
type family MShared a

type instance XShared Public.IdProp          = Private.IdProp
type instance XShared Public.IdInfo          = Private.IdInfo
type instance XShared Public.IdScope         = Private.IdScope
type instance XShared Public.SourceSpan      = Private.SourceSpan
type instance XShared Public.EitherSpan      = Private.EitherSpan
type instance XShared Public.SourceError     = Private.SourceError
-- type instance XShared Public.IdMap           = Private.IdMap
-- type instance XShared Public.LoadedModules   = Private.LoadedModules
type instance XShared Public.ModuleId        = Private.ModuleId
type instance XShared Public.PackageId       = Private.PackageId
type instance XShared Public.ImportEntities  = Private.ImportEntities
type instance XShared Public.Import          = Private.Import
type instance XShared Public.SpanInfo        = Private.SpanInfo
type instance XShared Public.RunResult       = Private.RunResult
type instance XShared Public.BreakInfo       = Private.BreakInfo

type instance MShared Private.IdProp         = Public.IdProp
type instance MShared Private.IdInfo         = Public.IdInfo
type instance MShared Private.IdScope        = Public.IdScope
type instance MShared Private.SourceSpan     = Public.SourceSpan
type instance MShared Private.EitherSpan     = Public.EitherSpan
type instance MShared Private.SourceError    = Public.SourceError
-- type instance MShared Private.IdMap          = Public.IdMap
-- type instance MShared Private.LoadedModules  = Public.LoadedModules
type instance MShared Private.ModuleId       = Public.ModuleId
type instance MShared Private.PackageId      = Public.PackageId
type instance MShared Private.ImportEntities = Public.ImportEntities
type instance MShared Private.Import         = Public.Import
type instance MShared Private.SpanInfo       = Public.SpanInfo
type instance MShared Private.RunResult      = Public.RunResult
type instance MShared Private.BreakInfo      = Public.BreakInfo

{------------------------------------------------------------------------------
  Removing explicit sharing
------------------------------------------------------------------------------}

-- | Many of the public data types that we export in "IdeSession" have a
-- corresponding private @XShared@ version. For instance, we have @IdProp@ and
-- @XShared IdProp@, @SourceError@ and @XShared SourceError@, etc. These
-- @XShared@ types are abstract; what's important is only that they can be
-- serialized (support @FromJSON@ and @ToJSON@). The main difference between
-- the public and the private data types is that the private data types use
-- explicit sharing. This is important for serialization, because there is
-- quite a bit of sharing in the type information that we collect and losing
-- this would be a significant performance hit. (The other difference is that
-- the private data types use specialized types that guarantee strictness.)
--
-- The @MShared (XShared a) ~ a@ condition on the @ExplicitSharing@ type class
-- is there for technical reasons only (it convinces GHC that the @XShared@
-- type family is a bijection).
class (MShared (XShared a) ~ a, Binary (XShared a)) => ExplicitSharing a where
  removeExplicitSharing :: Proxy a -> Private.ExplicitSharingCache -> XShared a -> a

showNormalized :: forall a. (Show a, ExplicitSharing a, MShared (XShared a) ~ a)
               => Proxy a -> Private.ExplicitSharingCache -> XShared a -> String
showNormalized _ cache x = show (removeExplicitSharing Proxy cache x :: a)

instance ExplicitSharing Public.IdProp where
  removeExplicitSharing _ cache Private.IdProp{..} = Public.IdProp {
      Public.idName       = idName
    , Public.idSpace      = idSpace
    , Public.idType       = toLazyMaybe idType
    , Public.idDefSpan    = removeExplicitSharing Proxy cache idDefSpan
    , Public.idDefinedIn  = removeExplicitSharing Proxy cache idDefinedIn
    , Public.idHomeModule = StrictMaybe.maybe
                              Nothing
                              (Just . removeExplicitSharing Proxy cache)
                              idHomeModule
    }

instance ExplicitSharing Public.IdInfo where
  removeExplicitSharing _ cache Private.IdInfo{..} = Public.IdInfo {
      Public.idProp  = case StrictIntMap.lookup (Private.idPropPtr idProp)
                                                (Private.idPropCache cache)
                         of Just idProp' -> removeExplicitSharing Proxy cache idProp'
                            Nothing      -> unknownProp
    , Public.idScope = removeExplicitSharing Proxy cache idScope
    }
    where
      unknownProp = Public.IdProp {
          idName        = Text.pack "<<unknown id>>"
        , idSpace       = Public.VarName
        , idType        = Nothing
        , idDefinedIn   = unknownModule
        , idDefSpan     = Public.TextSpan (Text.pack "<<unknown span>>")
        , idHomeModule  = Nothing
        }

      unknownModule = Public.ModuleId {
          moduleName    = Text.pack "<<unknown module>>"
        , modulePackage = unknownPackage
        }

      unknownPackage = Public.PackageId {
         packageName    = Text.pack "<<unknown package>>"
       , packageVersion = Nothing
       , packageKey     = Text.pack "<<unknown package>>"
       }

instance ExplicitSharing Public.ModuleId where
  removeExplicitSharing _ cache Private.ModuleId{..} = Public.ModuleId {
      Public.moduleName    = moduleName
    , Public.modulePackage = removeExplicitSharing Proxy cache modulePackage
    }

instance ExplicitSharing Public.PackageId where
  removeExplicitSharing _ _cache Private.PackageId{..} = Public.PackageId {
      Public.packageName    = packageName
    , Public.packageVersion = toLazyMaybe packageVersion
    , Public.packageKey     = packageKey
    }

instance ExplicitSharing Public.IdScope where
  removeExplicitSharing _ cache idScope = case idScope of
    Private.Binder -> Public.Binder
    Private.Local  -> Public.Local
    Private.Imported {..} -> Public.Imported {
        Public.idImportedFrom = removeExplicitSharing Proxy cache idImportedFrom
      , Public.idImportSpan   = removeExplicitSharing Proxy cache idImportSpan
      , Public.idImportQual   = idImportQual
      }
    Private.WiredIn -> Public.WiredIn

instance ExplicitSharing Public.SourceSpan where
  removeExplicitSharing _ cache Private.SourceSpan{..} = Public.SourceSpan {
      Public.spanFilePath   = dereferenceFilePathPtr cache spanFilePath
    , Public.spanFromLine   = spanFromLine
    , Public.spanFromColumn = spanFromColumn
    , Public.spanToLine     = spanToLine
    , Public.spanToColumn   = spanToColumn
    }

instance ExplicitSharing Public.EitherSpan where
  removeExplicitSharing _ cache eitherSpan = case eitherSpan of
    Private.ProperSpan sourceSpan ->
      Public.ProperSpan (removeExplicitSharing Proxy cache sourceSpan)
    Private.TextSpan str ->
      Public.TextSpan str

instance ExplicitSharing Public.SourceError where
  removeExplicitSharing _ cache Private.SourceError{..} = Public.SourceError {
      Public.errorKind = errorKind
    , Public.errorSpan = removeExplicitSharing Proxy cache errorSpan
    , Public.errorMsg  = errorMsg
    }

{-
instance ExplicitSharing Public.IdMap where
  removeExplicitSharing cache = Public.IdMap
                              . toLazyMap
                              . StrictMap.map (removeExplicitSharing cache)
                              . StrictMap.mapKeys (removeExplicitSharing cache)
                              . Private.idMapToMap
-}

{-
instance ExplicitSharing Public.LoadedModules where
  removeExplicitSharing cache = Map.map (removeExplicitSharing cache)
                              . toLazyMap
-}

instance ExplicitSharing Public.ImportEntities where
  removeExplicitSharing _ _cache entities = case entities of
    Private.ImportAll          -> Public.ImportAll
    Private.ImportHiding names -> Public.ImportHiding (toLazyList names)
    Private.ImportOnly   names -> Public.ImportOnly (toLazyList names)

instance ExplicitSharing Public.Import where
  removeExplicitSharing _ cache Private.Import{..} = Public.Import {
      Public.importModule     = removeExplicitSharing Proxy cache $ importModule
    , Public.importPackage    = toLazyMaybe importPackage
    , Public.importQualified  = importQualified
    , Public.importImplicit   = importImplicit
    , Public.importAs         = toLazyMaybe importAs
    , Public.importEntities   = removeExplicitSharing Proxy cache $ importEntities
    }

instance ExplicitSharing Public.SpanInfo where
  removeExplicitSharing _ cache spanInfo = case spanInfo of
    Private.SpanId       idInfo -> Public.SpanId (removeExplicitSharing Proxy cache idInfo)
    Private.SpanQQ       idInfo -> Public.SpanQQ (removeExplicitSharing Proxy cache idInfo)
    Private.SpanInSplice idInfo -> Public.SpanId (removeExplicitSharing Proxy cache idInfo)

instance ExplicitSharing Public.BreakInfo where
  removeExplicitSharing _ cache Private.BreakInfo{..} = Public.BreakInfo {
      Public.breakInfoModule      = breakInfoModule
    , Public.breakInfoSpan        = removeExplicitSharing Proxy cache breakInfoSpan
    , Public.breakInfoResultType  = breakInfoResultType
    , Public.breakInfoVariableEnv = breakInfoVariableEnv
    }

{------------------------------------------------------------------------------
  Low-level API
------------------------------------------------------------------------------}

dereferenceFilePathPtr :: Private.ExplicitSharingCache
                       -> Private.FilePathPtr -> FilePath
dereferenceFilePathPtr cache ptr = BSSC.unpack $
    StrictIntMap.findWithDefault
      unknownFilePath
      (Private.filePathPtr ptr)
      (Private.filePathCache cache)
  where
    unknownFilePath = BSSC.pack "<<unknown filepath>>"

{------------------------------------------------------------------------------
  Introducing explicit sharing
------------------------------------------------------------------------------}

-- | Introduce explicit sharing
--
-- This provides the opposite translation to removeExplicitSharing. Note however
-- that this is a partial function -- we never extend the cache, so if a
-- required value is missing from the cache we return @Nothing@.
class IntroduceSharing a where
  introduceExplicitSharing :: Private.ExplicitSharingCache -> a -> Maybe (XShared a)

instance IntroduceSharing Public.SourceSpan where
  introduceExplicitSharing cache Public.SourceSpan{..} = do
    ptr <- StrictIntMap.reverseLookup (Private.filePathCache cache)
                                      (BSSC.pack spanFilePath)
    return Private.SourceSpan {
        Private.spanFilePath   = Private.FilePathPtr ptr
      , Private.spanFromLine   = spanFromLine
      , Private.spanFromColumn = spanFromColumn
      , Private.spanToLine     = spanToLine
      , Private.spanToColumn   = spanToColumn
      }