-- | Notes taken by Emilija Rimšelytė
module Lessons.Lesson06 where

import Control.Concurrent (newChan, threadDelay, forkIO,readChan, writeChan, Chan)
import System.Random
import Control.Concurrent.STM
    ( TVar, writeTVar, newTVarIO, modifyTVar )
import Control.Monad.STM
import Control.Concurrent.STM.TVar (readTVar, readTVarIO)
import Control.Concurrent.Async
import Control.Monad (when)

-- | Entry point of a Haskell program.
-- 'putStrLn' performs an effect: it prints to the terminal.
-- In Haskell, effects live in IO — they’re not just values, they’re actions.
main :: IO ()
main :: IO ()
main = String -> IO ()
putStrLn String
"Hello, Haskell!"

-- | Reads a line from the terminal.
-- This is an IO action — it doesn’t give you a String directly,
-- it gives you a computation that will produce a String when run.
main' :: IO String
main' :: IO String
main' = IO String
getLine

-- | Reads and prints a line.
-- Demonstrates how to extract a value from IO using '<-' inside a 'do' block.
-- You can't escape IO in pure code — but inside 'do', you can unwrap and use.
m :: IO ()
m :: IO ()
m = do
    String
line <- IO String
getLine
    String -> IO ()
putStrLn String
line

-- | Asks the user for their name.
-- This is interactive, so it lives in IO.
-- Side effects like printing and reading are sequenced here.
queryName :: IO String
queryName :: IO String
queryName = do
    String -> IO ()
putStrLn String
"What is your name?"
    IO String
getLine

-- | Combines IO (input/output) with pure logic.
-- The greeting is pure, but getting the name is not.
aGame :: IO ()
aGame :: IO ()
aGame = do
    String
name <- IO String
queryName
    let salute :: String
salute = String
"Hello, " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"!"
    String -> IO ()
putStrLn String
salute


-- | This won’t compile, as you can see,  
-- IO and String can’t mix so free.
-- pureF :: String -> String
-- pureF a = queryName ++ a

-- | Mixes IO with pure string manipulation.
-- You extract the name, then glue it tight,
-- But wrap it in IO to make it right.
pureF :: String -> IO String
pureF :: String -> IO String
pureF String
a = do
    String
n <- IO String
queryName
    String -> IO String
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (String -> IO String) -> String -> IO String
forall a b. (a -> b) -> a -> b
$ String
n String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
a

-- | Returns a constant string wrapped in IO.
pureF' :: IO String
pureF' :: IO String
pureF' = String -> IO String
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return String
"labas"

-- | Waits 10 seconds, then prints "Hi".
-- Demonstrates time delay — an effect, so it’s in IO.
action :: IO ()
action :: IO ()
action = do
    Int -> IO ()
threadDelay Int
10000000
    String -> IO ()
putStrLn String
"Hi"

-- | Starts two concurrent threads using 'forkIO'.
-- Each runs 'action' independently.
-- Threads in Haskell are lightweight — managed by the runtime, not the OS.
threading :: IO ()
threading :: IO ()
threading = do
    IO () -> IO ThreadId
forkIO IO ()
action
    IO () -> IO ThreadId
forkIO IO ()
action
    String -> IO ()
putStrLn String
"!"

-- | Channels let threads communicate safely.
-- Writes to the channel, after a pause,
-- Communication between threads — that’s the cause.
actionC :: Chan String -> IO ()
actionC :: Chan String -> IO ()
actionC Chan String
ch = do
    Int -> IO ()
threadDelay Int
10000000
    Chan String -> String -> IO ()
forall a. Chan a -> a -> IO ()
writeChan Chan String
ch String
"Hi"

-- | Demonstrates inter-thread communication.
-- Two threads write to the same channel.
-- Main thread reads both messages and prints them.
threading' :: IO ()
threading' :: IO ()
threading' = do
    Chan String
ch <- IO (Chan String)
forall a. IO (Chan a)
newChan
    IO () -> IO ThreadId
forkIO (IO () -> IO ThreadId) -> IO () -> IO ThreadId
forall a b. (a -> b) -> a -> b
$ Chan String -> IO ()
actionC Chan String
ch
    IO () -> IO ThreadId
forkIO (IO () -> IO ThreadId) -> IO () -> IO ThreadId
forall a b. (a -> b) -> a -> b
$ Chan String -> IO ()
actionC Chan String
ch
    String
v1 <- Chan String -> IO String
forall a. Chan a -> IO a
readChan Chan String
ch
    String
v2 <- Chan String -> IO String
forall a. Chan a -> IO a
readChan Chan String
ch
    String -> IO ()
putStrLn (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
v1 String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
", " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
v2

-- | Delayed computation that returns a string.
-- Used with 'async' to run in parallel.
actionA :: String -> IO String
actionA :: String -> IO String
actionA String
v = do
    Int -> IO ()
threadDelay Int
5000000
    String -> IO String
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return String
v

-- | Runs two async actions concurrently.
-- Waits for both to finish, then combines results.
threading'' :: IO String
threading'' :: IO String
threading'' = do
    Async String
a1 <- IO String -> IO (Async String)
forall a. IO a -> IO (Async a)
async (IO String -> IO (Async String)) -> IO String -> IO (Async String)
forall a b. (a -> b) -> a -> b
$ String -> IO String
actionA String
"First"
    Async String
a2 <- IO String -> IO (Async String)
forall a. IO a -> IO (Async a)
async (IO String -> IO (Async String)) -> IO String -> IO (Async String)
forall a b. (a -> b) -> a -> b
$ String -> IO String
actionA String
"Second"
    String
v1 <- Async String -> IO String
forall a. Async a -> IO a
wait Async String
a1
    String
v2 <- Async String -> IO String
forall a. Async a -> IO a
wait Async String
a2
    String -> IO String
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (String -> IO String) -> String -> IO String
forall a b. (a -> b) -> a -> b
$ String
v1 String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
v2

-- | Transfers money between two accounts using STM.
-- STM = Software Transactional Memory.
-- It’s atomic, composable, and avoids locks.

-- If balance is low, retries the flow,
-- Wait until there's cash to go.
transfer :: TVar Integer -> TVar Integer -> Integer -> STM ()
transfer :: TVar Integer -> TVar Integer -> Integer -> STM ()
transfer TVar Integer
accA TVar Integer
accB Integer
amount = do
    Integer
a <- TVar Integer -> STM Integer
forall a. TVar a -> STM a
readTVar TVar Integer
accA
    Integer
b <- TVar Integer -> STM Integer
forall a. TVar a -> STM a
readTVar TVar Integer
accB
    let newA :: Integer
newA = Integer
a Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
amount
    Bool -> STM () -> STM ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Integer
newA Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
< Integer
0) STM ()
forall a. STM a
retry
    TVar Integer -> Integer -> STM ()
forall a. TVar a -> a -> STM ()
writeTVar TVar Integer
accA Integer
newA
    TVar Integer -> Integer -> STM ()
forall a. TVar a -> a -> STM ()
writeTVar TVar Integer
accB (Integer
b Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
amount)

-- | Demonstrates STM in action.
-- One thread tries to transfer too much,
-- Another adds funds — STM retries until it succeeds.

-- STM waits, but never breaks,
-- One thread gives, the other takes.
runTx :: IO ()
runTx :: IO ()
runTx = do
    TVar Integer
a <- Integer -> IO (TVar Integer)
forall a. a -> IO (TVar a)
newTVarIO Integer
50
    TVar Integer
b <- Integer -> IO (TVar Integer)
forall a. a -> IO (TVar a)
newTVarIO Integer
100
    Async ()
z <- IO () -> IO (Async ())
forall a. IO a -> IO (Async a)
async (IO () -> IO (Async ())) -> IO () -> IO (Async ())
forall a b. (a -> b) -> a -> b
$ STM () -> IO ()
forall a. STM a -> IO a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$ TVar Integer -> TVar Integer -> Integer -> STM ()
transfer TVar Integer
a TVar Integer
b Integer
51
    STM () -> IO ()
forall a. STM a -> IO a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$ TVar Integer -> (Integer -> Integer) -> STM ()
forall a. TVar a -> (a -> a) -> STM ()
modifyTVar TVar Integer
a (Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+Integer
10)
    ()
_ <- Async () -> IO ()
forall a. Async a -> IO a
wait Async ()
z
    Integer
ar <- TVar Integer -> IO Integer
forall a. TVar a -> IO a
readTVarIO TVar Integer
a
    Integer
br <- TVar Integer -> IO Integer
forall a. TVar a -> IO a
readTVarIO TVar Integer
b
    String -> IO ()
putStrLn (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"a=" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Integer -> String
forall a. Show a => a -> String
show Integer
ar String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
", b=" String -> String -> String
forall a. [a] -> [a] -> [a]
++  Integer -> String
forall a. Show a => a -> String
show Integer
br