{-# LANGUAGE DeriveDataTypeable #-}

module Picture
  ( Picture(..)
  , Point
  , Vector
  , Path
  , Source(..)
        -- * Compound shapes
  , lineLoop
  , sectorWire
  , rectanglePath
  , rectangleWire
  , rectangleSolid
  )
where

import           Data.Monoid
import           Data.Semigroup
import           Data.Foldable
import           Color
import           Text

-- | A point on the x-y plane.
type Point = (Double, Double)

-- | A vector can be treated as a point, and vis-versa.
type Vector = Point

-- | A path through the x-y plane.
type Path = [Point]

-- | An image location
data Source =
  -- | Path to an image inside ./images. 
  File String
  -- | An image url.
  | Url String
  deriving (Show, Eq)

-- | A 2D picture
data Picture
        -- | A blank picture, with nothing in it.
        = Blank
        -- | A line along an arbitrary path.
        | Line          Path
        -- | A polygon filled with a solid color.
        | Polygon       Path
        -- | A circle with the given radius.
        | Circle        Double
        -- | A circular arc drawn counter-clockwise between two angles
        --  (in degrees) at the given radius.
        | Arc           Double Double Double
        -- | A rectangle drawn with given width and height.
        | Rectangle     Double Double
        -- | Image to draw from a certain with given width and height.
        | Image         Source Double Double
        -- | Some text to draw with a vector font.
        | Text          String Font FontSize
        -- | A picture drawn with this color.
        | Color     Color Picture
        -- | A picture drawn with this stroke, given a color and size.
        | Stroke        Color Double Picture
        -- | A picture translated by the given x and y coordinates.
        | Translate     Double Double Picture
        -- | A picture scaled by the given x and y factors.
        | Scale         Double Double Picture
        -- | A picture consisting of several others.
        | Pictures      [Picture]
        deriving (Show, Eq)


-- Instances ------------------------------------------------------------------
instance Monoid Picture where
  mempty = Blank
  mappend a b = Pictures [a, b]
  mconcat = Pictures

instance Semigroup Picture where
  a <> b = Pictures [a, b]
  sconcat = Pictures . toList
  stimes  = stimesIdempotent

-- Other Shapes ---------------------------------------------------------------
-- | A closed loop along a path.
lineLoop :: Path -> Picture
lineLoop []       = Line []
lineLoop (x : xs) = Line ((x : xs) ++ [x])


-- | A wireframe sector of a circle.
--   An arc is draw counter-clockwise from the first to the second angle at
--   the given radius.
sectorWire :: Double -> Double -> Double -> Picture
sectorWire a1 a2 r_ =
  let r = abs r_
  in  Pictures
        [ Arc a1 a2 r
        , Line [(0, 0), (r * cos (degToRad a1), r * sin (degToRad a1))]
        , Line [(0, 0), (r * cos (degToRad a2), r * sin (degToRad a2))]
        ]


-- Rectangles -----------------------------------------------------------------

-- | A path representing a rectangle centered about the origin
rectanglePath
  :: Double        -- ^ width of rectangle
  -> Double        -- ^ height of rectangle
  -> Path
rectanglePath sizeX sizeY =
  let sx = sizeX / 2
      sy = sizeY / 2
  in  [(-sx, -sy), (-sx, sy), (sx, sy), (sx, -sy)]


-- | A wireframe rectangle centered about the origin.
rectangleWire :: Double -> Double -> Picture
rectangleWire sizeX sizeY = lineLoop $ rectanglePath sizeX sizeY


-- | A solid rectangle centered about the origin.
rectangleSolid
  :: Double         -- ^ width of rectangle
  -> Double         -- ^ height of rectangle
  -> Picture
rectangleSolid sizeX sizeY = Polygon $ rectanglePath sizeX sizeY

-- | Convert degrees to radians
degToRad :: Double -> Double
degToRad d = d * pi / 180
{-# INLINE degToRad #-}


-- | Convert radians to degrees
radToDeg :: Double -> Double
radToDeg r = r * 180 / pi
{-# INLINE radToDeg #-}


-- | Normalize an angle to be between 0 and 2*pi radians
normalizeAngle :: Double -> Double
normalizeAngle f = f - 2 * pi * floor' (f / (2 * pi))
 where
  floor' :: Double -> Double
  floor' x = fromIntegral (floor x :: Int)