module Arm.Encoder
  ( encode )
where
import Data.Bits
import Data.Int
import Data.Word
import Data.Array
import Arm.Instruction
import Arm.Operand
import Arm.RegisterName
condEq :: (Int, Int, Word32)
condNe :: (Int, Int, Word32)
condCs :: (Int, Int, Word32)
condHs :: (Int, Int, Word32)
condCc :: (Int, Int, Word32)
condLo :: (Int, Int, Word32)
condMi :: (Int, Int, Word32)
condPl :: (Int, Int, Word32)
condVs :: (Int, Int, Word32)
condVc :: (Int, Int, Word32)
condHi :: (Int, Int, Word32)
condLs :: (Int, Int, Word32)
condGe :: (Int, Int, Word32)
condLt :: (Int, Int, Word32)
condGt :: (Int, Int, Word32)
condLe :: (Int, Int, Word32)
condAl :: (Int, Int, Word32)
condNv :: (Int, Int, Word32)
condEq = (31, 28, 0x0)
condNe = (31, 28, 0x1)
condCs = (31, 28, 0x2)
condHs = (31, 28, 0x2)
condCc = (31, 28, 0x3)
condLo = (31, 28, 0x3)
condMi = (31, 28, 0x4)
condPl = (31, 28, 0x5)
condVs = (31, 28, 0x6)
condVc = (31, 28, 0x7)
condHi = (31, 28, 0x8)
condLs = (31, 28, 0x9)
condGe = (31, 28, 0xA)
condLt = (31, 28, 0xB)
condGt = (31, 28, 0xC)
condLe = (31, 28, 0xD)
condAl = (31, 28, 0xE)
condNv = (31, 28, 0xF)
encode
  :: Instruction
  -> Word32
encode (Add (Reg r1) (Reg r2) op2)
  = let w1 = concatFields 0
               [ condAl                 
               , (24, 21, 0x04)         
               , (19, 16, regIndex r2)  
               , (15, 12, regIndex r1)  
               ]
        w2 = concatFields 0
               (case op2 of
                  Reg r3
                    -> [(25, 25, 0), (3, 0, regIndex r3)]   
                  Con c1
                    -> [(25, 25, 1), (7, 0, c1)]            
               )
    in w1 .|. w2
encode (And (Reg r1) (Reg r2) op2)
  = let w1 = concatFields 0
               [ condAl                 
               , (24, 21, 0x00)         
               , (19, 16, regIndex r2)  
               , (15, 12, regIndex r1)  
               ]
        w2 = concatFields 0
               (case op2 of
                  Reg r3
                    -> [(3, 0, regIndex r3)]   
                  Con c1
                    -> [(7, 0, c1)]            
               )
    in w1 .|. w2
encode (B (Rel rel))
  = encodeBranch condAl rel
encode (Beq (Rel rel))
  = encodeBranch condEq rel
encode (Bgt (Rel rel))
  = encodeBranch condGt rel
encode (Bic (Reg r1) (Reg r2) op2)
  = let w1 = concatFields 0
               [ condAl                 
               , (24, 21, 0x0E)         
               , (19, 16, regIndex r2)  
               , (15, 12, regIndex r1)  
               ]
        w2 = concatFields 0
               (case op2 of
                  Reg r3
                    -> [(3, 0, regIndex r3)]   
                  Con c1
                    -> [(7, 0, c1)]            
               )
    in w1 .|. w2
encode (Bl (Rel rel))
  = encodeBranch condAl rel .|. concatFields 0 [(24, 24, 1)]
encode (Blt (Rel rel))
  = encodeBranch condLt rel
encode (Bne (Rel rel))
  = encodeBranch condNe rel
encode (Cmp (Reg r1) op2)
  = let w1 = concatFields 0
               [ condAl                  
               , (24, 21, 0x0A)          
               , (15, 12, regIndex r1)   
               ]
        w2 = encodeOp2 op2
    in w1 .|. w2
encode (Eor (Reg r1) (Reg r2) op2)
  = let w1 = concatFields 0
               [ condAl                 
               , (24, 21, 0x01)         
               , (19, 16, regIndex r2)  
               , (15, 12, regIndex r1)  
               ]
        w2 = concatFields 0
               (case op2 of
                  Reg r3
                    -> [(3, 0, regIndex r3)]   
                  Con c1
                    -> [(7, 0, c1)]            
               )
    in w1 .|. w2
encode (Ldmea op1 (Mrg regs))
  = encodeMReg 0x0 op1 regs
encode (Ldr (Reg r1) op2)
  = encodeLdrStr 0x0 0x1 r1 op2
encode (Ldrb (Reg r1) op2)
  = encodeLdrStr 0x0 0x0 r1 op2
encode (Mov (Reg r1) op2)
  = let w1 = concatFields 0
               [ condAl                  
               , (24, 21, 0x0D)          
               , (15, 12, regIndex r1)   
               ]
        w2 = encodeOp2 op2
    in w1 .|. w2
encode (Mul (Reg r1) (Reg r2) (Reg r3))
  = concatFields 0
      [ condAl
      , (19, 16, regIndex r1)
      , (11,  8, regIndex r3)
      , ( 7,  4, 0x09)
      , ( 3,  0, regIndex r2)
      ]
encode (Orr (Reg r1) (Reg r2) op2)
  = let w1 = concatFields 0
               [ condAl                 
               , (24, 21, 0x0C)         
               , (19, 16, regIndex r2)  
               , (15, 12, regIndex r1)  
               ]
        w2 = concatFields 0
               (case op2 of
                  Reg r3
                    -> [(3, 0, regIndex r3)]   
                  Con c1
                    -> [(7, 0, c1)]            
               )
    in w1 .|. w2
encode (Stmea op1 (Mrg regs))
  = encodeMReg 0x1 op1 regs
encode (Str (Reg r1) op2)
  = encodeLdrStr 0x1 0x1 r1 op2
encode (Strb (Reg r1) op2)
  = encodeLdrStr 0x1 0x0 r1 op2
encode (Sub (Reg r1) (Reg r2) op2)
  = let w1 = concatFields 0
               [ condAl                 
               , (24, 21, 0x02)         
               , (19, 16, regIndex r2)  
               , (15, 12, regIndex r1)  
               ]
        w2 = concatFields 0
               (case op2 of
                  Reg r3
                    -> [(3, 0, regIndex r3)]   
                  Con c1
                    -> [(7, 0, c1)]            
               )
    in w1 .|. w2
encode (Swi (Con c))
  = concatFields 0 [ condAl
                   , (27, 24, 0xF)
                   ] .|. c
encodeBranch cond rel
  = concatFields 0 [ cond,
  (27, 25, 0x5)
  ] .|. (to16to32 rel)
to16to32 n = (fromIntegral (fromIntegral n :: Word16) :: Word32)
encodeOp2 op
  = concatFields 0
      (case op of
        Reg r2
          -> [(3, 0, regIndex r2)]    
        Con c1
          -> [ (25, 25, 0x01)         
             , (7, 0, c1)             
             ])
encodeMReg ls op1 regs
  = let w1 = concatFields 0
               [ condAl
               , (27, 25, 0x04)    
             
               , (23, 23, 0x01)    
               , (20, 20, ls)      
               ]
        w2 = concatFields 0
               (case op1 of
                  Aut (Reg reg)
                    -> [ (21, 21, 0x01)   
                       , (19, 16, regIndex reg)
                       ]
                  Reg reg
                    -> [ (19, 16, regIndex reg)
                       ]
               )
        w3 = concatFields 0
               (map (\reg -> let i = fromIntegral (regIndex reg) in (i, i, 1)) regs)
    in w1 .|. w2 .|. w3
encodeLdrStr ls bw r1 op2
  = let w1 = concatFields 0
               [ condAl                 
               , (27, 26, 0x01)         
             
             
               , (22, 22, bw)           
               , (20, 20, ls)           
               , (15, 12, regIndex r1)  
               ]
        w2 = concatFields 0
               (case op2 of
                  Ind r2
                    -> [ 
                     
                         (19, 16, regIndex r2) 
                       ]
                  Bas r2 offset
                    -> [ 
                     
                         (19, 16, regIndex r2) 
                       , (11,  0, offset)      
                       ]
                  Aut (Bas r2 offset)
                    -> [ 
                         (21, 21, 0x01)        
                       , (19, 16, regIndex r2) 
                       , (11,  0, offset)      
                       ]
                  Pos (Ind r2) const
                    -> [ (24, 24, 0x01)        
                       , (21, 21, 0x01)        
                       , (19, 16, regIndex r2) 
                       , (11,  0, const)       
                       ]
               )
    in w1 .|. w2
concatFields
  :: Word32
  -> [(Int, Int, Word32)]
  -> Word32
concatFields word [] = word
concatFields word ((hi, lo, val) : fields)
  = let mask = fromIntegral (2 ^ (hi  lo + 1)  1)
        val' = val .&. mask
    in concatFields (word .|. (val' `shiftL` lo)) fields
regIndex
  :: RegisterName
  -> Word32
regIndex = fromIntegral . (index (R0, CPSR))