{-# Language DeriveTraversable, Safe #-}
{-|
Module      : Intcode.Opcode
Description : Intcode opcodes
Copyright   : (c) Eric Mertens, 2019
License     : ISC
Maintainer  : emertens@gmail.com

This module provides a representation of the intcode machine's opcodes.

Opcodes are parameterized over their parameters. This allows the
implementation to store both parameter modes and resolved parameter
pointers in the same constructors.

-}
module Intcode.Opcode
  (
  -- * Types
  Opcode(..), Mode(..),

  -- * Decoder
  decode,
  ) where

------------------------------------------------------------------------
-- Opcode decoder
------------------------------------------------------------------------

-- | Parameter modes
data Mode
  = Abs -- ^ absolute position
  | Imm -- ^ immediate
  | Rel -- ^ relative position
  deriving (Eq, Ord, Read, Show)

-- | Opcodes parameterized over argument representations.
data Opcode a
  = Add !a !a !a -- ^ __addition:__        @c = a + b@
  | Mul !a !a !a -- ^ __multiplication:__  @c = a * b@
  | Inp !a       -- ^ __input:__           @a = input()@
  | Out !a       -- ^ __output:__          @output(a)@
  | Jnz !a !a    -- ^ __jump-if-true:__    @if a then goto b@
  | Jz  !a !a    -- ^ __jump-if-false:__   @if !a then goto b@
  | Lt  !a !a !a -- ^ __less-than:__       @c = a < b@
  | Eq  !a !a !a -- ^ __equals:__          @c = a == b@
  | Arb !a       -- ^ __adjust-rel-base:__ @rel += a@
  | Hlt          -- ^ __halt__
  deriving (Eq, Ord, Read, Show, Functor, Foldable)

-- | Decode an instruction to determine the opcode and parameter modes.
--
-- >>> decode 1002
-- Just (Mul Abs Imm Abs)
decode :: Int {- ^ opcode -} -> Maybe (Opcode Mode)
decode n =
  case n `rem` 100 of
    1  -> fill (Add 1 2 3)
    2  -> fill (Mul 1 2 3)
    3  -> fill (Inp 1    )
    4  -> fill (Out 1    )
    5  -> fill (Jnz 1 2  )
    6  -> fill (Jz  1 2  )
    7  -> fill (Lt  1 2 3)
    8  -> fill (Eq  1 2 3)
    9  -> fill (Arb 1    )
    99 -> fill Hlt
    _  -> Nothing
  where
    fill = traverse (parameter n)
{-# INLINABLE decode #-}

-- | Compute the parameter mode for an argument at a given position.
parameter ::
  Int {- ^ opcode   -} ->
  Int {- ^ position -} ->
  Maybe Mode
parameter n i =
  case digit (i+1) n of
    0 -> Just Abs
    1 -> Just Imm
    2 -> Just Rel
    _ -> Nothing

-- | Arguments visited from left to right.
instance Traversable Opcode where
  {-# INLINE traverse #-}
  traverse f o =
    case o of
      Add x y z -> Add <$> f x <*> f y <*> f z
      Mul x y z -> Mul <$> f x <*> f y <*> f z
      Inp x     -> Inp <$> f x
      Out x     -> Out <$> f x
      Jnz x y   -> Jnz <$> f x <*> f y
      Jz  x y   -> Jz  <$> f x <*> f y
      Lt  x y z -> Lt  <$> f x <*> f y <*> f z
      Eq  x y z -> Eq  <$> f x <*> f y <*> f z
      Arb x     -> Arb <$> f x
      Hlt       -> pure Hlt

-- | Extract the ith digit from a number.
--
-- >>> digit 0 2468
-- 8
-- >>> digit 3 2468
-- 2
-- >>> digit 4 2468
-- 0
digit :: Int {- ^ position -} -> Int {- ^ number -} -> Int {- ^ digit -}
digit i x = x `quot` (10^i) `rem` 10