module Main where

import Control.Concurrent
import System

-- Parameter:  Version #Iterationen #Producer #Consumer #Kanalgröße
usage :: String
usage = "Usage: main Ver(0=ab,i=pci) #Iter #Prods #Cons bufsize "

main :: IO()
main =  do  args <- getArgs
            let version = read (head args)
                iter    = read (args!!1)
                np      = read (args!!2)
                nc      = read (args!!3)
                size    = read (args!!4)
                call    = case version of 
                                 0 -> ab iter
                                 1 -> pc1 iter 
                                 2 -> pc2 iter np nc
                                 3 -> pc3 iter np nc size
                                 otherwise -> error "version number must be in [0..3]"
            call
            -- damit Programm nicht terminiert, bevor Kindthreads fertig sind
            putStrLn "Bitte Taste druecken!"  
            c <- getChar 
            putStrLn "Ende!"


------------------------------------------------------
-- Erzeugen eines nebenläufigen Threads
------------------------------------------------------

ab :: Int ->  IO ()
ab n =  forkIO (loopy n 'a') >> loopy n 'b'


loop n ch = if n > 0 then putChar ch >> loop (n-1) ch
                     else return ()

loopy n ch = if n > 0 then putChar ch >> yield >> loopy (n-1) ch
                      else return ()


------------------------------------------------------
-- Producer-Consumer mit einzelner Puffervariablen
------------------------------------------------------

-- Channel Variable = Einplatzpuffer 
type SVar a = (MVar a,    -- Producer -> Consumer 
               MVar ())   -- Consumer -> Producer


newSVar :: IO (SVar a)
newSVar = do data_var <- newEmptyMVar
             ack_var  <- newMVar ()
             return (data_var, ack_var)

putSVar :: SVar a -> a -> IO () 
putSVar (dv,av) val = do takeMVar av
                         putMVar dv val

getSVar :: SVar a -> IO a 
getSVar (dv,av)  = do val <- takeMVar dv
                      putMVar av ()
                      return val

-- Producer-Consumer-System
pc1 :: Int -> IO ()                         
pc1 n = do cv <- newSVar 
           forkIO (produce n 0 cv) 
           consume n cv

produce :: Int -> Int -> SVar Int -> IO ()
produce 0 n cv = return ()
produce i n cv = do putSVar cv n
                    putStr ("\nProduced "++show n)
                    produce (i-1) (n+1) cv

consume :: Int -> SVar Int -> IO ()
consume 0 cv = return ()
consume i cv = do val <- getSVar cv 
                  putStr ("\nConsumed " ++ show val)
                  consume (i-1) cv
 
------------------------------------------------------
-- Producer-Consumer mit unbeschränktem Puffer 
------------------------------------------------------

-- Typ Chan a ist in Bibliothek vordefiniert

-- pc2 :: Int -> Int -> Int -> IO()
pc2 i np nc = do c <- newChan
                 multifork np (producer i c 0)
                 multifork nc (consumer i c)    

multifork :: Int -> (Int -> IO()) -> IO()
multifork 0     thr = return ()
multifork (n+1) thr = do forkIO $ thr (n+1)
                         multifork n thr 

producer :: Num a => Int -> Chan (a,Int) -> a -> Int -> IO() 
producer 0 _ _ _     = return ()
producer i ch val id = do writeChan ch (val,id)
                          putStr ("\n Producer " ++ show id ++ " put " ++ show val)
{- Modifiziere Laufzeitverhalten:  
                          writeChan ch (val+1,id)
                          putStr ("\n Producer " ++ show id ++ " put " ++ show (val+1))
                          yield
-}
                          producer (i-1) ch (val+2) id

consumer :: Show a => Int -> Chan a -> Int -> IO () 
consumer 0 _ _   = return ()
consumer i ch id = do val <- readChan ch 
                      putStr ("\n Consumer " ++ show id ++ " got " ++ show val)
                      -- yield
                      consumer (i-1) ch id



------------------------------------------------------
-- Producer-Consumer mit beschränktem Puffer 
------------------------------------------------------

type BChan a = (Chan a, QSem) 

-- Typ QSem ist vordefiniert

newBChan :: Int -> IO (BChan a)
newBChan n = do ch <- newChan 
                qs <- newQSem n
                return (ch,qs) 

putBChan  :: BChan a -> a -> IO ()
putBChan (c,s) x = do waitQSem s 
                      writeChan c x

getBChan  :: BChan a -> IO a
getBChan (c,s) = do x <- readChan c
                    signalQSem s
                    return x

-- Producer-Consumer System 
pc3 i np nc size 
            = do bc <- newBChan size 
                 multifork np (producerb i bc 0)
                 multifork nc (consumerb i bc)


producerb :: Num a => Int -> BChan (a,Int) -> a -> Int -> IO() 
producerb 0 _ _ _     = return ()
producerb i ch val id 
    = do putBChan ch (val,id)
         putStr ("\n Producer " ++ show id ++ " put " ++ show val)
{- Modifiziere Laufzeitverhalten:  
         putBChan ch (val+1,id)
         putStr ("\n Producer " ++ show id ++ " put " ++ show (val+1))
         yield
-}
         producerb (i-1) ch (val+2) id

consumerb :: Show a => Int -> BChan a -> Int -> IO () 
consumerb 0 _ _   = return ()
consumerb i ch id = do val <- getBChan ch 
                       putStr ("\n Consumer " ++ show id ++ " got " ++ show val)
                       -- yield
                       consumerb (i-1) ch id



