{-# LANGUAGE MagicHash, UnboxedTuples #-}

-- This test is adapted from setnumcapabilities001.

import GHC.Conc hiding (threadWaitRead, threadWaitWrite)
import GHC.Exts
import GHC.IO.Encoding
import System.Environment
import System.IO
import Control.Monad
import Text.Printf
import Data.Time.Clock
import Control.DeepSeq

import System.Posix.IO
import System.Posix.Types
import Control.Concurrent
import Control.Exception

passTheParcel :: Int -> IO (IO ())
passTheParcel n = do
  pipes@(p1 : rest) <- forM [0..n-1] $ \_ -> createPipe
  rs@((_,tid1) : _) <- forM (pipes `zip` (rest ++ [p1])) $ \((readfd, _), (_, writefd)) -> do
    let
      read = fdRead readfd $ fromIntegral 1
      write = fdWrite writefd
    mv <- newEmptyMVar
    tid <- forkIO $ let
      loop = flip catch (\(x :: IOException) -> pure ()) $ forever $ do
        threadWaitRead readfd
        (s, _) <- read
        threadWaitWrite writefd
        write s
      cleanup = do
        closeFdWith closeFd readfd
        closeFdWith closeFd writefd
        putMVar mv ()
      in loop `finally` cleanup
    pure (mv, tid)

  let
    cleanup = do
      killThread tid1
      forM_ rs $ \(mv, _) -> takeMVar mv

  fdWrite (snd p1) "a"
  pure cleanup


main = do
  setLocaleEncoding latin1 -- fdRead and fdWrite depend on the current locale
  [n,q,t,z] <- fmap (fmap read) getArgs
  cleanup_ptp <- passTheParcel z
  t <- forkIO $ do
    forM_ (cycle ([n,n-1..1] ++ [2..n-1])) $ \m -> do
      setNumCapabilities m
      threadDelay t
  printf "%d\n" (nqueens q)
  cleanup_ptp
  killThread t
      -- If we don't kill the child thread, it might be about to
      -- call setNumCapabilities() in C when the main thread exits,
      -- and chaos can ensue.  See #12038

nqueens :: Int -> Int
nqueens nq = length (pargen 0 [])
 where
    safe :: Int -> Int -> [Int] -> Bool
    safe x d []    = True
    safe x d (q:l) = x /= q && x /= q+d && x /= q-d && safe x (d+1) l

    gen :: [[Int]] -> [[Int]]
    gen bs = [ (q:b) | b <- bs, q <- [1..nq], safe q 1 b ]

    pargen :: Int -> [Int] -> [[Int]]
    pargen n b
       | n >= threshold = iterate gen [b] !! (nq - n)
       | otherwise      = concat bs
       where bs = map (pargen (n+1)) (gen [b]) `using` parList rdeepseq

    threshold = 3

using :: a -> Strategy a -> a
x `using` strat = runEval (strat x)

type Strategy a = a -> Eval a

newtype Eval a = Eval (State# RealWorld -> (# State# RealWorld, a #))

runEval :: Eval a -> a
runEval (Eval x) = case x realWorld# of (# _, a #) -> a

instance Functor Eval where
  fmap = liftM

instance Applicative Eval where
  pure x = Eval $ \s -> (# s, x #)
  (<*>)  = ap

instance Monad Eval where
  return = pure
  Eval x >>= k = Eval $ \s -> case x s of
                                (# s', a #) -> case k a of
                                                      Eval f -> f s'

parList :: Strategy a -> Strategy [a]
parList strat = traverse (rparWith strat)

rpar :: Strategy a
rpar  x = Eval $ \s -> spark# x s

rseq :: Strategy a
rseq x = Eval $ \s -> seq# x s

rparWith :: Strategy a -> Strategy a
rparWith s a = do l <- rpar r; return (case l of Lift x -> x)
  where r = case s a of
              Eval f -> case f realWorld# of
                          (# _, a' #) -> Lift a'

data Lift a = Lift a

rdeepseq :: NFData a => Strategy a
rdeepseq x = do rseq (rnf x); return x
