{-# OPTIONS_HADDOCK prune, ignore-exports #-}

module Data.Nutrition.Food
    ( Food ()
    , Data.Nutrition.Food.prot
    , Data.Nutrition.Food.carb
    , Data.Nutrition.Food.fat

    , (.@)

    , foodGenerator
    )
where

import Data.Monoid
import Data.Nutrition.Weight
import Data.Nutrition.Macros hiding (prot, carb, fat)
import Data.Nutrition.Macros as M
import Data.Nutrition.Defaults

-- | The Food data type consists of four things: 
-- 
-- * @foodname@ A name of the food. 
-- 
-- * @macros@   The macro nutrients of that specific food-item.
-- 
-- * @calories@ A calculated caloric value saved in kilo calories. 
-- 
-- * @amount@   The amount of the actual food. 
data Food 
    = Food 
      { foodname :: Name 
      , macros :: Macros 
      , calories :: Int  
      , amount :: Weight 
      } 

-- | A simple operator so one can write the 'Weight' value prefix to
-- the food.
(.@) :: Weight -> (Weight -> Food) -> Food
(.@) w f = f w 

-- | kcal takes a food item and gives back the kilo-caloric
-- value. This value is calculated from the macro-nutrition values.
kcal :: Food -> Int
kcal f 
    = round $ protkcal + carbkcal + fatkcal
    where 
      protkcal = kcalOfProt * (Data.Nutrition.Food.prot f)
      carbkcal = kcalOfCarb * (Data.Nutrition.Food.carb f)
      fatkcal  = kcalOfFat  * (Data.Nutrition.Food.fat f)



-- | This function is a "smart constructor" to construct a food
-- item. If no 'Weight' is supplied, a food generating function for a
-- specific food item is returned. 
foodGenerator :: String -> (Protein, Carb, Fat) -> (Weight -> Food)
foodGenerator s (p, c, f) w
    = Food
        s
        (newMacros (p, c, f))
        (round $ kcalOfProt * p / 100.0 * inGramm w
               + kcalOfCarb * c / 100.0 * inGramm w
               + kcalOfFat  * f / 100.0 * inGramm w)
        w

-- | The 'prot', 'carb' and 'fat' functions return the value of the
-- macro-nutrients in gram's. 
prot, carb, fat :: Food -> Float
prot f = (inGramm.amount $ f) * (M.prot . macros $ f) / 100.0
carb f = (inGramm.amount $ f) * (M.carb . macros $ f) / 100.0
fat  f = (inGramm.amount $ f) * (M.fat  . macros $ f) / 100.0


-- | 'Food' is an instance of the Show class, so it can be printed
-- onto the screen.
instance Show Food where
    show (Food n ms cals (Gramm a))
         = show (round a) ++ "g (" ++ show cals ++ " kcal) " ++ n ++ " (Prot: " ++ show (round $ M.prot ms) ++ "%, Carb: " 
                                             ++ show (round $ M.carb ms) ++ "%, Fat: " 
                                             ++ show (round $ M.fat ms)  ++ "%)\n"
           where prot_g = a * (M.prot ms) / 100.0
                 carb_g = a * (M.carb ms) / 100.0
                 fat_g  = a * (M.fat  ms) / 100.0


                  
-- | 'Food' is an instance of the Monoid class. That leads to the
-- possibility of combining foods to meals together.
instance Monoid Food where
    mempty 
        = Food "" (newMacros (0, 0, 0)) 0 (Gramm 0)

    f1 `mappend` f2
        = addFood f1 f2 


-- | The 'addFood' function does the combination of two foods into
-- another one.
addFood :: Food -> Food -> Food
addFood f1 f2
    = Food { foodname = foodname f1 ++ " and " ++ foodname f2 
           , macros = newMacros ( ((Data.Nutrition.Food.prot f1) + (Data.Nutrition.Food.prot f2)) / fact'
                                , ((Data.Nutrition.Food.carb f1) + (Data.Nutrition.Food.carb f2)) / fact'
                                , ((Data.Nutrition.Food.fat  f1) + (Data.Nutrition.Food.fat  f2)) / fact'
                                )
           , calories = calories f1 + calories f2
           , amount = amount f1 + amount f2
           }
    where 
      fact1 = (fG $ amount f1) / 100.0
      fact2 = (fG $ amount f2) / 100.0
      fact' = (fact1 + fact2)
      mf1 = macros f1
      mf2 = macros f2
      fG (Gramm x) = x