{- 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@] * 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