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