{- Copyright (C) 2013-2015 Dr. Alistair Ward This file is part of WeekDaze. WeekDaze 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 3 of the License, or (at your option) any later version. WeekDaze 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 WeekDaze. If not, see . -} {- | [@AUTHOR@] Dr. Alistair Ward [@DESCRIPTION@] * Individual /students/ can be grouped into a /body/ composed from those with identical /profiles/. * This reduces the number of personal timetables that must ultimately be constructed. * This is a relationship persists for the duration of a configuration-file, & in this respect differs from a /class/ of /student/s, which is a transient relationship formed for the duration of a single booking in the timetable, or perhaps for the bookings in a single /course/. [@TODO@] * The requirement to specify all the /student-Ids/, could be replaced with merely an anonymous head-count, since the application make very little use of this data. -} module WeekDaze.Aggregate.StudentBody( -- * Types -- ** Type-synonyms Mnemonic, -- Members, StudentBody( -- MkStudentBody, getMnemonic, getStudentIds ), -- * Constants -- tag, mnemonicTag, -- studentTag, studentIdTag, -- * Functions getSize, toPair, -- ** Constructor mkStudentBody ) where import qualified Control.Arrow import Control.Arrow((&&&)) import qualified Control.DeepSeq import qualified Data.List import qualified Data.Set import qualified Text.XHtml.Strict import Text.XHtml.Strict((<<), (!)) import qualified Text.XML.HXT.Arrow.Pickle as HXT import qualified ToolShed.SelfValidate import qualified WeekDaze.Data.Student as Data.Student import qualified WeekDaze.Size as Size -- | Used to qualify CSS & XML. tag :: String tag = "studentBody" -- | Used to qualify SQL & XML. mnemonicTag :: String mnemonicTag = "mnemonic" -- | Used to qualify XML. studentTag :: String studentTag = "student" -- | Used to qualify SQL & XML. studentIdTag :: String studentIdTag = "studentId" -- | The type of the mnemonic by which a /student-body/ is known. type Mnemonic = String -- | The type the member-/student/s. type Members = Data.Set.Set Data.Student.Id {- | * A group of /student/s with identical /profile/s; i.e. with identical requirements. * Because this data-type forms part of the results for the application, & can become excessively verbose, a concise /mnemonic/ is provided. -} data StudentBody = MkStudentBody { getMnemonic :: Mnemonic, -- ^ A mnemonic for the whole /student-body/; e.g. "2/B" or "Remedial". getStudentIds :: Members -- ^ The identifiers of the member-/student/s. } -- 'compare' is a performance-hotspot, & therefore has a specialised implementation. instance Ord StudentBody where MkStudentBody mnemonicL _ `compare` MkStudentBody mnemonicR _ = mnemonicL `compare` mnemonicR -- CAVEAT: because of the specialised 'Ord'-implementation, a compatible specialised 'Eq'-implementation is required; otherwise two unequal StudentBodies may be neither '<' nor '>'. instance Eq StudentBody where MkStudentBody mnemonicL _ == MkStudentBody mnemonicR _ = mnemonicL == mnemonicR instance Read StudentBody where readsPrec _ = map (Control.Arrow.first $ uncurry mkStudentBody . Control.Arrow.second Data.Set.fromList) . reads -- This instance-declaration hides the internal structure. instance Show StudentBody where showsPrec _ = shows . Control.Arrow.second Data.Set.toList . toPair instance Text.XHtml.Strict.HTML StudentBody where toHtml studentBody = Text.XHtml.Strict.thespan ! [ Text.XHtml.Strict.theclass tag, Text.XHtml.Strict.title . Data.List.intercalate ", " . map show {-display double-quotes-} . Data.Set.toList $ getStudentIds studentBody ] << getMnemonic studentBody instance ToolShed.SelfValidate.SelfValidator StudentBody where getErrors studentBody = ToolShed.SelfValidate.extractErrors [ (null $ getMnemonic studentBody, "null mnemonic for student-ids; " ++ show (getStudentIds studentBody)), (Data.Set.null $ getStudentIds studentBody, "zero student-ids are members of the student-body of mnemonic " ++ show (getMnemonic studentBody)) ] instance HXT.XmlPickler StudentBody where xpickle = HXT.xpElem tag . HXT.xpWrap ( uncurry mkStudentBody . Control.Arrow.second Data.Set.fromList, -- Construct from a Pair. Control.Arrow.second Data.Set.toList . toPair -- Deconstruct to a Pair. ) $ HXT.xpTextAttr mnemonicTag {-can't be null-} `HXT.xpPair` ( HXT.xpList1 {-can't be null-} . HXT.xpElem studentTag $ HXT.xpTextAttr studentIdTag {-can't be null-} ) instance Control.DeepSeq.NFData StudentBody where rnf = Control.DeepSeq.rnf . toPair -- | Smart constructor. mkStudentBody :: Mnemonic -> Members -> StudentBody mkStudentBody mnemonic students | ToolShed.SelfValidate.isValid studentBody = studentBody | otherwise = error $ "WeekDaze.Aggregate.StudentBody.mkStudentBody:\t" ++ ToolShed.SelfValidate.getFirstError studentBody ++ "." where studentBody = MkStudentBody mnemonic students -- | Deconstruct. toPair :: StudentBody -> (Mnemonic, Members) toPair = getMnemonic &&& getStudentIds -- | The number of /student/s in the body. getSize :: StudentBody -> Size.NStudents getSize = Data.Set.size . getStudentIds