packages feed

coin-1.0: src/Coin/UI/MainState.hs

{-
 *  Programmer:	Piotr Borek
 *  E-mail:     piotrborek@op.pl
 *  Copyright 2016 Piotr Borek
 *
 *  Distributed under the terms of the GPL (GNU Public License)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-}

module Coin.UI.MainState (
    MainState (..),
    mainStateNew,
    mainStateReadPropertiesAction,
    mainStateReadPropertiesFromDisk,
    propertyRead,
    propertyRead2,
    propertyRead3,
    propertyRead4,
    mainStateSavePropertiesOnDisk,
    mainStateSavePropertiesAction,
    propertyInsert,
    propertyInsertMaybe,
--
    P.PropertyMap,
    module Coin.UI.Utils.Observable
) where

import qualified Data.Binary as Binary
import qualified Database.Persist as DB

import qualified Coin.Utils.PropertyMap as P

import Control.Monad.Reader
import Control.Monad.State.Strict
import Data.IORef
import Data.Foldable
import System.Directory

import Coin.DB.Tables
import Coin.UI.Utils.Observable
import Coin.Config.Dirs

data MainState = MainState {
    mainStateSelectedAccountName :: Observable String,
    mainStateTagsUpdated         :: Observable [DB.Entity TagsTable],
    mainStateAccountsUpdated     :: Observable [DB.Entity AccountsTable],
    mainStatePropertySaveActions :: IORef [(String, StateT P.PropertyMap IO ())],
    mainStatePropertyReadActions :: IORef [(String, ReaderT P.PropertyMap IO ())],
    mainStateIncomeID            :: AccountsTableId,
    mainStateOutcomeID           :: AccountsTableId,
    mainStateCashID              :: AccountsTableId,
    mainStateIncomeName          :: String,
    mainStateOutcomeName         :: String,
    mainStateCashName            :: String,
    mainStateNoneName            :: String
}

mainStateNew :: IO MainState
mainStateNew = do
    accountName <- observableNew
    tagsUpdated <- observableNew
    accountsUpdated <- observableNew
    propertySaveActions <- newIORef []
    propertyReadActions <- newIORef []

    incomeID <- accountsTableIncomeID
    outcomeID <- accountsTableOutcomeID
    cashID <- accountsTableCashID
    noneID <- tagsTableNoneID

    incomeName  <- accountsTableSelectName incomeID
    outcomeName <- accountsTableSelectName outcomeID
    cashName    <- accountsTableSelectName cashID
    noneName    <- tagsTableSelectName noneID

    return MainState {
        mainStateSelectedAccountName = accountName,
        mainStateTagsUpdated = tagsUpdated,
        mainStateAccountsUpdated = accountsUpdated,
        mainStatePropertySaveActions = propertySaveActions,
        mainStatePropertyReadActions = propertyReadActions,
        mainStateIncomeID = incomeID,
        mainStateOutcomeID = outcomeID,
        mainStateCashID = cashID,
        mainStateIncomeName = incomeName,
        mainStateOutcomeName = outcomeName,
        mainStateCashName = cashName,
        mainStateNoneName = noneName
    }

mainStateReadPropertiesAction :: MainState -> String -> ReaderT P.PropertyMap IO () -> IO ()
mainStateReadPropertiesAction mainState prefix action =
    modifyIORef' (mainStatePropertyReadActions mainState) $ \list ->
        (prefix, action):list

mainStateReadProperties :: P.PropertyMap -> (String, ReaderT P.PropertyMap IO ()) -> IO ()
mainStateReadProperties propertyMap (prefix, action) =
    case P.lookup prefix propertyMap of
        Just properties -> runReaderT action properties
        Nothing         -> return ()

mainStateReadPropertiesFromDisk :: MainState -> IO ()
mainStateReadPropertiesFromDisk mainState = do
    dir <- configGetDirectory
    exists <- doesFileExist $ dir ++ "/app_state.st"
    propertyMap <- if exists
                       then Binary.decodeFile (dir ++ "/app_state.st")
                       else return P.empty
    actions <- readIORef $ mainStatePropertyReadActions mainState

    forM_ actions $ mainStateReadProperties propertyMap

propertyRead :: P.IsProperty a => String -> (a -> ReaderT P.PropertyMap IO ()) -> ReaderT P.PropertyMap IO ()
propertyRead key action = do
    properties <- ask
    case P.lookup key properties of
        Just value -> action value
        Nothing    -> return ()

propertyRead2 :: P.IsProperty a => String -> String -> (a -> a -> ReaderT P.PropertyMap IO ()) -> ReaderT P.PropertyMap IO ()
propertyRead2 key1 key2 action = do
    propertyRead key1 $ \value1 ->
        propertyRead key2 $ \value2 -> action value1 value2

propertyRead3 :: P.IsProperty a => String -> String -> String -> (a -> a -> a -> ReaderT P.PropertyMap IO ()) -> ReaderT P.PropertyMap IO ()
propertyRead3 key1 key2 key3 action = do
    propertyRead key1 $ \value1 ->
        propertyRead2 key2 key3 $ \value2 value3 -> action value1 value2 value3

propertyRead4 :: P.IsProperty a => String -> String -> String -> String -> (a -> a -> a -> a -> ReaderT P.PropertyMap IO ()) -> ReaderT P.PropertyMap IO ()
propertyRead4 key1 key2 key3 key4 action = do
    propertyRead key1 $ \value1 ->
        propertyRead3 key2 key3 key4 $ \value2 value3 value4 -> action value1 value2 value3 value4

mainStateSavePropertiesOnDisk :: MainState -> IO ()
mainStateSavePropertiesOnDisk mainState = do
    actions <- readIORef $ mainStatePropertySaveActions mainState
    properties <- foldrM
        (\(prefix, action) m -> do
            m0 <- execStateT action P.empty
            return $
                case P.lookup prefix m of
                    Just m' -> P.insert prefix (P.union m' m0) m
                    Nothing -> P.insert prefix m0 m
        )
        P.empty
        actions
    dir <- configGetDirectory
    Binary.encodeFile (dir ++ "/app_state.st") properties

mainStateSavePropertiesAction :: MainState -> String -> StateT P.PropertyMap IO () -> IO ()
mainStateSavePropertiesAction mainState prefix action = do
    modifyIORef' (mainStatePropertySaveActions mainState) $ \list ->
        (prefix, action):list

propertyInsert :: P.IsProperty a => String -> a -> StateT P.PropertyMap IO ()
propertyInsert key value = get >>= \m -> put $ P.insert key value m

propertyInsertMaybe :: P.IsProperty a => String -> Maybe a -> StateT P.PropertyMap IO ()
propertyInsertMaybe key value = get >>= \m -> put $ P.insertMaybe key value m