--- Code by Dan Doel - https://github.com/unisonweb/unison/issues/1055#issuecomment-565753502
{-# language BangPatterns #-}

module POC (C(..), eval0, setup) where

import Control.Monad.Primitive

import qualified Data.Vector.Generic.Mutable as GM
import qualified Data.Vector.Unboxed.Mutable as UM

data Args
  = Arg1 !Int
  | Arg2 !Int !Int
  -- | ArgN {-# UNPACK #-} !(U.Vector Int)

data C
  = App !Int !Closure !Args
  | Lit !Int
  | Dec !Int
  | Add !Int !Int
  | Prn !Int
  | Let !C !C
  | Nop
  | Brn !Int !C !C
  | Ret !Int !Int
  | AddThn !Int !Int !C
  | LitThn !Int !C
  | DecThn !Int !C

data Closure
  = Ref !Int

data K = E | !C :< !K

type Stk = UM.MVector (PrimState IO) Int
type SP = Int

eval0 :: C -> IO ()
eval0 c = do
  stk <- GM.new 512
  eval stk 512 E c

resolve :: Closure -> C
resolve (Ref 0) = loop
resolve (Ref 1) = fib
resolve _ = error "Unknown reference"

eval :: Stk -> SP -> K -> C -> IO ()
eval !stk !sp !k  (Dec i) = do
  x <- GM.unsafeRead stk (sp + i)
  let sp' = sp-1
  GM.unsafeWrite stk sp' (x-1)
  jump stk sp' k
eval !stk !sp !k  (Add i j) = do
  x <- GM.unsafeRead stk (sp + i)
  y <- GM.unsafeRead stk (sp + j)
  let sp' = sp-1
  GM.unsafeWrite stk sp' (x+y)
  jump stk sp' k
eval !stk !sp !k  (Prn i) = do
  x <- GM.unsafeRead stk (sp + i)
  print x
  jump stk sp k
eval !stk !sp !k  (App lo f args) = do
  sp' <- moveArgs stk sp lo args
  eval stk sp' k (resolve f)
eval !stk !sp !k  (Lit n) = do
  let sp' = sp-1
  GM.unsafeWrite stk sp' n
  jump stk sp' k
eval !stk !sp !k  (Let c d) = do
  eval stk sp (d :< k) c
eval !stk !sp !k  (Brn i t f) = do
  b <- GM.unsafeRead stk (sp+i)
  if b == 0
    then eval stk sp k t
    else eval stk sp k f
eval !stk !sp !k  (Ret lo i) = do
  let sp' = sp+lo-1
  GM.unsafeRead stk (sp+i) >>= GM.unsafeWrite stk sp'
  jump stk sp' k
eval !stk !sp !k  (DecThn i d) = do
  x <- GM.unsafeRead stk (sp + i)
  let sp' = sp-1
  GM.unsafeWrite stk sp' (x-1)
  eval stk sp' k d
eval !stk !sp !k  (AddThn i j d) = do
  x <- GM.unsafeRead stk (sp + i)
  y <- GM.unsafeRead stk (sp + j)
  let sp' = sp-1
  GM.unsafeWrite stk sp' (x+y)
  eval stk sp' k d
eval !stk !sp !k  (LitThn n d) = do
  let sp' = sp-1
  GM.unsafeWrite stk sp' n
  eval stk sp' k d
eval !stk !sp !k Nop = jump stk sp k

jump :: Stk -> SP -> K -> IO ()
jump _   _  E = return ()
jump stk sp (d :< k) = eval stk sp k d
{-# inline jump #-}

moveArgs :: Stk -> SP -> Int -> Args -> IO Int
moveArgs !stk !sp !lo (Arg1 i) = do
  let sp' = sp + lo - 1
  GM.unsafeRead stk (sp + i) >>= GM.unsafeWrite stk sp'
  return sp'
moveArgs !stk !sp !lo (Arg2 i j) = do
  x <- GM.unsafeRead stk (sp + i)
  y <- GM.unsafeRead stk (sp + j)
  let sp' = sp + lo - 2
  GM.unsafeWrite stk sp' x
  GM.unsafeWrite stk (sp'+1) y
  return sp'
{-# inline moveArgs #-}

loop :: C
loop = Brn 0 fls tru
  where
  fls = Ret 2 1
  tru = Let (Add 0 1) $ Let (Dec 1) $ App 4 (Ref 0) (Arg2 0 1)

fib :: C
fib = Brn 0
        (Let (Lit 0) (Ret 2 0)) $
         Let (Dec 0) $
         Brn 0
           (Let (Lit 1) (Ret 3 0)) $
            Let (App 0 (Ref 1) (Arg1 0)) $
            Let (Dec 1) $
            Let (App 0 (Ref 1) (Arg1 0)) $
            Let (Add 0 2) $
            Ret 6 0

setup :: C
setup = Let (Lit 0) $ Let (Lit 100000000) $ Let (App 2 (Ref 0) (Arg2 0 1)) $ (Prn 0)
-- setup = Let (Lit 36) $ Let (App 1 (Ref 1) (Arg1 0)) $ Prn 0