-- | When the tiles have been transformed, all children of 'TileNode' are
-- references and every tree contains just a single node. This is where this
-- module is used, it removes the redundant type structure.
-- It also provides the possibilty to change the transformation behaviour, but
-- the tile merging is much worse, since we lost a lot of useful information.
--
-- This can be used as a finishing step for materialization or as an
-- intermediate step, since it provides an interface for 'String' and a
-- generic interface which takes a materializer and a substituter.
module Database.Algebra.SQL.Tile.Flatten
    ( flattenTransformResultWith
    , flattenTransformResult
    , FlatTile
    ) where

import qualified Data.IntMap.Lazy                        as IntMap (empty,
                                                                    insert,
                                                                    lookup)
import           Data.Maybe                              (fromMaybe)
import qualified Data.MultiSet                           as MultiSet (MultiSet,
                                                                      empty,
                                                                      insert,
                                                                      singleton,
                                                                      union)

import qualified Database.Algebra.SQL.Query              as Q
import           Database.Algebra.SQL.Query.Substitution
import           Database.Algebra.SQL.Query.Util         (emptySelectStmt,
                                                          mkPCol)
import           Database.Algebra.SQL.Termination
import           Database.Algebra.SQL.Tile


-- TODO error used in lookup

-- | A flat tile containing:
--
--     * the body of the tile
--
--     * a multiset of a type which identifies a referenceable flat tile
--
type FlatTile a = (Q.SelectStmt, MultiSet.MultiSet a)

-- | Flatten a transform result using SQL identifiers directly.
flattenTransformResult :: ([TileTree], [TileDep])
                       -> ([FlatTile String], [(String, FlatTile String)])
flattenTransformResult =
    flattenTransformResultWith m s
  where -- Materializes a table reference as string (a SQL identifier).
        m tableRef    = 't' : show tableRef
        s             = Q.FETableReference

-- | Flatten the transformed result and apply the given functions where
-- possible.
-- The materializer should produce the desired representation, and the
-- substituter should produce expressions which can be substituted as
-- 'Q.FromExpr'.
flattenTransformResultWith :: Ord a
                           => (ExternalReference -> a) -- ^ The materializer.
                           -> (a -> Q.FromExpr)        -- ^ The substituter.
                           -> ([TileTree], [TileDep])
                           -> ([FlatTile a], [(a, FlatTile a)])
flattenTransformResultWith materializer substituter (tiles, deps) =
    ( map (flattenTileTreeWith materializer substituter) tiles
    , map f deps
    )
  where f (extRef, tile) =
            ( materializer extRef
            , flattenTileTreeWith materializer substituter tile
            )

flattenTileTreeWith :: Ord a
                    => (ExternalReference -> a)
                    -> (a -> Q.FromExpr)
                    -> TileTree
                    -> FlatTile a
flattenTileTreeWith materializer substituter (TileNode fs body children) =
    flattenTileNodeWith materializer substituter fs body children

-- This case should never happen, but is provided for completeness.
flattenTileTreeWith materializer substituter (ReferenceLeaf tableId s)  =
    ( emptySelectStmt
      { Q.selectClause = map f s
      , Q.fromClause =
            [Q.FPAlias (substituter alias) aliasName $ Just s]
      }
    , MultiSet.singleton alias
    )
  where f col         = Q.SCAlias (Q.EEBase $ mkPCol aliasName col) col
        aliasName     = "tmpAlias"
        alias         = materializer tableId

flattenTileNodeWith :: Ord a
                    => (ExternalReference -> a)
                    -> (a -> Q.FromExpr)
                    -> FeatureSet
                    -> Q.SelectStmt
                    -> TileChildren
                    -> FlatTile a
flattenTileNodeWith materializer substituter _ body children =
    -- Merge the replacements into the body.
    ( replaceReferencesSelectStmt lookupFun body
    , externalReferences
    )
  where lookupFun v                           =
            fromMaybe (error "missing reference while replacing")
                      $ IntMap.lookup v bodySubstitutes
        (bodySubstitutes, externalReferences) =
            foldr f (IntMap.empty, MultiSet.empty) children

        f (ref, tile) (substitutes, refs) = case tile of
            -- There is probably no better way to merge here, because we have no
            -- information about where the references are located. (TODO lookup
            -- is possible, but expensive)
            TileNode m b c           ->
                let (b', externalRefs) =
                        flattenTileNodeWith materializer substituter m b c
                in
                ( IntMap.insert ref (Q.FESubQuery $ Q.VQSelect b') substitutes
                , MultiSet.union externalRefs refs
                )

            -- Most of the time this case should be taken.
            ReferenceLeaf tableId _  ->
                ( IntMap.insert ref (substituter alias) substitutes
                , MultiSet.insert alias refs
                )
              where alias = materializer tableId

