    -----------------------------------------------------------------------------
    -- |
    -- Module      :  Graphics.HGL.Draw.Region
    -- Copyright   :  (c) Alastair Reid, 1999-2003
    -- License     :  BSD-style (see the file libraries/base/LICENSE)
    --
    -- Maintainer  :  libraries@haskell.org
    -- Stability   :  provisional
    -- Portability :  non-portable (requires concurrency)
    --
    -- An efficient representation of sets of pixels.
    --
    -----------------------------------------------------------------------------

    module Graphics.HGL.Draw.Region
    	( Region
    #if !X_DISPLAY_MISSING
    	, emptyRegion		-- :: Region
    #endif
    	, rectangleRegion	-- :: Point -> Point -> Region
    	, ellipseRegion		-- :: Point -> Point -> Region
    	, polygonRegion		-- :: [Point] -> Region
    	, intersectRegion	-- :: Region -> Region -> Region
    	, unionRegion		-- :: Region -> Region -> Region
    	, subtractRegion	-- :: Region -> Region -> Region
    	, xorRegion		-- :: Region -> Region -> Region
    	, regionToGraphic	-- :: Region -> Graphic
    	) where

    import Foreign.C.Types
    import Graphics.HGL.Units (Point, Angle)
    import Graphics.HGL.Draw.Monad (Graphic)
    import Graphics.HGL.Internals.Draw (mkDraw)
    #if !X_DISPLAY_MISSING
    import Graphics.HGL.X11.Types (DC(..), fromPoint)
    import qualified Graphics.X11.Xlib as X
    #else
    import Graphics.HGL.Win32.Types
    import qualified Graphics.Win32 as Win32
    #endif

    import System.IO.Unsafe( unsafePerformIO )

    ----------------------------------------------------------------
    -- The Interface (SOE, p136)
    --
    -- Note that Win32 does not include emptyRegion (SOE, p140).
    -- The obvious Win32 implementation (an empty rectangle) could create problems
    -- when you calculate the bounding box
    -- (This could be fixed by implementing Empty Regions explicitly in Haskell
    --  at the (small) cost of an extra test on every region operation.)
    ----------------------------------------------------------------

    #if !X_DISPLAY_MISSING
    newtype Region = MkRegion X.Region
    #else
    newtype Region = MkRegion Win32.HRGN
    #endif

    #if !X_DISPLAY_MISSING
    -- | An empty region.  This is not supported on Win32.
    -- It is possible to use an empty rectangle region instead.
    emptyRegion     :: Region
    #endif

    -- | A rectangular region, with the given points as opposite corners.
    rectangleRegion :: Point -> Point -> Region

    -- | An elliptical region that fits in the rectangle with the given points
    -- as opposite corners.
    ellipseRegion   :: Point -> Point -> Region

    -- | A polygonal region defined by a list of 'Point's.
    polygonRegion   :: [Point] -> Region

    -- | The intersection of two regions.
    intersectRegion :: Region -> Region -> Region

    -- | The union of two regions.
    unionRegion     :: Region -> Region -> Region

    -- | The part of the first region that is not also in the second.
    subtractRegion  :: Region -> Region -> Region

    -- | The symmetric difference of two regions.
    xorRegion    	:: Region -> Region -> Region

    -- | Fill a 'Region' using the current 'Graphics.HGL.Draw.Brush'.
    regionToGraphic :: Region -> Graphic

    ----------------------------------------------------------------
    -- The Implementation
    ----------------------------------------------------------------

    #if !X_DISPLAY_MISSING

    emptyXRegion = unsafePerformIO X.createRegion
    emptyRegion = MkRegion emptyXRegion

    rectangleRegion (x0,y0) (x1,y1) =
      polygonRegion [(x0,y0),(x0,y1),(x1,y1),(x1,y0)]

    ellipseRegion p0 p1 = MkRegion $ unsafePerformIO $ do
      X.polygonRegion pts X.evenOddRule
     where
      X.Point x0 y0 = fromPoint p0
      X.Point x1 y1 = fromPoint p1

      rx = (x1 - x0) `div` 2
      ry = (y1 - y0) `div` 2

      cx = x0 + rx
      cy = y0 + ry

      rx' = fromIntegral rx
      ry' = fromIntegral ry

      pts = [ X.Point (cx + round (rx' * c)) (cy + round (ry' * s))
            | (c,s) <- cos'n'sins
            ]

    cos'n'sins :: [(Double,Double)]
    cos'n'sins = [ (cos a, sin a) | a <- angles ]

    angles :: [Angle]
    angles = take 40 [0, pi/20 .. ]

    polygonRegion pts = MkRegion $ unsafePerformIO $ do
      X.polygonRegion (map fromPoint pts) X.evenOddRule

    intersectRegion = combine X.intersectRegion
    unionRegion     = combine X.unionRegion
    subtractRegion  = combine X.subtractRegion
    xorRegion       = combine X.xorRegion

    type XRegionOp = X.Region -> X.Region -> X.Region -> IO CInt

    combine :: XRegionOp -> Region -> Region -> Region
    combine op (MkRegion r1) (MkRegion r2) = unsafePerformIO $ do
      r <- X.createRegion
      op r1 r2 r
      return (MkRegion r)

    regionToGraphic (MkRegion r) = mkDraw $ \ dc -> do
      X.setRegion (disp dc) (brushGC dc) r
      X.fillRectangle (disp dc) (drawable dc) (brushGC dc) 0 0 (-1) (-1)  -- entire window (in 2s complement!)
      X.setRegion (disp dc) (brushGC dc) emptyXRegion
      return ()

    #else /* X_DISPLAY_MISSING */

    rectangleRegion pt0 pt1 = unsafePerformIO $ do
      r <- Win32.createRectRgn x0 y0 x1 y1
      return (MkRegion r)
     where
      (x0,y0) = fromPoint pt0
      (x1,y1) = fromPoint pt1

    -- Sigh! createEllipticRgn raises an exception if either dimension
    -- of the ellipse is empty.  We hack around this by using rectangleRegion
    -- in the problematic case (since createRectRgn behaves sensibly).
    ellipseRegion pt0 pt1
      | x0 /= x1 && y0 /= y1
      = unsafePerformIO $ do
          r <- Win32.createEllipticRgn x0 y0 x1 y1
          return (MkRegion r)
      | otherwise
      = rectangleRegion pt0 pt1
     where
      (x0,y0) = fromPoint pt0
      (x1,y1) = fromPoint pt1

    polygonRegion pts = unsafePerformIO $ do
      r <- Win32.createPolygonRgn (map fromPoint pts) Win32.wINDING
      return (MkRegion r)

    -- combine :: Win32.ClippingMode -> Region -> Region -> Region -> IO ()
    -- combine mode (MkRegion r1) (MkRegion r2) (MkRegion result) = do
    --   Win32.combineRgn result r1 r2 mode
    --   return ()

    combine :: Win32.ClippingMode -> Region -> Region -> Region
    combine mode (MkRegion r1) (MkRegion r2) = unsafePerformIO $ do
      r <- Win32.createRectRgn 0 0 0 0
      Win32.combineRgn r r1 r2 mode
      return (MkRegion r)

    regionToGraphic (MkRegion r) = mkDraw (\hdc -> Win32.paintRgn hdc r)

    intersectRegion = combine Win32.rGN_AND
    unionRegion     = combine Win32.rGN_OR
    xorRegion       = combine Win32.rGN_XOR
    subtractRegion  = combine Win32.rGN_DIFF

    #endif /* X_DISPLAY_MISSING */

    ----------------------------------------------------------------
    -- End
    ----------------------------------------------------------------