module Language.MASMGen.Core ( newGlobalVar
                             , mkFunc
                             , initFuncState
                             , initProgState
                             , section
                             , output
                             , produceAsm
                             , produceAsmOptions
                             , produceAsmInclude
                             , produceAsmProg
                             , produceAsmGlobalVarMap
                             , produceAsmFuncs
                             , printShowableInstr
                             , add
                             , addb
                             , addw
                             , addl
                             , sub
                             , subb
                             , subw
                             , subl
                             , imul
                             , idiv
                             , inc
                             , dec
                             , mov
                             , movb
                             , movw
                             , movl
                             , goto
                             , push
                             , pushl
                             , pop
                             , popl
                             , shl
                             , sal
                             , shr
                             , sar
                             , lea
                             , label
                             , comment
                             )
                                   where
import Language.MASMGen.Types
import qualified Data.Map as M
import Control.Monad.State.Lazy
import Control.Monad.Writer.Lazy
import Data.List
import Data.Word
mkFunc :: String -> MASMFuncM () -> MASMProgM ()
mkFunc name thisFunc = do
  f <- gets funcs
  modify $ \s -> s { funcs = Func (execState thisFunc (initFuncState name)) : f }
newGlobalVar :: String -> MASMVar -> MASMProgM Var
newGlobalVar name value = do
  map <- gets globalVarMap
  modify $ \s -> s { globalVarMap = M.insert name value map }
  return $ mkVar name (fst value)
initFuncState :: String -> MASMFunc
initFuncState s = MASMFunc { funcName = s
                           , instrs = []
                           }
initProgState :: MASMProg
initProgState = MASMProg { globalVarMap = M.empty
                         , funcs = []
                         }
section :: String -> Writer [MASMOutput] ()
section x = stell $ MASMOutput $ '.' : x
output :: [MASMOutput] -> [String]
output x = let output' :: Int -> [MASMOutput] -> [String]
               output' indent (y:ys) = case y of
                                         MASMOutput str -> (replicate indent ' ') <> str : output' indent ys
                                         MASMOutputNoIndent str -> str : output' indent ys
                                         Indent -> output' (indent + 4) ys
                                         Dedent -> case indent - 4 >= 0 of
                                                     True -> output' (indent - 4) ys
                                                     False -> output' 0 ys
                                         NewLine -> "" : output' indent ys
               output' _ [] = []
           in output' 0 x
produceAsm :: MASM -> Writer [MASMOutput] ()
produceAsm (MASM { masmProgMode = progMode
                 , masmProgOptions = progOptions
                 , masmInclude = include
                 , masmProg = prog
                 }) = do
  stell $ MASMOutput $ case progMode of
                                 Mode386 -> ".386"
                                 Mode486 -> ".486"
                                 Mode586 -> ".586"
                                 Mode686 -> ".686"
  produceAsmOptions progOptions
  produceAsmInclude include
  produceAsmProg prog
produceAsmOptions :: [String] -> Writer [MASMOutput] ()
produceAsmOptions = tell . map (MASMOutput . ("option " <>))
produceAsmInclude :: [MASMInclude] -> Writer [MASMOutput] ()
produceAsmInclude = tell . map (\item -> MASMOutput (case item of
                                                       MASMInclude a -> "include " <> a
                                                       MASMIncludeLib a -> "includelib " <> a))
produceAsmProg :: MASMProgM () -> Writer [MASMOutput] ()
produceAsmProg prog = let finalProg = execState prog initProgState
                      in do
                        section "DATA"
                        produceAsmGlobalVarMap $ globalVarMap finalProg
                        section "CODE"
                        produceAsmFuncs $ reverse $ funcs finalProg
produceAsmGlobalVarMap :: MASMVarMap -> Writer [MASMOutput] ()
produceAsmGlobalVarMap varMap = let assocsList = M.assocs varMap
                                    printVar :: (String, (MASMType, Maybe [Lit])) -> Writer [MASMOutput] ()
                                    printVar (name, (varType, val)) =
                                        let result = case val of
                                                       Just x -> intersperse ',' (concat . map show $ x)
                                                       Nothing -> "?"
                                        in
                                          stell $ MASMOutput $ name <> " " <> show varType <> " " <> result
                                in do
                                  sequence_ $ map printVar assocsList
produceAsmFuncs :: [MASMTopLevel] -> Writer [MASMOutput] ()
produceAsmFuncs (x:xs) = do
  case x of
    Func func -> let name = funcName func
                     ins = reverse $ instrs func
                 in do
                   stell $ MASMOutput $ name <> " PROC"
                   stell $ Indent
                   sequence_ $ map printShowableInstr ins
                   stell $ Dedent
                   stell $ MASMOutput $ name <> " ENDP"
  stell $ NewLine
  produceAsmFuncs xs
produceAsmFuncs [] = return ()
printShowableInstr :: MASMInstr -> Writer [MASMOutput] ()
printShowableInstr instr = let binOp m x y = stell $ MASMOutput $ m <> " " <> show x <> ", " <> show y
                               sizedBinOp m size x y = case size of
                                                         Just size -> stell $ MASMOutput $ m <> " " <> show size <> " " <> show x <> ", " <> show y
                                                         Nothing -> binOp m x y
                               sinOp m x = stell $ MASMOutput $ m <> " " <> show x
                               sizedSinOp m size x = case size of
                                                       Just size -> stell $ MASMOutput $ m <> " " <> show size <> " " <> show x
                                                       Nothing -> sinOp m x
                           in case instr of
                                MASMAdd size x y -> sizedBinOp "ADD" size x y
                                MASMSub size x y -> sizedBinOp "SUB" size x y
                                MASMMul size x y -> sizedBinOp "IMUL" size x y
                                MASMDiv size x y -> sizedBinOp "IDIV" size x y
                                MASMInc size x -> sizedSinOp "INC" size x
                                MASMDec size x -> sizedSinOp "DEC" size x
                                MASMMov size x y -> sizedBinOp "MOV" size x y
                                MASMMovsx x y -> binOp "MOVSX" x y
                                MASMMovzx x y -> binOp "MOVZX" x y
                                MASMFuncCall name convention _ -> error "func call not implemented"
                                MASMGoto x -> sinOp "GOTO" x
                                MASMLabel x -> stell $ MASMOutputNoIndent $ x <> ":"
                                MASMPush size x -> sizedSinOp "PUSH" size x
                                MASMPop size x -> sizedSinOp "POP" size x
                                MASMShl x y -> binOp "SHL" x y
                                MASMSal x y -> binOp "SAL" x y
                                MASMShr x y -> binOp "SHR" x y
                                MASMSar x y -> binOp "SAR" x y
                                MASMLea x y -> binOp "LEA" x y
                                MASMComment x -> stell $ MASMOutput $ ';' : x
modFun :: MASMInstr -> MASMFuncM ()
modFun x = modify (\f -> let i = instrs f
                         in f { instrs = x : i })
add :: Operand -> Operand -> MASMFuncM ()
add x y = modFun $ MASMAdd Nothing x y
addb :: Operand -> Operand -> MASMFuncM ()
addb x y = modFun $ MASMAdd (Just DB) x y
addw :: Operand -> Operand -> MASMFuncM ()
addw x y = modFun $ MASMAdd (Just DW) x y
addl :: Operand -> Operand -> MASMFuncM ()
addl x y = modFun $ MASMAdd (Just DD) x y
sub :: Operand -> Operand -> MASMFuncM ()
sub x y = modFun $ MASMSub Nothing x y
subb :: Operand -> Operand -> MASMFuncM ()
subb x y = modFun $ MASMSub (Just DB) x y
subw :: Operand -> Operand -> MASMFuncM ()
subw x y = modFun $ MASMSub (Just DW) x y
subl :: Operand -> Operand -> MASMFuncM ()
subl x y = modFun $ MASMSub (Just DD) x y
imul :: Operand -> Operand -> MASMFuncM ()
imul x y = modFun $ MASMMul Nothing x y
idiv :: Operand -> Operand -> MASMFuncM ()
idiv x y = modFun $ MASMDiv Nothing x y
inc :: Operand -> MASMFuncM ()
inc x = modFun $ MASMInc Nothing x
dec :: Operand -> MASMFuncM ()
dec x = modFun $ MASMDec Nothing x
mov :: Operand -> Operand -> MASMFuncM ()
mov x y = modFun $ MASMMov Nothing x y
typedSinOp :: TypedMASMInstrSinCon -> MASMType -> Operand -> MASMFuncM ()
typedSinOp instr ty x = case operandClass x of
                          Pointer -> modFun $ instr (Just (Ptr ty)) x
                          _ -> modFun $ instr (Just ty) x
typedBinOp :: TypedMASMInstrBinCon -> MASMType -> Operand -> Operand -> MASMFuncM ()
typedBinOp instr ty x y = case operandClass x of
                            Pointer -> modFun $ instr (Just (Ptr ty)) x y
                            _ -> modFun $ instr (Just ty) x y
movb :: Operand -> Operand -> MASMFuncM ()
movb = typedBinOp MASMMov DB
movw :: Operand -> Operand -> MASMFuncM ()
movw = typedBinOp MASMMov DW
movl :: Operand -> Operand -> MASMFuncM ()
movl = typedBinOp MASMMov DD
movsx :: Operand -> Operand -> MASMFuncM ()
movsx x y = modFun $ MASMMovsx x y
movzx :: Operand -> Operand -> MASMFuncM ()
movzx x y = modFun $ MASMMovzx x y
goto :: String -> MASMFuncM ()
goto x = modFun $ MASMGoto x
push :: Operand -> MASMFuncM ()
push x = modFun $ MASMPush Nothing x
pushl :: Operand -> MASMFuncM ()
pushl x = typedSinOp MASMPush DD x
pop :: Operand -> MASMFuncM ()
pop x = modFun $ MASMPop Nothing x
popl :: Operand -> MASMFuncM ()
popl x = typedSinOp MASMPop DD x
shl :: Operand -> Operand -> MASMFuncM ()
shl dest count = modFun $ MASMShl dest count
sal :: Operand -> Operand -> MASMFuncM ()
sal dest count = modFun $ MASMSal dest count
shr :: Operand -> Operand -> MASMFuncM ()
shr dest count = modFun $ MASMShr dest count
sar :: Operand -> Operand -> MASMFuncM ()
sar dest count = modFun $ MASMSar dest count
lea :: Operand -> Operand -> MASMFuncM ()
lea x y = modFun $ MASMLea x y
label :: String -> MASMFuncM ()
label x = modFun $ MASMLabel x
comment :: String -> MASMFuncM ()
comment x = modFun $ MASMComment x
stell :: (Monad m, Monoid (m a)) => a -> Writer (m a) ()
stell = tell . return