packages feed

weekdaze-0.0.0.1: src-lib/WeekDaze/Model/TimetableAxisTriple.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@]

	* Defines an ordered triple of orthogonal /timetable/-axes.

	* Since these axes represent different concepts, the order in which they're traversed when defining the /lesson/ at each /time-slot/, is significant.
-}

module WeekDaze.Model.TimetableAxisTriple(
-- * Types
-- ** Type-synonyms
--	Triple,
-- ** Data-types
	Axes(
		deconstruct
	),
-- * Constants
	tag,
	permutations,
-- * Functions
	fromList,
	toList,
	invertSense,
	generatePermutationsOf,
-- ** Constructor
	mkAxes,
-- ** Predicates
--	areOrthogonal,
	hasWildSense
) where

import qualified	Control.Arrow
import qualified	Control.DeepSeq
import qualified	Data.List
import qualified	Text.XML.HXT.Arrow.Pickle		as HXT
import qualified	ToolShed.SelfValidate
import qualified	WeekDaze.Model.TimetableAxis		as Model.TimetableAxis
import qualified	WeekDaze.Model.TimetableAxisTraversal	as Model.TimetableAxisTraversal

-- | Used to qualify XML.
tag :: String
tag	= "traversalOrder"

{- |
	* Defines the order & sense, in which the axes of a /timetable/ are traversed.

	* Each traversal is a raster-scan over the coordinates of a /timetable/,
	but the order in which one nests the required iterations potentially affects both the efficiency & result.

	* The first axis specified changes least frequently during the raster-scan, & the last most frequently.

	* There are @ (3! * 2^3 = 48) @ permutations, since we distinguish between the /senses/ in which each axis can be traversed.
-}
type Triple	= (Model.TimetableAxisTraversal.AxisTraversal, Model.TimetableAxisTraversal.AxisTraversal, Model.TimetableAxisTraversal.AxisTraversal)

-- | Dummy data-type on which to hang instance-definitions.
newtype Axes	= MkAxes {
	deconstruct	:: Triple
} deriving Eq

instance Read Axes where
	readsPrec _	= map (Control.Arrow.first mkAxes) . reads

instance Show Axes where
	showsPrec _ (MkAxes triple)	= shows triple

instance ToolShed.SelfValidate.SelfValidator Axes where
	getErrors axes	= ToolShed.SelfValidate.extractErrors [(not $ areOrthogonal axes, "axes must be orthogonal; " ++ show axes)]

instance HXT.XmlPickler Axes where
	xpickle	= HXT.xpElem tag $ HXT.xpWrap (
		mkAxes,		-- Construct from a Triple.
		deconstruct	-- Deconstruct to a Triple.
	 ) $ HXT.xpTriple (
		HXT.xpElem "x" HXT.xpickle
	 ) (
		HXT.xpElem "y" HXT.xpickle
	 ) (
		HXT.xpElem "z" HXT.xpickle
	 )

instance Control.DeepSeq.NFData Axes where
	rnf	= Control.DeepSeq.rnf . deconstruct

-- | Smart constructor.
mkAxes :: Triple -> Axes
mkAxes triple
	| ToolShed.SelfValidate.isValid axes	= axes
	| otherwise				= error $ "WeekDaze.Model.TimetableAxisTriple.mkAxes:\t" ++ ToolShed.SelfValidate.getFirstError axes ++ "."
	where
		axes	= MkAxes triple

-- | True if the three specified axes are distinct.
areOrthogonal :: Axes -> Bool
areOrthogonal	= (== 3) . length . Data.List.nub . map Model.TimetableAxisTraversal.getAxis . toList

-- | Invert the sense of each axis, but not their order, thus reversing the raster.
invertSense :: Axes -> Axes
invertSense	= fromList . map Model.TimetableAxisTraversal.invertSense . toList

-- | Convert from a list of /axis-traversal/s.
fromList :: [Model.TimetableAxisTraversal.AxisTraversal] -> Axes
fromList [x, y, z]	= mkAxes (x, y, z)
fromList axisTraversals	= error $ "WeekDaze.Model.TimetableAxisTriple.mkAxes:\tprecisely three axes-traversals are required " ++ show axisTraversals ++ "."

-- | Convert into a list of /axis-traversal/s.
toList :: Axes -> [Model.TimetableAxisTraversal.AxisTraversal]
toList (MkAxes (x, y, z))	= [x, y, z]

-- | The constant list of all permutations of axis-order & sense of travel along each.
permutations :: [Axes]
permutations	= [
	mkAxes (
		Model.TimetableAxisTraversal.MkAxisTraversal (Just xSense) xAxis,
		Model.TimetableAxisTraversal.MkAxisTraversal (Just ySense) yAxis,
		Model.TimetableAxisTraversal.MkAxisTraversal (Just zSense) $ Model.TimetableAxis.getPerpendicular xAxis yAxis
	) |
		xSense	<- [minBound .. maxBound],
		xAxis	<- Model.TimetableAxis.range,
		ySense	<- [minBound .. maxBound],
		yAxis	<- Model.TimetableAxis.getOthers xAxis,
		zSense	<- [minBound .. maxBound]
 ] -- List-comprehension.

-- | The list of permutations of axis-order & sense of travel along each matching the supplied specification.
generatePermutationsOf :: Axes -> [Axes]
generatePermutationsOf axes@(MkAxes (x, y, z))
	| not $ hasWildSense axes	= [axes]	-- Not strictly necessary, since the main algorithm copes with this case.
	| otherwise			= [
		mkAxes (
			Model.TimetableAxisTraversal.MkAxisTraversal (Just xSense) $ Model.TimetableAxisTraversal.getAxis x,
			Model.TimetableAxisTraversal.MkAxisTraversal (Just ySense) $ Model.TimetableAxisTraversal.getAxis y,
			Model.TimetableAxisTraversal.MkAxisTraversal (Just zSense) $ Model.TimetableAxisTraversal.getAxis z
		) |
			xSense	<- Model.TimetableAxisTraversal.maybeSenseToList $ Model.TimetableAxisTraversal.getMaybeSense x,
			ySense	<- Model.TimetableAxisTraversal.maybeSenseToList $ Model.TimetableAxisTraversal.getMaybeSense y,
			zSense	<- Model.TimetableAxisTraversal.maybeSenseToList $ Model.TimetableAxisTraversal.getMaybeSense z
	] -- List-comprehension.

-- | True if the sense is ill-defined.
hasWildSense :: Axes -> Bool
hasWildSense	= any Model.TimetableAxisTraversal.hasWildSense . toList