import System.IO(openFile, IOMode(ReadMode), hGetContents, hClose, hPutStrLn, stderr)
import System.Exit(exitFailure)
import System.Environment(getArgs, getProgName)
import Control.Parallel.Strategies

import Data.Map as M
import Data.List as L

-- helper function that convert string to integer
rInt :: String -> Integer
rInt = read

main :: IO ()
main = do
  args <- getArgs
  case args of
    [filename, k] -> do -- Expects one filename
      h <- openFile filename ReadMode
      contents <- hGetContents h -- Read the file
      let w = Prelude.map rInt $ words $ contents
      let len = length w
      let list = counter w
      let chunks = [1..(2^len)]
      print $ L.foldl' (+) 0 (parAlgo (rInt k) chunks list) -- WHNF
      hClose h
    _ -> do 
      pn <- getProgName -- Usage message
      hPutStrLn stderr $ "Usage: "++pn++" <Filename> <Sum> <Size of Set>"
      exitFailure -- Terminate the program

-- parallel fold (actually its performance is worse than non-parallel)
{-
pfold :: (Num a, Enum a) => (a -> a -> a) -> [a] -> a
pfold _ [x] = x
pfold mapp xs  = (ys `par` zs) `pseq` (ys `mapp` zs) where
  len = length xs
  (ys', zs') = Prelude.splitAt (len `div` 2) xs
  ys = pfold mapp ys'
  zs = pfold mapp zs'
-}

-- update map to get index of nums
counter :: [Integer] -> Map Integer Integer
counter list = M.fromList (zip [1..] list)

-- helper function that converts decimal to binary list
helper :: Integer -> [Int]
helper 0 = []
helper n = 
        let (q, r) = n `divMod` 2 
        in (fromIntegral r) : helper q

-- get real value from Maybe
remov :: Maybe Integer -> Integer
remov (Just a) = a
remov Nothing = 0

-- calculate sum according to the binary format
calSum :: [Int] -> Map Integer Integer -> Integer -> Integer -> Integer
calSum [] _ x _ = x
calSum (y:ys) list x cnt
      | y == 0 = calSum ys list x (cnt + 1)
      | otherwise = calSum ys list (x + (remov z)) (cnt + 1)
          where z = M.lookup cnt list

parMap_ :: Map Integer Integer -> Integer -> Integer -> Integer
parMap_ list k x 
      | calSum (helper x) list (0::Integer) (1::Integer) == k = 1
      | otherwise = 0
  
-- do MapReduce
parAlgo :: Integer -> [Integer] -> Map Integer Integer -> [Integer]
parAlgo k nums list = 
  let bs = Prelude.map (parMap_ list k) nums
      cs = bs `using` (parListChunk 16384) rseq
  in cs

  
-- algorithm part
{-
binaryEncoding :: Integer -> Integer -> [Integer] -> Integer
binaryEncoding k curSum [] 
      | k == curSum = 1
      | otherwise = 0
binaryEncoding k curSum (x:xs) = binaryEncoding k (curSum + x) xs + (binaryEncoding k curSum xs)
-}
