{-
   Multisamp.hs (adapted from multisamp.c which is (c) Silicon Graphics, Inc.)
   Copyright (c) Sven Panne 2002-2018 <svenpanne@gmail.com>
   This file is part of HOpenGL and distributed under a BSD-style license
   See the file libraries/GLUT/LICENSE

   This program draws shows how to use multisampling to draw anti-aliased
   geometric primitives. The same display list, a pinwheel of triangles and
   lines of varying widths, is rendered twice. Multisampling is enabled when the
   left side is drawn. Multisampling is disabled when the right side is drawn.

   Pressing the 'b' key toggles drawing of the checkerboard background.
   Antialiasing is sometimes easier to see when objects are rendered over a
   contrasting background.
-}

import Control.Monad ( when )
import Data.Char ( toLower )
import Data.IORef ( IORef, newIORef )
import System.Exit ( exitWith, ExitCode(ExitSuccess) )
import Graphics.UI.GLUT

data State = State { bgToggle :: IORef Bool }

makeState :: IO State
makeState = do
   b <- newIORef True
   return $ State { bgToggle = b }

data DisplayLists = DisplayLists { pinwheelList, backgroundList :: DisplayList }

-- Print out state values related to multisampling. Create display list with
-- "pinwheel" of lines and triangles.
myInit :: IO DisplayLists
myInit = do
   clearColor $= Color4 0 0 0 0
   sb <- get sampleBuffers
   putStrLn ("number of sample buffers is " ++ show sb)
   s <- get samples
   putStrLn ("number of samples is " ++ show s)

   -- resolve overloading, not needed in "real" programs
   let color3f = color :: Color3 GLfloat -> IO ()
       vertex2f = vertex :: Vertex2 GLfloat -> IO ()

   p <- defineNewList Compile $ do
      flip mapM_ [ 0 .. 18 ] $ \i ->
         preservingMatrix $ do
            rotate (360 * fromIntegral i / 19 :: GLfloat) (Vector3 0 0 1)
            color3f (Color3 1 1 1)
            lineWidth $= fromIntegral ((i `mod` 3 :: Int) + 1)
            renderPrimitive Lines $ do
              vertex2f (Vertex2 0.25 0.05)
              vertex2f (Vertex2 0.9 0.2)
            color3f (Color3 0 1 1)
            renderPrimitive Triangles $ do
               vertex2f (Vertex2 0.25 0)
               vertex2f (Vertex2 0.9 0)
               vertex2f (Vertex2 0.875 0.1)

   b <- defineNewList Compile $ do
      color3f (Color3 1 0.5 0)
      renderPrimitive Quads $
         flip mapM_ [ 0 .. 15 ] $ \i ->
            flip mapM_ [ 0 .. 15 ] $ \j ->
               when (((i + j) `mod` 2 :: Int) == 0) $ do
                  let ii = fromIntegral i * 0.25
                      jj = fromIntegral j * 0.25
                  vertex2f (Vertex2 (-2.0  + ii) (-2.0  + jj))
                  vertex2f (Vertex2 (-2.0  + ii) (-1.75 + jj))
                  vertex2f (Vertex2 (-1.75 + ii) (-1.75 + jj))
                  vertex2f (Vertex2 (-1.75 + ii) (-2.0  + jj))

   return $ DisplayLists { pinwheelList = p, backgroundList = b }

-- Draw two sets of primitives, so that you can compare the user of
-- multisampling against its absence.
--
-- This code enables antialiasing and draws one display list and disables and
-- draws the other display list
display :: State -> DisplayLists -> DisplayCallback
display state displayLists = do
   clear [ ColorBuffer ]

   t <- get (bgToggle state)
   when t $
      callList (backgroundList displayLists)

   -- resolve overloading, not needed in "real" programs
   let translatef = translate :: Vector3 GLfloat -> IO ()

   multisample $= Enabled
   preservingMatrix $ do
      translatef (Vector3 (-1) 0 0)
      callList (pinwheelList displayLists)

   multisample $= Disabled
   preservingMatrix $ do
      translatef (Vector3 1 0 0)
      callList (pinwheelList displayLists)

   swapBuffers

reshape :: ReshapeCallback
reshape size@(Size w h) = do
   viewport $= (Position 0 0, size)
   matrixMode $= Projection
   loadIdentity
   let wf = fromIntegral w
       hf = fromIntegral h
   if w <= 2 * h
      then ortho2D (-2) 2 (-2*hf/wf) (2*hf/wf)
      else ortho2D (-2*wf/hf) (2*wf/hf) (-2) 2
   matrixMode $= Modelview 0
   loadIdentity

keyboard :: State -> KeyboardMouseCallback
keyboard state (Char c) Down _ _ = case toLower c of
   'b'   -> do bgToggle state $~ not; postRedisplay Nothing
   '\27' -> exitWith ExitSuccess
   _     -> return ()
keyboard _ _ _ _ _ = return ()

-- Main Loop: Open window with initial window size, title bar, RGBA display
-- mode, and handle input events.
main :: IO ()
main = do
   (progName, _args) <- getArgsAndInitialize
   initialDisplayMode $= [ DoubleBuffered, RGBMode, Multisampling ]
   initialWindowSize $= Size 600 300
   _ <- createWindow progName
   state <- makeState
   displayLists <- myInit
   reshapeCallback $= Just reshape
   keyboardMouseCallback $= Just (keyboard state)
   displayCallback $= display state displayLists
   mainLoop
