{-# LANGUAGE LambdaCase #-}
module Alien.Interaction where
import Control.Applicative
import Control.Monad
import Control.Monad.Trans.State
import Data.Bits
import Data.List
import Data.Maybe
import Alien.FFI
modulate :: IntList -> [Bool]
modulate (LInt n)     = (if n >= 0 then [False, True] else [True, False])
                        ++ replicate len True
                        ++ [False]
                        ++ map (testBit absn) (reverse [0 .. 4*len-1])
                        where
                          absn = abs n
                          len = head $ dropWhile (\l -> 16^l <= absn) [0..]
modulate LNil         = [False, False]
modulate (LCons x xs) = [True, True] ++ modulate x ++ modulate xs
demodulate :: [Bool] -> IntList
demodulate = fromMaybe (error "Demodulate parse error") . evalStateT (go <* end)
  where
    getBit = StateT uncons
    end = StateT $ \case
      [] -> pure ((), [])
      _  -> empty
    go = liftA2 (,) getBit getBit >>= \case
      (False, False) -> pure LNil
      (True, True) -> LCons <$> go <*> go
      (sign, _) -> LInt <$> do
        let getLen = getBit >>= \b -> if b then succ <$> getLen else pure 0
        len <- getLen
        mantissa <- replicateM (4 * len) getBit
        pure $ (if sign then negate else id) $ foldl' (\x y -> 2*x + if y then 1 else 0) 0 mantissa
makeClick
  :: Monad m
  => (IntList -> m IntList) 
  -> (AlienState -> m ()) 
  -> alienValue
  -> AlienState
  -> (Integer, Integer) 
  -> m (AlienState, [Drawing])
makeClick send step interactor stt (x, y) = go stt (LCons (LInt x) (LInt y))
  where
    go st input = step st >> case interactWith interactor st input of
      (st', Left request) -> go st' =<< send request
      (st', Right pictures) -> pure (st', pictures)
runInteraction
  :: Monad m
  => (IntList -> m IntList) 
  -> ([Drawing] -> m (Integer, Integer)) 
  -> (AlienState -> m ()) 
  -> alienValue
  -> AlienState 
  -> m ()
runInteraction send click step interactor initState = click [] >>= goClick initState
  where
    goClick st (x, y) = go st (LCons (LInt x) (LInt y))
    go st input = step st >> case interactWith interactor st input of
      (st', Left request) -> send request >>= go st'
      (st', Right pictures) -> click pictures >>= goClick st'