packages feed

weekdaze-0.0.0.1: src-lib/WeekDaze/Aggregate/StudentBody.hs

{-
	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 <http://www.gnu.org/licenses/>.
-}
{- |
 [@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