-- Brute Force Polynomial Multiplication in Parallelization with MapReduce

module BruteForce.MapReduce
(
  map_reduce
, mult_polys
) where

import qualified Data.Map as M
import Control.Parallel(pseq)
import Control.Parallel.Strategies(NFData, parMap, rdeepseq, parListChunk, runEval)

{-
map_reduce :: (NFData a, NFData b, NFData c, NFData d)
              => Int -> (a -> b) -> ([b] -> [c]) -> (c -> d) -> [a] -> [d]
map_reduce depth mapper shuffle reducer input =
  let par_size = (length input) `div` depth in
  let mapper_result = runEval $ parListChunk par_size rdeepseq (map mapper input) in
  let shuffle_result = shuffle mapper_result in
  let reducer_result = runEval $ parListChunk par_size rdeepseq (map reducer shuffle_result) in
  reducer_result
-}
map_reduce :: (NFData a, NFData b, NFData c, NFData d)
              => Int -> (a -> b) -> ([b] -> [c]) -> (c -> d) -> [a] -> [d]
map_reduce _ mapper shuffle reducer input = 
  let mapper_result = parMap rdeepseq mapper input in
  let shuffle_result = shuffle mapper_result in
  let reduce_result = parMap rdeepseq reducer shuffle_result in
  mapper_result `pseq` reduce_result

preprocess_polys :: [Double] -> [Double] -> [(Int, Int, Double, Double)]
preprocess_polys l1 l2 = [(i1, i2, f1, f2) | (i1, f1) <- i_l1, (i2, f2) <- i_l2]
  where
    i_l1 = zip [1..(length l1)] l1
    i_l2 = zip [1..(length l2)] l2

mult_poly_mapper :: (Int, Int, Double, Double) -> (Int, [Double])
mult_poly_mapper (i1, i2, f1, f2) = (i1 + i2, [f1 * f2])

-- O(nlogn) overhead
mult_poly_shuffle :: [(Int, [Double])] -> [(Int, [Double])]
mult_poly_shuffle l = M.toList $ M.fromListWith (++) l

-- since shuffle_result comes from Map, it is already sorted
-- and therefore index can be discarded
mult_poly_reducer :: (Int, [Double]) -> Double
mult_poly_reducer (_, l) = sum l

mult_polys :: Int -> [Double] -> [Double] -> [Double]
mult_polys depth x y =
    map_reduce depth mult_poly_mapper mult_poly_shuffle mult_poly_reducer
  $ preprocess_polys x y
