{-# LANGUAGE UnicodeSyntax #-}
-- | Usage: Hit /q/ to abort the match
module SoccerFun.UI.GL where

import qualified Graphics.UI.GLUT as GL
import Graphics.UI.GLUT (($=), get)
import Data.IORef
import Control.Monad
import Unsafe.Coerce
import System.Exit

import SoccerFun.MatchControl
import SoccerFun.Types
import SoccerFun.Geometry
import SoccerFun.Referee.Ivanov
import SoccerFun.RefereeAction
import System.Random
import SoccerFun.MatchGame
import SoccerFun.Player
import SoccerFun.Team (Team)
import Data.Maybe
import SoccerFun.Field
import SoccerFun.Ball

gameLength  TimeUnit
gameLength = 1

runMatch  (Home  Field  Team)  (Home  Field  Team)  IO ()
runMatch t1 t2 = do
	let
		field = Field 70 105
		team1 = t1 West field
		team2 = t2 East field
	match  liftM (setMatchStart team1 team2 field (ivanovReferee field team1 team2) 1) newStdGen
	(prog,args)  GL.getArgsAndInitialize
--	GL.initialDisplayCapabilities $= [GL.With GL.DisplayDouble, GL.With GL.DisplaySamples]
--	GL.multisample $= GL.Enabled
	GL.initialDisplayMode $= [GL.DoubleBuffered, GL.Multisampling]
	p  get GL.displayModePossible
	when (not p) $ do
		GL.initialDisplayMode $= [GL.DoubleBuffered]
		p  get GL.displayModePossible
		when (not p) $ GL.initialDisplayMode $= []
	window  GL.createWindow "SoccerFun"
	GL.clearColor $= (GL.Color4 0 0.5 0 0  GL.Color4 GL.GLclampf) -- green
	aspect  newIORef 1
	registerCallbacks window aspect =<< newIORef match
	GL.cursor $= GL.LeftArrow
	GL.mainLoop
	GL.exit

registerCallbacks  GL.Window  IORef GL.GLdouble  IORef Match  IO ()
registerCallbacks window aspect match = do
	GL.displayCallback $= display
	GL.reshapeCallback $= Just reshape
	GL.keyboardMouseCallback $= Just inputCallback
	gameLoop
--	GL.keyboardMouseCallback $= Just inputCallback
	where

	inputCallback  GL.Key  GL.KeyState  GL.Modifiers  GL.Position  IO ()
	inputCallback (GL.Char 'q') _ _ _ = exitSuccess
	inputCallback _ _ _ _ = return ()

	gameLoop = do
		((refereeActions,playerWithActions),match')  liftM stepMatch $ readIORef match
		writeIORef match match'
		GL.postRedisplay $ Just window
		let actions = mapMaybe logRefereeAction refereeActions
		when (not $ GameOver `elem` refereeActions) $ do
			if null actions
				then GL.addTimerCallback 30 gameLoop
				else GL.addTimerCallback 1000 gameLoop
		mapM_ putStrLn actions

	reshape s@(GL.Size w h) = do
		writeIORef aspect newAspect
		GL.viewport $= (GL.Position 0 0, s)
		GL.matrixMode $= GL.Projection
		GL.loadIdentity
		GL.perspective 0 newAspect (-1) 1
		GL.matrixMode $= GL.Modelview 0
		where newAspect = fromIntegral w / fromIntegral (max 1 h)

	display = do
		m@Match {theField = field, team1 = t1, team2 = t2, theBall = ball, score = score, playingTime = time}  readIORef match
		GL.clear [GL.ColorBuffer]
		GL.loadIdentity
		a  readIORef aspect
		if a < 1 then GL.ortho2D (-1) 1 (-1/a) (1/a) else GL.ortho2D (-1*a) (1*a) (-1) 1
		let zoom = convertFloat $ 2.1 / flength field
		GL.scale zoom zoom 1
		GL.translate $ vector2 (-flength field/2,-fwidth field/2)

		renderStatus m
		GL.lineWidth $= 3
		renderField field
		GL.lineWidth $= 2
		renderBall ball m
		colorRGB (1,0,0) -- red
		mapM_ renderPlayer t1
		colorRGB (0,0,1) -- blue
		mapM_ renderPlayer t2

		GL.swapBuffers

	renderStatus Match {theField = field, team1 = t1, team2 = t2, theBall = ball, score = score, playingTime = time} = do
		colorRGB (0,0,0) -- black
		GL.preservingMatrix $ do
			GL.translate $ vector2 (0, fwidth field + 2)
			GL.lineWidth $= 2
			drawStatus $ show (fst score) ++ ":" ++ show (snd score) ++ "   " ++ show (round $ (1 - time) * 90) ++ ":00"

	renderField field@Field {flength = l, fwidth = w} = do
		colorRGB (0.8,1,0.8) -- green-white
		GL.renderPrimitive GL.LineLoop $ do -- side line
			vertex2 (0,0)
			vertex2 (l,0)
			vertex2 (l,w)
			vertex2 (0,w)
		GL.renderPrimitive GL.LineStrip $ do -- middle line
			vertex2 (l/2,0)
			vertex2 (l/2,w)
		GL.preservingMatrix $ do -- centre circle and centre spot
			GL.translate $ vector2 (l/2,w/2)
			GL.renderPrimitive GL.LineLoop $ circle radiusCentreCircle 23
			GL.renderPrimitive GL.Polygon $ circle radiusCentreSpot 7
		colorRGB (1,1,1) -- white
		mapM_ renderPole [(x,y) | let (n,s) = goalPoles field, y  [n,s], x  [0,l]] -- goal poles
		GL.preservingMatrix $ do
			GL.translate $ vector2 (penaltySpotDepth, w/2)
			GL.renderPrimitive GL.Polygon $ circle radiusPenaltySpot 7
		GL.preservingMatrix $ do
			GL.translate $ vector2 (l - penaltySpotDepth, w/2)
			GL.renderPrimitive GL.Polygon $ circle radiusPenaltySpot 7

	renderPole pos = GL.preservingMatrix $ do
		colorRGB (0.3,0.3,0.3)
		GL.translate $ vector2 pos
		GL.renderPrimitive GL.Polygon (circle goalPoleWidth 7)

	renderBall ball match = do
		let p = case ball of
			GainedBy pid  toPosition3D $ pos $ fromJust $ lookupPlayer pid match
			Free Ball {ballPos = p}  p
		colorRGB (1,1,1) -- white
		drawAt3D p $ GL.renderPrimitive GL.Polygon (circle radiusBall 7)

	renderPlayer Player {pos = p, playerID = id} = drawAt p $ do
		square
		drawString $ show $ playerNo id

colorRGB  (GL.GLfloat,GL.GLfloat,GL.GLfloat)  IO ()
colorRGB (r,g,b) = GL.color $ GL.Color3 r g b

--dot3D ∷ Position3D → IO ()
--dot3D pos3D = dot $ pxy pos3D

drawAt3D  Position3D  IO ()  IO ()
drawAt3D pos = drawAt (pxy pos)

drawAt  Position  IO ()  IO ()
drawAt pos draw = GL.preservingMatrix $ do
	GL.translate $ vector2 (px pos, py pos)
	draw

square  IO ()
square = GL.preservingMatrix $ do
	GL.renderPrimitive GL.LineLoop $ do
		vertex2 (-0.7,-0.7)
		vertex2 (0.7,-0.7)
		vertex2 (0.7,0.7)
		vertex2 (-0.7,0.7)

vector2  (Float,Float)  GL.Vector3 GL.GLfloat
vector2 (x,y) = GL.Vector3 (convertFloat x) (convertFloat y) 0

vertex2  (Float,Float)  IO ()
vertex2 (x,y) = GL.vertex $ GL.Vertex2 (convertFloat x) (convertFloat y)

circle r = oval r r

oval r1 r2 step = mapM_ vertex2 vs where
	is = take (truncate step + 1) [0, i' .. ]
	i' = 2 * pi / step
	vs = [ (r1 * cos i, r2 * sin i) | i <- is ]

{-
drawPort pos = GL.preservingMatrix $ do
	GL.translate $ vector pos
	GL.renderPrimitive GL.Polygon (circle 0.15 0.15 10)

drawNode label = do
	GL.renderPrimitive GL.LineLoop (circle 1 1 20)
	drawString label
-}

drawString label = GL.preservingMatrix $ do
	GL.translate $ GL.Vector3 (-0.3) (-0.3) (0  GL.GLfloat)
	GL.scale 0.007 0.007 (0  GL.GLdouble)
	GL.renderString GL.MonoRoman label

drawStatus label = GL.preservingMatrix $ do
	GL.translate $ GL.Vector3 (-0.3) (-0.3) (0  GL.GLfloat)
	GL.scale 0.03 0.03 (0  GL.GLdouble)
	GL.renderString GL.MonoRoman label

convertDouble  Double  GL.GLdouble
convertDouble = unsafeCoerce

convertFloat  Float  GL.GLfloat
convertFloat = unsafeCoerce