packages feed

schedule-planner-0.1.0.2: src/SchedulePlanner/Serialize.hs

{-|
Module      : $Header$
Description : (de)serializing in- and output
Copyright   : (c) Justus Adam, 2015
License     : LGPL-3
Maintainer  : development@justusadam.com
Stability   : experimental
Portability : POSIX

Hold the capeablilities to get and export in- and output data as well as (de)serialize it
-}
module SchedulePlanner.Serialize where

import           Control.Applicative        (pure, (<*>))
import           Control.Monad              (liftM)
import           Data.List                  (intercalate)
import qualified Data.Map                   as Map (assocs, lookup)
import           SchedulePlanner.Calculator
import           Text.JSON                  as JSON (JSON, JSObject, JSValue (JSArray, JSObject),
                                                     Result (..), decodeStrict,
                                                     encode, showJSON,
                                                     toJSObject, valFromObj)
import           Text.Printf                (printf)



-- |Key for the rules data in the json input
ruleKey       = "rules"
-- |Key for the lesson data in the json input
lessonKey     = "lessons"
-- |Key for the scope property in Rule objects in the json input
scopeKey      = "scope"
-- |Key for the severity property in Rule objects in the json input
severityKey   = "severity"
-- |Key for the day property in Rule objects in the json input
ruleDayKey    = "day"
-- |Key for the slot property in Rule objects in the json input
ruleSlotKey   = "slot"
-- |Key for the subject property in Lesson objects in the json input
subjectKey    = "subject"
-- |Key for the day property in Lesson objects in the json input
lessonDayKey  = "day"
-- |Key for the slot property in Rule objects in the json input
lessonSlotKey = "slot"


-- |How many days a week has
daysPerWeek   = 7
-- |The amount of imeslots each day
slotsPerDay   = 7
-- |The caracter width of a single slot in output
cellWidth     = 20


-- |Open a file and return the contents as parsed json
getFromFile :: JSON a => String -> IO(Result a)
getFromFile filename = liftM decodeStrict (readFile filename)


-- |Open a file and write json to it
writeToFile :: String -> JSValue -> IO()
writeToFile filename = writeFile filename . JSON.encode


-- | parses file as JSON and returns internally used data structures
-- | convenience function
deSerialize :: String -> Result ([Result Rule], [Result (Lesson String)])
deSerialize = transformTypes . decodeStrict


-- |Turns parsed json values into the internally used datastructures.
transformTypes :: Result JSValue -> Result ([Result Rule], [Result (Lesson String)])
transformTypes (Ok (JSObject o))  = do
    rv      <- valFromObj ruleKey o
    lv      <- valFromObj lessonKey o

    rules   <- extractRules rv
    lessons <- extractLessons lv

    return (rules, lessons)
transformTypes (Ok _)             = Error "wrong value type"
transformTypes (Error e)          = Error e


serialize :: Show s => [MappedSchedule s] -> String
serialize = JSON.encode . nativeToJson


-- |Transform Native the native schedules into JSON
nativeToJson :: Show s => [MappedSchedule s] -> JSValue
nativeToJson = JSArray . map convert
  where
    convert :: Show s => MappedSchedule s -> JSValue
    convert =
      pure (\a b -> JSObject (JSON.toJSObject [a,b]))
        <*> ((,) "weight" . showJSON . totalWeight)
        <*> ((,) "values" . JSArray .
              map
                (\((i, j), b) ->
                  JSObject (JSON.toJSObject [
                            ("day", showJSON i),
                            ("slot", showJSON j),
                            ("subject", (showJSON . show . subject) b)
                          ]))
                . Map.assocs
              )


-- |Turns a parsed json value into a 'List' of 'Rule's or return an 'Error'
extractRules :: JSValue -> Result [Result Rule]
extractRules (JSArray rv)  = return $ map handleOne rv
  where
    handleOne :: JSValue -> Result Rule
    handleOne (JSObject o)  = do
      scope     <- valFromObj scopeKey o
      severity  <- valFromObj severityKey o

      let rp = (`Rule` severity)

      case scope of
        "day"   -> do
          day   <- valFromObj ruleDayKey o
          return $ rp $ Day day
        "slot"  -> do
          slot  <- valFromObj ruleSlotKey o
          return $ rp $ Slot slot
        "cell"  -> do
          slot  <- valFromObj ruleSlotKey o
          day   <- valFromObj ruleDayKey o
          return $ rp $ Cell day slot

    handleOne _             = Error "wrong value type"

extractRules _             = Error "key lessons does not contain array"


-- |Turns a parsed json value into a 'List' of 'Lesson's or return an 'Error'
extractLessons :: JSValue -> Result [Result (Lesson String)]
extractLessons (JSArray a)  = return $ map handleOne a
  where
    handleOne :: JSValue -> Result (Lesson String)
    handleOne (JSObject o)  = do
      subject <- valFromObj subjectKey o
      day     <- valFromObj lessonDayKey o
      slot    <- valFromObj lessonSlotKey o
      return $ Lesson slot day 0 subject
    handleOne _             = Error "wrong type"

extractLessons _            = Error "wrong value type"


shortSubject :: Show s => s -> String
shortSubject = reverse . take cellWidth . reverse . show


{-|
  Transform a 'MappedSchedule' into a printable,
  and more importantly, readable String
-}
formatSchedule :: Show s => MappedSchedule s -> String
formatSchedule hours = intercalate "\n" $ header : map formatDay allHours
  where
    allHours = [(i, [1..slotsPerDay]) | i <- [1..daysPerWeek]]

    formatLesson :: Timeslot -> String
    formatLesson i =
      printf ("%" ++ show cellWidth ++ "v") $ maybe [] (shortSubject . subject) (Map.lookup i hours)

    formatDay :: (Int, [Int]) -> String
    formatDay (i, l) = intercalate " | " [formatLesson (j, i) | j <- l]

    header = printf "Total Weight: %10v" (totalWeight hours)