-----------------------------------------------------------------------------
-- |
-- Module      :  FRP.UISF.Graphics.Graphic
-- Copyright   :  (c) Daniel Winograd-Cort 2015
-- License     :  see the LICENSE file in the distribution
--
-- Maintainer  :  dwc@cs.yale.edu
-- Stability   :  experimental

{-# LANGUAGE BangPatterns #-}
module FRP.UISF.Graphics.Graphic (
  -- $graphics
  -- * Graphics
  Graphic(..),
  nullGraphic,
  overGraphic,
  withColor, withColor',
  text, textLines,
  ellipse, shearEllipse, line, polygon, polyline, polybezier, arc,
  circleFilled, circleOutline, rectangleFilled, rectangleOutline,
  translateGraphic, rotateGraphic, scaleGraphic,
  boundGraphic
  ) where


import FRP.UISF.Graphics.Color
import FRP.UISF.Graphics.Text
import FRP.UISF.Graphics.Types
import Control.DeepSeq

{- $graphics
This module provides an abstract representation for graphics in the GUI 
along with a rendering function specific to OpenGL.

The Graphic data type encodes an abstract graphic, which can be created 
and combined with smart constructors.  This means that Graphics are 
inherently restricted to what is possible in this abstract form.  For 
example, OpenGL may support 3D rotations, but because the abstract 
Graphic does not, they cannot be performed in UISF.

For now, we have a lean set of graphics that should satisfy most GUI 
needs.  Additionally, this layer of abstraction should make it easier 
to add more graphical back ends (perhaps a web back end in the future?).  
If UISF grows to include more graphical representations (e.g. more ways 
to render text), we can add them as necessary.

-}

------------------------------------------------------------
-- Graphic
------------------------------------------------------------

-- | The main Graphic data type stores graphic information.  
--  Constructors are not directly exposed to encourage the use of 
--  the smart constructors.
--
--  If you would like to add custom rendering functions for Graphic, 
--  you will clearly need access to the constructors to destruct the 
--  graphics.  Please request this, and I can either export them 
--  or we can discuss adding more rendering functions to this library.

data Graphic = 
    NoGraphic
  | GText Point UIText
  | GPolyLine [Point]
  | GPolygon [Point]
  | GArc Rect Angle Angle
  | GEllipse Rect
  | GBezier [Point]
  | GTranslate Point Graphic
  | GRotate Point Angle Graphic
  | GScale Double Double Graphic
  | GColor RGB Graphic
  | GBounded Rect Graphic
  | OverGraphic Graphic Graphic
  deriving (Eq, Show)

instance NFData Graphic where
  rnf NoGraphic = ()
  rnf (GText (!_,!_) str) = rnf str
  rnf (GPolyLine !pts) = ()
  rnf (GPolygon !pts) = ()
  rnf (GArc ((!_,!_),(!_,!_)) !_ !_) = ()
  rnf (GEllipse ((!_,!_),(!_,!_))) = ()
  rnf (GBezier !pts) = ()
  rnf (GTranslate (!_,!_) g) = rnf g
  rnf (GRotate (!_,!_) !_ g) = rnf g
  rnf (GScale !_ !_ g) = rnf g
  rnf (GColor !_ g) = rnf g
  rnf (GBounded ((!_,!_),(!_,!_)) g) = rnf g
  rnf (OverGraphic g1 g2) = rnf g1 `seq` rnf g2

-- | The absence of a graphic.
nullGraphic :: Graphic
nullGraphic = NoGraphic

-- | The overlay of two graphics, the first over the second.
overGraphic :: Graphic -> Graphic -> Graphic
overGraphic g1 NoGraphic = g1
overGraphic NoGraphic g2 = g2
overGraphic g1 g2 = OverGraphic g1 g2

----------
-- Text --
----------

-- | Paint the given text at the given point.
text :: UITexty s => Point -> s -> Graphic
text p = GText p . toUIText

-- | A convenience function for painting a set of (Point,String) pairs.
textLines :: UITexty s => [(Point, s)] -> Graphic
textLines = foldl (\g (p,s) -> overGraphic (text p s) g) nullGraphic

------------
-- Colors --
------------

-- | Use the given color to paint the given graphic.
withColor :: Color -> Graphic -> Graphic
withColor = withColor' . colorToRGB

-- | Use the given RGB color to paint the given graphic.
withColor' :: RGB -> Graphic -> Graphic
withColor' _ NoGraphic = NoGraphic
withColor' c g = GColor c g


------------
-- Shapes --
------------

-- | Draw an ellipse bounded by the given rectangle.
ellipse :: Rect -> Graphic
ellipse = GEllipse

-- | Draw a shear ellipse bounded by the given rectangle.  This code 
--  was written originally by Paul Liu.
shearEllipse :: Point -> Rect -> Graphic
shearEllipse (x0,y0) r = 
  let ((x1,y1), (w, h)) = normaliseRect r
      (x2,y2) = (x1 + w, y1 + h)
      x =  (x1 + x2) / 2  -- centre of parallelogram
      y =  (y1 + y2) / 2
      dx1 = (x1 - fromIntegral x0) / 2 -- distance to corners from centre
      dy1 = (y1 - fromIntegral y0) / 2
      dx2 = (x2 - fromIntegral x0) / 2
      dy2 = (y2 - fromIntegral y0) / 2
      pts = [ (round $ x + c*dx1 + s*dx2, round $ y + c*dy1 + s*dy2)
            | (c,s) <- cos'n'sins ]
      cos'n'sins = [ (cos a, sin a) | a <- segment 0 (2 * pi) (40 / (w + h))]
  in GPolygon pts

-- | Draw a line segment connecting the given two points.
line :: Point -> Point -> Graphic
line p q = GPolyLine [p,q]

-- | Draw a filled polygon with corners defined by the given points.
polygon :: [Point] -> Graphic
polygon = GPolygon

-- | Draw a sequence of line segments defined by the given points.
polyline :: [Point] -> Graphic
polyline = GPolyLine

-- | Draw a Bezier curve defined by the given points.
polybezier :: [Point] -> Graphic
polybezier = GBezier

-- | Draw an arc of the ellipse bounded by the given rectangle that 
--  starts at the first angle measure and ends at the second.
arc :: Rect -> Angle -> Angle -> Graphic
arc = GArc

-- | Draw a filled circle with given center and radius.
circleFilled :: Point -> Int -> Graphic
circleFilled (x,y) r = GEllipse ((x-r,y-r),(2*r,2*r))

-- | Draw the outline of a circle with given center and radius.
circleOutline :: Point -> Int -> Graphic
circleOutline (x,y) r = GArc ((x-r,y-r),(2*r,2*r)) 0 360

-- | Draw a filled rectangle.
rectangleFilled :: Rect -> Graphic
rectangleFilled ((x,y), (w, h)) = GPolygon [(x, y), (x + w, y), (x + w, y + h), (x, y + h)]

-- | Draw the outline of a rectangle.
rectangleOutline :: Rect -> Graphic
rectangleOutline ((x,y), (w, h)) = GPolyLine [(x, y), (x + w, y), (x + w, y + h), (x, y + h)]


---------------------
-- Transformations --
---------------------

-- | Translate the given graphic so that its origin is at the given 
--  point.
translateGraphic :: Point -> Graphic -> Graphic
translateGraphic _ NoGraphic = NoGraphic
translateGraphic p g = GTranslate p g

-- | Rotate the given graphic around the given point by the given 
--  number of degrees.
rotateGraphic :: Point -> Angle -> Graphic -> Graphic
rotateGraphic _ _ NoGraphic = NoGraphic
rotateGraphic p a g = GRotate p a g

-- | Scale the given graphic in the X and Y dimension respectively.
scaleGraphic :: Double -> Double -> Graphic -> Graphic
scaleGraphic _ _ NoGraphic = NoGraphic
scaleGraphic x y g = GScale x y g


--------------
-- Bounding --
--------------

-- | Cut the given graphic so that nothing outside of the given 
--  rectangle is visible.
boundGraphic :: Rect -> Graphic -> Graphic
boundGraphic _ NoGraphic = NoGraphic
boundGraphic r g = GBounded r g



------------------------------------------------------------
-- Helper functions
------------------------------------------------------------
normaliseRect :: Rect -> ((Double, Double),(Double, Double))
normaliseRect ((x, y), (w, h)) = ((fromIntegral x', fromIntegral y'), (fromIntegral w', fromIntegral h'))
  where (x',w') = if w < 0 then (x+w, 0-w) else (x, w)
        (y',h') = if h < 0 then (y+h, 0-h) else (y, h)

segment :: (Num t, Ord t) => t -> t -> t -> [t]
segment start stop step = ts start
  where ts i = if i >= stop then [stop] else i : ts (i + step)