{-# LANGUAGE GADTs #-}
module DDC.Build.Pipeline.Core
        ( PipeCore (..)
        , pipeCore
        , pipeCores

        , PipeTetra (..)
        , pipeTetra

        , PipeFlow (..)
        , pipeFlow)
where
import DDC.Build.Pipeline.Error
import DDC.Build.Pipeline.Sink
import DDC.Build.Pipeline.Salt
import DDC.Build.Language
import DDC.Core.Simplifier
import DDC.Base.Pretty
import DDC.Base.Name
import DDC.Data.Canned
import DDC.Llvm.Pretty                                  ()

import qualified DDC.Core.Flow                          as Flow
import qualified DDC.Core.Flow.Profile                  as Flow
import qualified DDC.Core.Flow.Transform.Forward        as Flow
import qualified DDC.Core.Flow.Transform.Melt           as Flow
import qualified DDC.Core.Flow.Transform.Wind           as Flow
import qualified DDC.Core.Flow.Transform.Rates.SeriesOfVector as Flow
import qualified DDC.Core.Flow.Convert                  as Flow

import qualified DDC.Core.Tetra.Transform.Curry         as Tetra
import qualified DDC.Core.Tetra.Transform.Boxing        as Tetra
import qualified DDC.Core.Tetra                         as Tetra

import qualified DDC.Core.Babel.PHP                     as PHP

import qualified DDC.Core.Salt.Platform                 as Salt
import qualified DDC.Core.Salt.Runtime                  as Salt
import qualified DDC.Core.Salt                          as Salt

import qualified DDC.Core.Transform.Unshare             as C
import qualified DDC.Core.Transform.Reannotate          as C
import qualified DDC.Core.Transform.Namify              as C
import qualified DDC.Core.Transform.Snip                as Snip
import qualified DDC.Core.Transform.Flatten             as Flatten
import qualified DDC.Core.Transform.Eta                 as Eta
import qualified DDC.Core.Transform.Beta                as Beta
import qualified DDC.Core.Transform.Lambdas             as Lambdas
import qualified DDC.Core.Transform.Forward             as Forward
import qualified DDC.Core.Simplifier                    as C

import qualified DDC.Core.Fragment                      as C
import qualified DDC.Core.Check                         as C
import qualified DDC.Core.Pretty                        as C
import qualified DDC.Core.Module                        as C

import qualified DDC.Core.Exp.Annot                     as C

import qualified DDC.Type.Env                           as Env

import qualified Control.Monad.State.Strict             as S
import Control.Monad
import Control.DeepSeq


---------------------------------------------------------------------------------------------------
-- | Process a core module.
data PipeCore a n where
  -- Plumb the module on without transforming it.
  PipeCoreId
        :: ![PipeCore a n]
        -> PipeCore a n

  -- Output a module to console or file.
  PipeCoreOutput    
        :: !(C.PrettyMode (C.Module a n))
        -> !Sink 
        -> PipeCore a n

  -- Type check a module.
  PipeCoreCheck      
        :: (Pretty a, Pretty (err (C.AnTEC a n)))
        => !(Fragment n err)            -- Language fragment to check against.
        -> !(C.Mode n)                  -- Checker mode.
        -> !Sink                        -- Sink for checker trace.
        -> ![PipeCore (C.AnTEC a n) n]  -- Pipes for result.
        -> PipeCore a n

  -- Type check a module, discarding previous per-node type annotations.
  PipeCoreReCheck
        :: (NFData a, Show a, Pretty a, Pretty (err (C.AnTEC a n)))
        => !(Fragment n err)
        -> !(C.Mode n)
        -> ![PipeCore (C.AnTEC a n)  n]
        -> PipeCore  (C.AnTEC a n') n

  -- Reannotate a module module.
  PipeCoreReannotate
        :: (NFData b, Show b)
        => (a -> b)
        -> ![PipeCore b n]
        ->  PipeCore  a n

  -- Apply a simplifier to a module.
  PipeCoreSimplify  
        :: (Pretty a, CompoundName n)
        => !(Fragment n err)
        -> !s
        -> !(Simplifier s a n)
        -> ![PipeCore () n] 
        -> PipeCore a n

  -- Treat a module as belonging to the Core Tetra fragment from now on.
  PipeCoreAsTetra
        :: ![PipeTetra a]
        -> PipeCore a Tetra.Name

  -- Treat a module as belonging to the Core Flow fragment from now on.
  PipeCoreAsFlow 
        :: Pretty a
        => ![PipeFlow a]
        -> PipeCore a Flow.Name

  -- Treat a module as belonging to the Core Salt fragment from now on.
  PipeCoreAsSalt
        :: Pretty a 
        => ![PipeSalt a] 
        -> PipeCore a Salt.Name

  -- Apply a canned function to a module.
  -- This is helpful for debugging, and tweaking the output before pretty printing.
  -- More reusable transforms should be made into their own pipeline stage.
  PipeCoreHacks
        :: (NFData a, Show b, NFData b)
        => Canned (C.Module a n -> IO (C.Module b n))
        -> ![PipeCore b n]
        -> PipeCore a n


-- | Process a Core module.
--
--   Returns empty list on success.
pipeCore
        :: (NFData a, Show a, NFData n, Eq n, Ord n, Show n, Pretty n)
        => C.Module a n
        -> PipeCore a n
        -> IO [Error]

pipeCore !mm !pp
 = case pp of
        PipeCoreId !pipes
         -> {-# SCC "PipeCoreId" #-}
            pipeCores mm pipes

        PipeCoreOutput !mode !sink
         -> {-# SCC "PipeCoreOutput" #-}
            pipeSink (renderIndent $ pprModePrec mode 0 mm) sink

        PipeCoreCheck !fragment !mode !sinkTrace !pipes
         -> {-# SCC "PipeCoreCheck" #-}
            let profile         = fragmentProfile fragment

                -- Check the module is type correct, 
                --  using the generic core type checker.
                goCheck mm1
                 = case C.checkModule (C.configOfProfile profile) mm1 mode of
                        (Left err,  C.CheckTrace doc) 
                         -> do  pipeSink (renderIndent doc) sinkTrace
                                return [ErrorLint err]
                        
                        (Right mm2, C.CheckTrace doc) 
                         -> do  pipeSink (renderIndent doc) sinkTrace
                                goComplies mm2

                -- Check the module compiles with the language profile.
                goComplies mm1
                 = case C.complies profile mm1 of
                        Just err         -> return [ErrorLint err]
                        Nothing          -> goFragment mm1

                -- Check the module satisfies fragment specific checks.
                goFragment mm1
                 = case fragmentCheckModule fragment mm1 of
                        Just err         -> return [ErrorLint err]
                        Nothing          -> pipeCores mm1 pipes

             in goCheck mm

        PipeCoreReCheck !fragment !mode !pipes
         -> {-# SCC "PipeCoreReCheck" #-}
            pipeCore (C.reannotate C.annotTail mm)
         $  PipeCoreCheck fragment mode SinkDiscard pipes 

        PipeCoreReannotate f !pipes
         -> {-# SCC "PipeCoreStrip" #-}
            let mm' = (C.reannotate f mm)
            in  pipeCores mm' pipes

        PipeCoreSimplify !fragment !nameZero !simpl !pipes
         -> {-# SCC "PipeCoreSimplify" #-}
            let profile         = fragmentProfile fragment
                primKindEnv     = C.profilePrimKinds      profile
                primTypeEnv     = C.profilePrimTypes      profile

                !mm'            = (result . flip S.evalState nameZero
                                   $ applySimplifier profile primKindEnv primTypeEnv simpl mm)

                !mm2            = C.reannotate (const ()) mm'

                -- NOTE: It is helpful to deepseq here so that we release 
                --       references to the unsimplified version of the code.
                --       Because we've just applied reannotate, we also
                --       release type annotations on the expression tree.
            in  mm2 `deepseq` pipeCores mm2 pipes

        PipeCoreAsTetra !pipes
         -> {-# SCC "PipeCoreAsTetra" #-}
            liftM concat $ mapM (pipeTetra mm) pipes

        PipeCoreAsFlow !pipes
         -> {-# SCC "PipeCoreAsFlow" #-}
            liftM concat $ mapM (pipeFlow mm) pipes

        PipeCoreAsSalt !pipes
         -> {-# SCC "PipeCoreAsSalt" #-}
            liftM concat $ mapM (pipeSalt mm) pipes

        PipeCoreHacks !(Canned f) !pipes
         -> {-# SCC "PipeCoreHacks" #-} 
            do  mm'     <- f mm
                pipeCores mm' pipes


pipeCores :: (NFData a, Show a, NFData n, Eq n, Ord n, Show n, Pretty n)
          => C.Module a n -> [PipeCore a n] -> IO [Error]

pipeCores !mm !pipes 
 = go [] pipes
 where  go !errs []   
         = return errs

        go !errs (pipe : rest)
         = do   !err     <- pipeCore mm pipe
                go (errs ++ err) rest


-- PipeTetra --------------------------------------------------------------------------------------
-- | Process a Core Tetra module.
data PipeTetra a where
        -- Output the module in core language syntax.
        PipeTetraOutput 
         :: !Sink
         -> PipeTetra a

        -- Manage currying of functions.
        PipeTetraCurry
         :: (NFData a, Show a)
         => ![PipeCore () Tetra.Name]
         -> PipeTetra  (C.AnTEC a Tetra.Name)

        -- Manage boxing of numeric values.
        PipeTetraBoxing
         :: (NFData a, Show a)
         => ![PipeCore a Tetra.Name]
         -> PipeTetra a

        -- Convert the module to the Core Salt Fragment.
        PipeTetraToSalt 
         :: (NFData a, Show a)
         => !Salt.Platform 
         -> !Salt.Config
         -> ![PipeCore a Salt.Name]
         -> PipeTetra  (C.AnTEC a Tetra.Name)

        -- Print as PHP code
        PipeTetraToPHP
         :: (NFData a, Show a)
         => !Sink
         -> PipeTetra a



-- | Process a Core Tetra module.
pipeTetra 
        :: C.Module a Tetra.Name
        -> PipeTetra a
        -> IO [Error]

pipeTetra !mm !pp
 = case pp of
        PipeTetraOutput !sink
         -> {-# SCC "PipeTetraOutput" #-}
            pipeSink (renderIndent $ ppr mm)  sink

        PipeTetraCurry  !pipes
         -> {-# SCC "PipeTetraCurry"  #-}
            case Tetra.curryModule (C.unshareModule mm) of
             Left err  -> return [ErrorTetraConvert err]
             Right mm' -> pipeCores mm' pipes

        PipeTetraBoxing !pipes
         -> {-# SCC "PipeTetraBoxing" #-}
            pipeCores (Tetra.boxingModule mm) pipes

        PipeTetraToSalt !platform !runConfig !pipes
         -> {-# SCC "PipeTetraToSalt" #-}
            case Tetra.saltOfTetraModule platform runConfig 
                        (C.profilePrimDataDefs Tetra.profile) 
                        (C.profilePrimKinds    Tetra.profile)
                        (C.profilePrimTypes    Tetra.profile)
                        mm 
             of  Left  err  -> return [ErrorTetraConvert err]
                 Right mm'  -> pipeCores mm' pipes 

        PipeTetraToPHP !sink
         -> {-# SCC "PipeTetraToPHP" #-}
            let -- Snip program to expose intermediate bindings.
                mm_snip         = Flatten.flatten 
                                $ Snip.snip (Snip.configZero) mm

                -- The floater needs bindings to be fully named.
                namifierT       = C.makeNamifier Tetra.freshT Env.empty
                namifierX       = C.makeNamifier Tetra.freshX Env.empty
                mm_namified     = S.evalState (C.namify namifierT namifierX mm_snip) 0

                doc  = PHP.phpOfModule mm_namified
            in  pipeSink (renderIndent doc) sink


-- PipeFlow ---------------------------------------------------------------------------------------
-- | Process a Core Flow module.
data PipeFlow a where
  -- Output the module in core language syntax.
  PipeFlowOutput 
        :: Sink
        -> PipeFlow a

  -- Apply a canned function to a module.
  -- This is helpful for debugging, and tweaking the output before pretty printing.
  -- More reusable transforms should be made into their own pipeline stage.
  PipeFlowHacks
        :: (NFData a, Show b, NFData b)
        => Canned (C.Module a Flow.Name -> IO (C.Module b Flow.Name))
        -> ![PipeFlow b]
        -> PipeFlow a

  -- Run the prep transform to expose flow operators.
  PipeFlowPrep
        :: [PipeCore () Flow.Name] 
        -> PipeFlow ()

  -- Run rate inference to transform vector operations into loops of series expressions.
  PipeFlowRate
        :: [PipeCore () Flow.Name] 
        -> PipeFlow ()

  -- Run the lowering transform on a module.
  --  It needs to be already prepped and have full type annotations.
  --  Lowering it kills the annotations.
  PipeFlowLower
        :: Flow.Config
        -> [PipeCore () Flow.Name]
        -> PipeFlow (C.AnTEC () Flow.Name)

  -- Melt compound data into primitive types.
  PipeFlowMelt
        :: [PipeCore () Flow.Name]
        -> PipeFlow (C.AnTEC () Flow.Name)

  -- Wind loop# primops into tail recursive loops.
  PipeFlowWind
        :: [PipeCore () Flow.Name]
        -> PipeFlow (C.AnTEC () Flow.Name)

  -- Wind loop# primops into tail recursive loops.
  PipeFlowToTetra
        :: [PipeCore () Salt.Name]
        -> PipeFlow (C.AnTEC () Flow.Name)


-- | Process a Core Flow module.
pipeFlow :: C.Module a Flow.Name
         -> PipeFlow a
         -> IO [Error]

pipeFlow !mm !pp
 = case pp of
        PipeFlowOutput !sink
         -> {-# SCC "PipeFlowOutput" #-}
            pipeSink (renderIndent $ ppr mm) sink

        PipeFlowHacks !(Canned f) !pipes
         -> {-# SCC "PipeFlowHacks" #-} 
            do  mm'     <- f mm
                pipeFlows mm' pipes

        PipeFlowPrep  !pipes
         -> {-# SCC "PipeFlowPrep"   #-}
            let 
                -- Eta-expand so all workers have explicit parameter names.
                mm_eta          = C.result $ Eta.etaModule Flow.profile
                                        (Eta.configZero { Eta.configExpand = True})
                                        mm

                -- Snip program to expose intermediate bindings.
                mm_snip         = Flatten.flatten 
                                $ Snip.snip 
                                        (Snip.configZero { Snip.configSnipLetBody = True })
                                        mm_eta

                -- The floater needs bindings to be fully named.
                namifierT       = C.makeNamifier Flow.freshT Env.empty
                namifierX       = C.makeNamifier Flow.freshX Env.empty
                mm_namified     = S.evalState (C.namify namifierT namifierX mm_snip) 0

                -- Float worker functions and initializers into their use sites, 
                -- leaving only flow operators at the top-level.
                mm_float        = Flow.forwardProcesses mm_namified

            in  pipeCores mm_float pipes

        PipeFlowRate  !pipes
         -> {-# SCC "PipeFlowRate"   #-}
            let 
                -- Eta-expand so all workers have explicit parameter names.
                mm_eta          = C.result $ Eta.etaModule Flow.profile
                                        (Eta.configZero { Eta.configExpand = True})
                                        mm

                -- Snip program to expose intermediate bindings.
                mm_snip         = Flatten.flatten 
                                $ Snip.snip 
                                        (Snip.configZero { Snip.configSnipLetBody = True })
                                        mm_eta

                -- The floater needs bindings to be fully named.
                namifierT       = C.makeNamifier Flow.freshT Env.empty
                namifierX       = C.makeNamifier Flow.freshX Env.empty
                mm_namified     = S.evalState (C.namify namifierT namifierX mm_snip) 0


                floatControl l
                 = case l of
                   C.LLet _ x
                     | Just _ <- C.takeXLamFlags x
                     -> Forward.FloatForceUsedOnce
                   _ -> Forward.FloatDeny

                -- Force forward all worker functions.
                -- Anything that's not a vector op will be treated as an external,
                -- so that's fine.
                mm_float        = C.result
                                $ Forward.forwardModule Flow.profile
                                    (Forward.Config floatControl False)
                                    $ C.reannotate (const ()) mm_namified



                goRate
                 -- Rate inference uses the types
                 = case C.checkModule (C.configOfProfile Flow.profile) mm_float C.Recon of
                     (Left err, _)    
                      -> return [ErrorCoreTransform err]

                     (Right mm', _) 
                      -> let mm_stripped = C.reannotate (const ()) mm'
                             mm_flow     = fst $ Flow.seriesOfVectorModule mm_stripped
                           
                            -- Synthesise the types of any newly created bindings.
                         in case C.checkModule (C.configOfProfile Flow.profile) mm_flow C.Synth of
                             (Left err, _ct)         
                              -> return [ErrorCoreTransform err]
                            
                             (Right mm_flow', _ct) 
                              -> let mm_reannot' = C.reannotate (const ()) mm_flow'
                                 in pipeCores mm_reannot' pipes
            in  goRate


        PipeFlowLower !config !pipes 
         -> {-# SCC "PipeFlowLower" #-}
            let mm_stripped     = C.reannotate (const ()) mm

            in  case Flow.lowerModule config mm_stripped of
                 Right mm'      -> pipeCores mm' pipes
                 Left  err      -> return [ErrorCoreTransform err]

        PipeFlowMelt !pipes
         -> {-# SCC "PipeFlowMelt" #-}
            let mm_stripped     = C.reannotate (const ()) mm
                (mm_melted, _info) = Flow.meltModule mm_stripped
            in  pipeCores mm_melted pipes

        PipeFlowWind !pipes
         -> {-# SCC "PipeFlowWind" #-}
            let mm_stripped     = C.reannotate (const ()) mm
                mm_wound        = Flow.windModule mm_stripped
            in  pipeCores mm_wound pipes

        PipeFlowToTetra !pipes
         -> {-# SCC "PipeFlowToTetra" #-}
            let 
                -- Apply any lambdas we can
                mm_beta         = C.result $ Beta.betaReduce Flow.profile
                                        (Beta.configZero { Beta.configBindRedexes = True})
                                        mm

                -- Eta-expand all the leftovers so they can be lifted
                mm_eta          = C.result $ Eta.etaModule   Flow.profile
                                        (Eta.configZero { Eta.configExpand = True})
                                        mm_beta


                -- Lift up any remaining lambdas
                mm_lift         = Lambdas.lambdasModule Flow.profile mm_eta

                -- Snip program so arguments and case scrutinees are just variables
                mm_snip         = Flatten.flatten 
                                $ Snip.snip 
                                        Snip.configZero
                                        mm_lift

                -- The floater needs bindings to be fully named.
                namifierT       = C.makeNamifier Flow.freshT Env.empty
                namifierX       = C.makeNamifier Flow.freshX Env.empty
                mm_namified     = S.evalState (C.namify namifierT namifierX mm_snip) 0

            in  case Flow.tetraOfFlowModule mm_namified of
                 Left  err  -> return [ErrorFlowConvert err]
                 Right mm'  ->
                  case C.checkModule (C.configOfProfile Salt.profile) mm' C.Recon of
                   (Left err, _ct)         
                    -> return [ErrorCoreTransform err]
                   (Right mm_check', _ct) 
                    -> let mm_reannot' = C.reannotate (const ()) mm_check'

                           floatControl l
                             = case l of
                               C.LLet b _
                                 | Just _ <- C.takeTFun $ C.typeOfBind b
                                 -> Forward.FloatForce
                               _ -> Forward.FloatAllow

                           -- Forward all functions
                           mm_float        = C.result
                                           $ Forward.forwardModule Salt.profile
                                               (Forward.Config floatControl True)
                                               $ C.reannotate (const ()) mm_reannot'

                       in  pipeCores mm_float pipes



-- | Process a Flow module with several different pipes.
pipeFlows :: (NFData a, Show a)
          => C.Module a Flow.Name -> [PipeFlow a] -> IO [Error]

pipeFlows !mm !pipes 
 = go [] pipes
 where  go !errs []   
         = return errs

        go !errs (pipe : rest)
         = do   !err     <- pipeFlow mm pipe
                go (errs ++ err) rest