packages feed

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

{-# LANGUAGE OverloadedStrings #-}
{-|
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
  ( mapToJSON
  , formatSchedule
  , shortSubject
  , DataFile(DataFile)
  , eitherDecode
  ) where

import           Control.Arrow              as Arrow (first)
import           Data.Aeson                 (FromJSON, Object, ToJSON,
                                             Value (Object), decode, encode,
                                             object, parseJSON, toJSON, (.:),
                                             (.=), eitherDecode)
import           Data.Aeson.Types           (Parser)
import qualified Data.ByteString.Lazy       as LBS (readFile, writeFile)
import           Data.List                  as List (intercalate)
import qualified Data.Map                   as Map (Map, lookup, toList)
import           Data.Text                  as T (Text, pack)
import           SchedulePlanner.Calculator (Lesson (..), MappedSchedule,
                                             Rule (..), Target (..), Timeslot,
                                             timeslot, totalWeight)
import           Text.Printf                (printf)
import           Control.Monad              (mzero)



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


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


-- |Base structure of the input JSON file
data DataFile = DataFile [Rule] [Lesson Text]


instance FromJSON a => FromJSON (Lesson a) where
  parseJSON (Object o) = Lesson
    <$> o .: lessonSlotKey
    <*> o .: lessonDayKey
    <*> pure 0
    <*> o .: subjectKey
  parseJSON _          = mzero


instance ToJSON a => ToJSON (Lesson a) where
  toJSON =
    object . sequenceA
      [ (.=) lessonSlotKey . timeslot
      , (.=) lessonDayKey  . day
      , (.=) subjectKey    . subject
      ]


instance ToJSON Rule where
  toJSON =
    object . ((:)
      <$> ((.=) severityKey . severity)
      <*> uncurry (:) . Arrow.first (scopeKey .=) . getTarget . target)
    where
      getTarget :: Target -> (Text, [(Text, Value)])
      getTarget (Day d)    = ("day",   [ruleDayKey  .= d])
      getTarget (Cell d s) = ("cell",  [ruleDayKey  .= d, ruleSlotKey .= s])
      getTarget (Slot s)   = ("slot",  [ruleSlotKey .= s])


instance FromJSON Rule where
  parseJSON (Object o) = Rule
    <$> ((o .: scopeKey) >>= fromScope o)
    <*> o .: severityKey
    where
      fromScope :: Object -> Text -> Parser Target
      fromScope obj "day"  = Day  <$> obj .: ruleDayKey
      fromScope obj "slot" = Slot <$> obj .: ruleSlotKey
      fromScope obj "cell" = Cell <$> obj .: ruleSlotKey <*> obj .: ruleDayKey
      fromScope _ _        = error "unknown input"  -- I am so sorry
  parseJSON _         = mzero


instance FromJSON DataFile where
  parseJSON (Object o) = DataFile
    <$> o .: ruleKey
    <*> o .: lessonKey
  parseJSON _          = mzero

instance ToJSON DataFile where
  toJSON (DataFile r l) =
    object
      [ lessonKey .= l
      , ruleKey   .= r
      ]


-- |Convert a suitable Map to a JSON Value
mapToJSON :: ToJSON a => Map.Map Text a -> Value
mapToJSON = object . map (uncurry (.=)) . Map.toList

-- |Open a file and return the contents as parsed json
getFromFile :: FilePath -> IO(Maybe DataFile)
getFromFile = fmap decode . LBS.readFile


-- |Open a file and write json to it
writeToFile :: ToJSON a => FilePath -> Map.Map Text a -> IO()
writeToFile filename = LBS.writeFile filename . encode . mapToJSON


-- |Shorten a subject to something printable
shortSubject :: Show s => s -> String
shortSubject = reverse . take cellWidth . reverse . show


{-|
  Transform a 'MappedSchedule' into a printable,
  and more importantly, readable Text
-}
formatSchedule :: Show s => MappedSchedule s -> Text
formatSchedule hours = pack $ List.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) = List.intercalate " | " [formatLesson (j, i) | j <- l]

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