-- Each new term in the Fibonacci sequence is generated by adding the previous
-- two terms. By starting with 1 and 2, the first 10 terms will be:
-- 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
-- By considering the terms in the Fibonacci sequence whose values do not
-- exceed four million, find the sum of the even-valued terms.

{-# LANGUAGE ConstraintKinds#-}
{-# LANGUAGE FlexibleContexts#-}
--

import Control.Monad.State
import Control.Monad.Reader
import Control.Lens
import Data.Monoid
import System.Environment

import Types

type Program state config a = StateT state (ReaderT config IO) a

runprogram :: Program s c a -> s -> c -> IO (a, s)
runprogram p s c = runReaderT (runStateT p s) c

initialstate :: FibSumState
initialstate = FibSumState 0 1 1

getconfig :: IO FibSumConfig
getconfig = do
  limit' <- getArgs
  pure $ FibSumConfig $ read $ head limit'

type MonadFibSum m =
  ( MonadState FibSumState m
  , MonadReader FibSumConfig m
  , MonadIO m
  )

fibsum :: MonadFibSum m => m ()
fibsum = do
  prevfib' <- use prevfib
  fsum <- use currentsum
  currentfib' <- use currentfib
  itemlimit <- view limit
  unless (currentfib' > itemlimit) $ do
    -- sum only when even
    when (currentfib' `mod` 2 == 0) $ currentsum += currentfib'
    currentfib += prevfib'
    prevfib += (currentfib' - prevfib')
    fibsum
  when (currentfib' > itemlimit) $ liftIO . putStrLn $
    "Total sum of even numbers in Fibonacci sequence up to " <>
    (show itemlimit) <> ": " <> show fsum


main :: IO ()
main = do
  config <- getconfig
  void $ runprogram fibsum initialstate config
