{-# LANGUAGE BinaryLiterals #-}

module Data.Digest.DrunkenBishop (drunkenBishop) where

import           Data.Array
import           Data.Bits
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import           Data.Digest.Pure.MD5
import           Data.Word (Word8)

type Board = Array (Int, Int) Int

initialPosition :: (Int, Int)
initialPosition = (8, 4)

mkBoard :: Board
mkBoard = array bounds [ (i, 0) | i <- range bounds ]
  where bounds = ((0, 0), (16, 8))

toDirections :: BS.ByteString -> [Dir]
toDirections bs = case BS.uncons bs of
  Just (x, xs) -> toDir (x `shift` (-6)) :
                  toDir (x `shift` (-4)) :
                  toDir (x `shift` (-2)) :
                  toDir x :
                  toDirections xs
  Nothing      -> []

data Dir = UL | UR | DL | DR deriving (Eq, Show)

toDir :: Word8 -> Dir
toDir x = go (x .&. 0b11)
  where go 0b00 = UL
        go 0b01 = UR
        go 0b10 = DL
        go 0b11 = DR
        go _    = error "unreachable"

move :: Dir -> (Int, Int) -> (Int, Int)
move d (x, y) = snap (go d (x, y))
  where go UL (x, y) = (x-1, y-1)
        go UR (x, y) = (x+1, y-1)
        go DL (x, y) = (x-1, y+1)
        go DR (x, y) = (x+1, y+1)
        snap (x, y) = (clamp x 0 16, clamp y 0 8)

clamp :: Ord a => a -> a -> a -> a
clamp n low high
  | n < low   = low
  | n > high  = high
  | otherwise = n

toChar :: Int -> Char
toChar n = case n of
  00 -> ' '
  01 -> '.'
  02 -> 'o'
  03 -> '+'
  04 -> '='
  05 -> '*'
  06 -> 'B'
  07 -> 'O'
  08 -> 'X'
  09 -> '@'
  10 -> '%'
  11 -> '&'
  12 -> '#'
  13 -> '/'
  14 -> '^'
  15 -> 'S'
  16 -> 'E'
  _  -> '?'

runSteps :: (Int, Int) -> [Dir] -> Board -> Board
runSteps pos [] b = b // [(pos, 16)]
runSteps pos (d:ds) b =
  let newPos = move d pos
  in if b ! pos == 15
    then runSteps newPos ds b
    else runSteps newPos ds (b // [(newPos, clamp ((b ! newPos) + 1) 0 14)])

drunkenBishop :: BSL.ByteString -> String
drunkenBishop bs = render (runSteps (8, 4) (toDirections h) mkBoard // [((8, 4), 15)])
  where render b = unlines [ foldr (:) "" [ toChar (b ! (x, y)) | x <- [0..16] ]
                           | y <- [0..8]
                           ]
        h = md5DigestBytes (md5 bs)
