{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE RecordWildCards #-}
module Command.Betris (command, Options(..), betris) where

import Control.Concurrent (forkIO, threadDelay)
import Control.Concurrent.STM.TChan
import Control.Lens hiding (argument)
import Control.Monad (forever)
import Control.Monad.STM (atomically)
import Data.Char (chr)
import Data.IORef
import qualified Data.Map as Map
import Data.Semigroup ((<>))
import Data.Time.Units
import Data.Version (showVersion)
import Game.Tetris
import Graphics.Vty.Config (userConfig)
import Graphics.Vty.Input hiding (Event)
import qualified Graphics.Vty.Input as Vty (Event)
import Linear.V2 (V2(..), _x)
import Paths_betris (version)
import Prelude hiding (Left, Right)
import Options.Applicative hiding (command, (<|>))
import System.Console.ANSI
import System.IO (hFlush, stdout)

command :: Parser (IO ())
command = betris <$> (versionOption <*> programOptions)

newtype Options = Options { initialDelay :: Millisecond } deriving (Eq, Show)

programOptions :: Parser Options
programOptions = Options <$> initialDelayOption

data Event e = Tick | Ev e deriving (Eq, Read, Show, Functor)

betris :: Options -> IO ()
betris Options{..} = do
  input <- inputForConfig =<< userConfig
  chan <- newTChanIO
  game <- initGame 0
  speed <- newIORef $ fromIntegral $ toMicroseconds initialDelay

  _ <- forkIO . forever . atomically $
    readTChan (_eventChannel input) >>= writeTChan chan . Ev
  _ <- forkIO $ forever $ do
    readIORef speed >>= threadDelay
    modifyIORef speed (subtract 500)
    atomically $ writeTChan chan Tick

  _ <- play chan game
  shutdownInput input
  putStrLn ""

play :: TChan (Event Vty.Event) -> Game -> IO Game
play chan tetris
  | isGameOver tetris = pure tetris
  | otherwise = do
    putStr $ "\r"
          <> emboss tetris
          <> " [" <> show (tetris ^. score) <> "]"
          <> clearFromCursorToLineEndCode
    hFlush stdout
    atomically (readTChan chan) >>= \case
      Tick                 -> play chan =<< timeStep tetris
      Ev (EvKey KLeft [])  -> play chan $ hardDrop tetris
      Ev (EvKey KUp [])    -> play chan $ Left `shift` tetris
      Ev (EvKey KDown [])  -> play chan $ Right `shift` tetris
      Ev (EvKey KEnter []) -> play chan $ rotate tetris
      Ev (EvKey KEsc [])   -> pure tetris
      _                    -> play chan tetris

emboss :: Game -> String
emboss game = map go [1, 3 .. boardHeight] where
  go y = chr $ foldr (f y) 0x2800 [((0,0),1), ((1,0), 2), ((2,0), 4)
                                  ,((0,1),8), ((1,1),16), ((2,1),32)
                                  ,((3,0),64),((3,1),128)]
  f y ((x',y'), v) a = case Map.lookup (V2 (x+x') (y+y')) fullBoard of
    Just _ -> a + v
    _ -> a
  x = minimum $ (boardWidth - 3) : map (^. _x) (coords $ game ^. block)
  fullBoard = game ^. board <> blk (game ^. block)
  blk b = Map.fromList $ map (, b ^. shape) $ coords b

initialDelayOption :: Parser Millisecond
initialDelayOption = option auto $
    long "initial-delay"
 <> short 'i'
 <> metavar "DURATION"
 <> value (fromMicroseconds 1000000)
 <> showDefault
 <> help "Initial delay"

versionOption :: Parser (a -> a)
versionOption = infoOption (showVersion version) $
    long "version"
 <> help "Show version"