{-
    Copyright 2015 Markus Ongyerth, Stephan Guenther

    This file is part of Monky.

    Monky is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Monky 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 Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Monky.  If not, see <http://www.gnu.org/licenses/>.
-}
{-# LANGUAGE CPP #-}
{-|
Module      : Monky.Battery
Description : Allows access to information about a battery connected to the system
Maintainer  : ongy
Stability   : testing
Portability : Linux

This module allows to read information about a battery connected to the system.

This module does not support every setup yet.
The files in /sys/ used by this module are driver dependend.
If it crashes monky for you setup please make a bug report with an
`ls /sys/class/power_supply/" ++ e ++ "/` attached.
-}

module Monky.Battery
  ( getBatteryHandle
  , getBatteryHandle'
  , getCurrentStatus
  , getCurrentLevel
  , BatteryHandle
  , getTimeLeft
  , getLoading
  , BatteryState(..)
  )
where

#if MIN_VERSION_base(4,8,0)
#else
import Control.Applicative ((<$>))
#endif

import Data.Maybe (fromMaybe)
import Data.IORef
import System.Directory
import Monky.Utility

-- FilesArray for the files, those have to be handled different so they
-- get their own type for pattern matching
-- Everything needed as state will be carried in the battery handle
-- |The handle exported by this module
data BatteryHandle = BatH FilesArray (IORef Int)

--PowerNow will use: power_now energy_now energy_full
--ChargeNow will use: voltage_now current_now current_avg charge_now charge_full
data FilesArray = PowerNow File File File File |
  ChargeNow File File (Maybe File) File File File deriving(Show)

-- |Datatype to represent battery state
data BatteryState = BatFull | BatLoading | BatDraining

pnowPath :: String -> String
pnowPath e  = "/sys/class/power_supply/" ++ e ++ "/power_now"
enowPath :: String -> String
enowPath e  = "/sys/class/power_supply/" ++ e ++ "/energy_now"
efullPath :: String -> String
efullPath e = "/sys/class/power_supply/" ++ e ++ "/energy_full"
vnowPath :: String -> String
vnowPath e  = "/sys/class/power_supply/" ++ e ++ "/voltage_now"
cnowPath :: String -> String
cnowPath e  = "/sys/class/power_supply/" ++ e ++ "/current_now"
cavgPath :: String -> String
cavgPath e  = "/sys/class/power_supply/" ++ e ++ "/current_avg"
chnowPath :: String -> String
chnowPath e = "/sys/class/power_supply/" ++ e ++ "/charge_now"
chfullPath :: String -> String
chfullPath e = "/sys/class/power_supply/" ++ e ++ "/charge_full"
adpPath :: String -> String
adpPath e  = "/sys/class/power_supply/"++ e ++"/online"

-- |Internal function for getCurrentStatus
getCurrentStatusInt :: File -> IO Int
getCurrentStatusInt = readValue


getCurrentStatusM :: BatteryHandle -> IO Int
getCurrentStatusM (BatH (PowerNow _ _ _ adp) _) =
  getCurrentStatusInt adp
getCurrentStatusM (BatH (ChargeNow _ _ _ _ _ adp) _) =
  getCurrentStatusInt adp

-- |Get the current state of the battery (loading/draining/full)
getCurrentStatus :: BatteryHandle -> IO BatteryState
getCurrentStatus h = do
  val <- getCurrentStatusM h
  case val of
    1 -> return BatLoading
    0 -> return BatDraining
    _ -> return BatFull

-- |Internal function for getcurrentLevel
getCurrentLevelInt :: File -> File -> IO Int
getCurrentLevelInt n f = do
  now <- readValue n
  full <- readValue f
  return $ now * 100 `div` full

-- |Get the charge left in the battery in percent.
getCurrentLevel :: BatteryHandle -> IO Int
getCurrentLevel (BatH (PowerNow _ now full _) _) =
  getCurrentLevelInt now full
getCurrentLevel (BatH (ChargeNow _ _ _ now full _) _) =
  getCurrentLevelInt now full


-- |Internal function for getTimeLeft
getTimeLeftInt :: File -> File -> File -> Int -> File -> IO (Int, Int)
getTimeLeftInt n c f s adp = do
  online <- getCurrentStatusInt adp
  now <- readValue n
  full <- readValue f
  let gap = if online == 0 then now else full - now
  change <- readValue c
  let avg = (change * 20 + s * 80) `div` 100
  return (sdivUBound gap (avg `div` 3600) 0, avg)

-- |Get current loading speed in Watt/s
getLoading :: BatteryHandle -> IO Float
getLoading (BatH (PowerNow pnow _ _ _) _) = do
  power <- readLine pnow
  return $(read power :: Float) / 1000000
getLoading (BatH (ChargeNow vnow cnow _ _ _ _) _) = do
  voltage <- readLine vnow
  current <- readLine cnow
  let pow = ((read voltage :: Float) * (read current :: Float)) / 1000000000000
  return pow

-- |Get an approximated amount of seconds left until the battery runs out
getTimeLeft :: BatteryHandle -> IO Int
getTimeLeft (BatH (PowerNow pnow enow efull adp) s)= do
  c <- readIORef s
  (t, n) <- getTimeLeftInt enow pnow efull c adp
  writeIORef s n
  return t
getTimeLeft (BatH (ChargeNow _ cnow cavg chnow chfull adp) s)= do
  c <- readIORef s
  (t, _) <- getTimeLeftInt chnow (fromMaybe cnow cavg) chfull c adp
  return $ (floor . sqrt $(fromIntegral t :: Float)) * 60

-- |Create a power handle that uses the power_now file
createPowerNowHandle :: String -> String -> IO BatteryHandle
createPowerNowHandle e b = do
  power_now <- fopen $ pnowPath b
  energy_now <- fopen $ enowPath b
  energy_full <- fopen $ efullPath b
  adp_online <- fopen $ adpPath e
  ref <- newIORef (0 :: Int)
  return $BatH (PowerNow power_now energy_now energy_full adp_online) ref

-- |Create a power handle that uses the charge_now file
createChargeNowHandle :: String -> String -> IO BatteryHandle
createChargeNowHandle e b = do
  voltage_now <- fopen $ vnowPath b
  current_now <- fopen $ cnowPath b
  exists <- doesFileExist $ cavgPath b
  current_avg <- if exists then Just <$> fopen (cavgPath b) else return Nothing
  charge_now <- fopen $ chnowPath b
  charge_full <- fopen $ chfullPath b
  adp_online <- fopen $ adpPath e
  ref <- newIORef (0 :: Int)
  return $BatH (ChargeNow voltage_now current_now current_avg charge_now charge_full adp_online) ref

-- |Create a 'BatteryHandle'
getBatteryHandle :: String  -- ^The name of the wall socket adapter used by the battery
                 -> String -- ^The name of the battery
                 -> IO BatteryHandle
getBatteryHandle e b = do
  exists <- doesFileExist $ pnowPath b
  if exists
    then createPowerNowHandle e b
    else createChargeNowHandle e b

-- |Version which defaults to "BAT0"
getBatteryHandle' :: String -> IO BatteryHandle
getBatteryHandle' = flip getBatteryHandle "BAT0"