{- |
    Module      :  $Header$
    Description :  Conditional compiling transformation
    Copyright   :  (c) 2017        Kai-Oliver Prott
                       2017        Finn Teegen
    License     :  BSD-3-clause

    Maintainer  :  fte@informatik.uni-kiel.de
    Stability   :  experimental
    Portability :  portable

    TODO
-}
module Curry.CondCompile.Transform (condTransform) where

import           Control.Monad.State
import           Control.Monad.Extra        (concatMapM)
import qualified Data.Map            as Map
import           Data.Maybe                 (fromMaybe)
import           Text.Parsec                             hiding (State)
import           Text.Parsec.Error          ()

import Curry.Base.Message
import Curry.Base.Position
import Curry.Base.Pretty

import Curry.CondCompile.Parser
import Curry.CondCompile.Type

type CCState = Map.Map String Int

type CCM = State CCState

condTransform :: CCState -> FilePath -> String -> Either Message String
condTransform s fn p = either (Left . convertError)
                              (Right . transformWith s)
                              (parse program fn p)

transformWith :: CCState -> Program -> String
transformWith s p = show $ pPrint $ evalState (transform p) s

convertError :: ParseError -> Message
convertError err = posMessage pos $
  foldr ($+$) empty $ map text $ tail $ lines $ show err
  where pos = Position (sourceName src) (sourceLine src) (sourceColumn src)
        src = errorPos err

class CCTransform a where
  transform :: a -> CCM [Stmt]

instance CCTransform Stmt where
  transform (Line              s) = return [Line s]
  transform (If     c stmts is e) = do
    s <- get
    if checkCond c s
      then do stmts' <- transform stmts
              return (blank : stmts' ++ fill is ++ fill e ++ [blank])
      else case is of
             []                        -> do
               stmts' <- transform e
               return (blank : fill stmts ++ stmts' ++ [blank])
             (Elif (c', stmts') : is') -> do
               stmts'' <- transform (If c' stmts' is' e)
               return (blank : fill stmts ++ stmts'')
  transform (IfDef  v stmts is e) = transform (If (Defined  v) stmts is e)
  transform (IfNDef v stmts is e) = transform (If (NDefined v) stmts is e)
  transform (Define          v i) = modify (Map.insert v i) >> return [blank]
  transform (Undef           v  ) = modify (Map.delete v) >> return [blank]

instance CCTransform a => CCTransform [a] where
  transform = concatMapM transform

instance CCTransform Else where
  transform (Else (Just p)) = (blank :) <$> transform p
  transform (Else Nothing ) = return []

checkCond :: Cond -> CCState -> Bool
checkCond (Comp v op i) = flip (compareOp op) i . fromMaybe 0 . Map.lookup v
checkCond (Defined   v) = Map.member v
checkCond (NDefined  v) = Map.notMember v

compareOp :: Ord a => Op -> a -> a -> Bool
compareOp Eq  = (==)
compareOp Neq = (/=)
compareOp Lt  = (<)
compareOp Leq = (<=)
compareOp Gt  = (>)
compareOp Geq = (>=)

class FillLength a where
  fillLength :: a -> Int

instance FillLength Stmt where
  fillLength (Line   _           ) = 1
  fillLength (Define _ _         ) = 1
  fillLength (Undef  _           ) = 1
  fillLength (If     _ stmts is e) =
    3 + fillLength stmts + fillLength e + fillLength is
  fillLength (IfDef  v stmts is e) = fillLength (If (Defined  v) stmts is e)
  fillLength (IfNDef v stmts is e) = fillLength (If (NDefined v) stmts is e)

instance FillLength a => FillLength [a] where
  fillLength = foldr ((+) . fillLength) 0

instance FillLength Else where
  fillLength (Else (Just stmts)) = 1 + fillLength stmts
  fillLength (Else Nothing     ) = 0

instance FillLength Elif where
  fillLength (Elif (_, stmts)) = 1 + fillLength stmts

fill :: FillLength a => a -> [Stmt]
fill p = replicate (fillLength p) blank

blank :: Stmt
blank = Line ""
