{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE RankNTypes                 #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE TypeApplications           #-}
{-# LANGUAGE TypeFamilies               #-}

-- |
-- Module      :  Datafix.FrameworkBuilder
-- Copyright   :  (c) Sebastian Graf 2017-2020
-- License     :  ISC
-- Maintainer  :  sgraf1337@gmail.com
-- Portability :  portable
--
-- Builds a 'DataFlowFramework' for a 'Denotation'al formulation in terms of
-- 'MonadDatafix'. Effectively reduces descriptions from "Datafix.Denotational"
-- to ones from "Datafix.Explicit", so that solvers such as "Datafix.Worklist"
-- only have to provide an interpreter for 'MonadDependency'.

module Datafix.FrameworkBuilder
  ( FrameworkBuilder
  , buildFramework
  ) where

import           Data.Primitive.Array
import           Datafix.Common
import           Datafix.Denotational
import           Datafix.Explicit
import           Datafix.NodeAllocator

-- | Constructs a build plan for a 'DataFlowFramework' by tracking allocation of
-- 'Node's mapping to 'ChangeDetector's and transfer functions.
newtype FrameworkBuilder m a
  = FrameworkBuilder { unwrapFB :: NodeAllocator (ChangeDetector (Domain m), LiftedFunc (Domain m) m) a }
  deriving (Functor, Applicative, Monad)

instance MonadDependency m => MonadDatafix (FrameworkBuilder m) where
  type DepM (FrameworkBuilder m) = m
  datafix cd func = FrameworkBuilder $ allocateNode $ \node -> do
    let deref = dependOn @m node
    (ret, transfer) <- unwrapFB (func deref)
    return (ret, (cd, transfer))

-- | @(root, max, dff) = buildFramework builder@ executes the build plan specified
-- by @builder@ and returns the resulting 'DataFlowFramework' @dff@, as well as
-- the @root@ 'Node' denoting the transfer function returned by the
-- 'FrameworkBuilder' action and the @max@imum node of the problem as a proof for
-- its denseness.
buildFramework
  :: forall m a
   . MonadDependency m
  => (forall md . (MonadDatafix md, DepM md ~ m) => md a)
  -> (a, Node, DataFlowFramework m)
buildFramework plan = (a, Node (sizeofArray arr - 1), prob)
  where
    prob = DFF (snd . indexArray arr . unwrapNode) (fst . indexArray arr . unwrapNode)
    (a, arr) = runAllocator $ unwrapFB $ plan @(FrameworkBuilder m)