    {-# LANGUAGE FlexibleContexts       #-}
    {-# LANGUAGE FlexibleInstances      #-}
    {-# LANGUAGE FunctionalDependencies #-}
    {-# LANGUAGE MultiParamTypeClasses  #-}
    -----------------------------------------------------------------------------
    -- |
    -- Module      :  Diagrams.Query
    -- Copyright   :  (c) 2013 diagrams-lib team (see LICENSE)
    -- License     :  BSD-style (see LICENSE)
    -- Maintainer  :  diagrams-discuss@googlegroups.com
    --
    -- A query is a function that maps points in a vector space to values
    -- in some monoid. Queries naturally form a monoid, with two queries
    -- being combined pointwise.
    --
    -----------------------------------------------------------------------------

    module Diagrams.Query
      ( -- * Queries
        Query(..)
      , HasQuery (..)
      , sample
      , inquire

        -- ** Queries on diagrams
      , query
      , value
      , resetValue
      , clearValue
      ) where

    import           Data.Monoid

    import           Diagrams.Core

    -- | Types which can answer a 'Query' about points inside the geometric
    --   object.
    --
    --   If @t@ and @m@ are both a 'Semigroup's, 'getQuery' should satisfy
    --
    -- @
    -- 'getQuery' (t1 <> t2) = 'getQuery' t1 <> 'getQuery' t2
    -- @
    class HasQuery t m | t -> m where
      -- | Extract the query of an object.
      getQuery :: t -> Query (V t) (N t) m

    instance HasQuery (Query v n m) m where
      getQuery = id

    instance Monoid m => HasQuery (QDiagram b v n m) m where
      getQuery = query

    -- | Test if a point is not equal to 'mempty'.
    --
    -- @
    -- 'inquire' :: 'QDiagram' b v n 'Any' -> 'Point' v n -> 'Bool'
    -- 'inquire' :: 'Query' v n 'Any'      -> 'Point' v n -> 'Bool'
    -- 'inquire' :: 'Diagrams.BoundingBox.BoundingBox' v n  -> 'Point' v n -> 'Bool'
    -- @
    inquire :: HasQuery t Any => t -> Point (V t) (N t) -> Bool
    inquire t = getAny . sample t

    -- | Sample a diagram's query function at a given point.
    --
    -- @
    -- 'sample' :: 'QDiagram' b v n m -> 'Point' v n -> m
    -- 'sample' :: 'Query' v n m      -> 'Point' v n -> m
    -- 'sample' :: 'Diagrams.BoundingBox.BoundingBox' v n  -> 'Point' v n -> 'Any'
    -- 'sample' :: 'Diagrams.Path.Path' 'V2' 'Double'   -> 'Point' v n -> 'Diagrams.TwoD.Path.Crossings'
    -- @
    sample :: HasQuery t m => t -> Point (V t) (N t) -> m
    sample = runQuery . getQuery

    -- | Set the query value for 'True' points in a diagram (/i.e./ points
    --   \"inquire\" the diagram); 'False' points will be set to 'mempty'.
    value :: Monoid m => m -> QDiagram b v n Any -> QDiagram b v n m
    value m = fmap fromAny
      where fromAny (Any True)  = m
            fromAny (Any False) = mempty

    -- | Reset the query values of a diagram to @True@/@False@: any values
    --   equal to 'mempty' are set to 'False'; any other values are set to
    --   'True'.
    resetValue :: (Eq m, Monoid m) => QDiagram b v n m -> QDiagram b v n Any
    resetValue = fmap toAny
      where toAny m | m == mempty = Any False
                    | otherwise   = Any True

    -- | Set all the query values of a diagram to 'False'.
    clearValue :: QDiagram b v n m -> QDiagram b v n Any
    clearValue = fmap (const (Any False))