{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE RankNTypes #-}

module Graphics.X11.Turtle(
	-- * meta data
	xturtleVersion,

	-- * types and classes
	Field,
	Turtle,
	ColorClass,

	-- * Field functions
	-- ** meta
	openField,
	closeField,
	waitField,
	topleft,
	center,

	-- ** on events
	onclick,
	onrelease,
	ondrag,
	onmotion,
	onkeypress,
	ontimer,

	-- * Turtle functions
	-- ** meta
	newTurtle,
	killTurtle,
	inputs,
	runInputs,
	getSVG,

	-- ** move turtle
	forward,
	backward,
	goto,
	setx,
	sety,
	left,
	right,
	setheading,
	circle,
	home,
	undo,
	silentundo,
	sleep,
	flush,

	-- ** draw
	dot,
	stamp,
	beginfill,
	endfill,
	write,
	image,
	bgcolor,
	clear,

	-- ** change states
	addshape,
	beginpoly,
	endpoly,
	getshapes,
	shape,
	shapesize,
	hideturtle,
	showturtle,
	penup,
	pendown,
	pencolor,
	pensize,
	radians,
	degrees,
	speed,
	flushoff,
	flushon,

	-- ** informations
	position,
	xcor,
	ycor,
	distance,
	heading,
	towards,
	isdown,
	isvisible,
	windowWidth,
	windowHeight
) where

import Graphics.X11.Turtle.Data(shapeTable, speedTable)
import Graphics.X11.Turtle.State(
	TurtleState, direction, visible, undonum, drawed, polyPoints)
import qualified Graphics.X11.Turtle.State as S(position, degrees, pendown)
import Graphics.X11.Turtle.Input(TurtleInput(..), turtleSeries)
import Graphics.X11.Turtle.Move(
	Field, Coordinates(..), openField, closeField, waitField,
	topleft, center, coordinates, fieldSize, forkField, flushField,
	addLayer, clearLayer, addCharacter, clearCharacter, moveTurtle,
	onclick, onrelease, ondrag, onmotion, onkeypress, ontimer)
import Text.XML.YJSVG(SVG(..), Position(..), Color(..))
import qualified Text.XML.YJSVG as S(center, topleft)

import Control.Concurrent(killThread, newChan, writeChan, getChanContents)
import Control.Monad(replicateM_, zipWithM_)
import Control.Arrow((&&&))
import Data.IORef(IORef, newIORef, readIORef)
import Data.IORef.Tools(atomicModifyIORef_)
import Data.Fixed(mod')

--------------------------------------------------------------------------------

xturtleVersion :: (Int, String)
xturtleVersion = (69, "0.1.11")

--------------------------------------------------------------------------------

data Turtle = Turtle {
	field :: Field, input :: TurtleInput -> IO (),
	info :: forall a . (TurtleState -> a) -> IO a,
	shapes :: IORef [(String, [(Double, Double)])],
	inputs :: IO [TurtleInput], killTurtle :: IO ()}

class ColorClass a where getColor :: a -> Color

instance ColorClass String where getColor = ColorName

instance (Integral r, Integral g, Integral b) => ColorClass (r, g, b) where
	getColor (r, g, b) =
		RGB (fromIntegral r) (fromIntegral g) (fromIntegral b)

--------------------------------------------------------------------------------

newTurtle :: Field -> IO Turtle
newTurtle f = do
	index <- newIORef 1; shapesRef <- newIORef shapeTable
	chan <- newChan; hist <- getChanContents chan
	let states = turtleSeries hist
	l <- addLayer f; c <- addCharacter f
	thr <- forkField f $ zipWithM_ (moveTurtle f c l) states $ tail states
	let t = Turtle {
		field = f,
		input = (atomicModifyIORef_ index succ >>) . writeChan chan,
		info = \n -> fmap (n . (states !!)) $ readIORef index,
		shapes = shapesRef,
		inputs = fmap (flip take hist . pred) $ readIORef index,
		killTurtle = flushField f True $
			clearLayer l >> clearCharacter c >> killThread thr}
	shape t "classic" >> input t (Undonum 0) >> return t

runInputs :: Turtle -> [TurtleInput] -> IO ()
runInputs = mapM_ . input

getSVG :: Turtle -> IO [SVG]
getSVG = fmap reverse . flip info drawed

convertPosition :: Turtle -> Position -> IO Position
convertPosition t p = do
	(w, h) <- windowSize t
	coord <- coordinates $ field t
	return $ case coord of
		CoordCenter -> S.center w h p
		CoordTopLeft -> S.topleft w h p

--------------------------------------------------------------------------------

forward, backward :: Turtle -> Double -> IO ()
forward t = input t . Forward
backward t = forward t . negate

goto :: Turtle -> Double -> Double -> IO ()
goto t@Turtle{field = f} x y = do
	coord <- coordinates f
	input t $ Goto $ case coord of
		CoordCenter -> Center x y
		CoordTopLeft -> TopLeft x y

setx, sety :: Turtle -> Double -> IO ()
setx t x = do
	pos <- info t S.position >>= convertPosition t
	input t $ Goto $ case pos of
		Center _ y -> Center x y
		TopLeft _ y -> TopLeft x y
sety t y = do
	pos <- info t S.position >>= convertPosition t
	input t $ Goto $ case pos of
		Center x _ -> Center x y
		TopLeft x _ -> TopLeft x y

left, right, setheading :: Turtle -> Double -> IO ()
left t = input t . TurnLeft
right t = left t . negate
setheading t = input t . Rotate

circle :: Turtle -> Double -> IO ()
circle t r = do
	deg <- info t S.degrees
	forward t (r * pi / 36)
	left t (deg / 36)
	replicateM_ 35 $ forward t (2 * r * pi / 36) >> left t (deg / 36)
	forward t (r * pi / 36)
	input t $ Undonum 74

home :: Turtle -> IO ()
home t = goto t 0 0 >> setheading t 0 >> input t (Undonum 3)

undo :: Turtle -> IO ()
undo t = info t undonum >>= flip replicateM_ (input t Undo)

silentundo :: Turtle -> Int -> IO ()
silentundo t n = input t $ SilentUndo n

sleep :: Turtle -> Int -> IO ()
sleep t = input t . Sleep

flush :: Turtle -> IO ()
flush = (`input` Flush)

--------------------------------------------------------------------------------

dot :: Turtle -> Double -> IO ()
dot t = input t . Dot

stamp :: Turtle -> IO ()
stamp = (`input` Stamp)

beginfill, endfill :: Turtle -> IO ()
beginfill = (`input` SetFill True)
endfill = (`input` SetFill False)

write :: Turtle -> String -> Double -> String -> IO ()
write t fnt sz = input t . Write fnt sz

image :: Turtle -> FilePath -> Double -> Double -> IO ()
image t fp = curry $ input t . uncurry (PutImage fp)

bgcolor :: ColorClass c => Turtle -> c -> IO ()
bgcolor t = input t . Bgcolor . getColor

clear :: Turtle -> IO ()
clear = (`input` Clear)

--------------------------------------------------------------------------------

addshape :: Turtle -> String -> [(Double, Double)] -> IO ()
addshape t n s = atomicModifyIORef_ (shapes t) ((n, s) :)

beginpoly :: Turtle -> IO ()
beginpoly = (`input` SetPoly True)

endpoly :: Turtle -> IO [(Double, Double)]
endpoly t = input t (SetPoly False) >> info t polyPoints >>=
	mapM (fmap (posX &&& posY) . convertPosition t)

getshapes :: Turtle -> IO [String]
getshapes = fmap (map fst) . readIORef . shapes

shape :: Turtle -> String -> IO ()
shape t n = readIORef (shapes t) >>=
	maybe (putStrLn $ "no shape named " ++ n) (input t . Shape) . lookup n

shapesize :: Turtle -> Double -> Double -> IO ()
shapesize t = curry $ input t . uncurry Shapesize

hideturtle, showturtle :: Turtle -> IO ()
hideturtle = (`input` SetVisible False)
showturtle = (`input` SetVisible True)

penup, pendown :: Turtle -> IO ()
penup = (`input` SetPendown False)
pendown = (`input` SetPendown True)

pencolor :: ColorClass c => Turtle -> c -> IO ()
pencolor t = input t . Pencolor . getColor

pensize :: Turtle -> Double -> IO ()
pensize t = input t . Pensize

radians :: Turtle -> IO ()
radians = (`degrees` (2 * pi))

degrees :: Turtle -> Double -> IO ()
degrees t = input t . Degrees

speed :: Turtle -> String -> IO ()
speed t str = case lookup str speedTable of
	Just (ps, ds) -> do
		input t $ PositionStep ps
		input t $ DirectionStep ds
		input t $ Undonum 3
	Nothing -> putStrLn "no such speed"

flushoff, flushon :: Turtle -> IO ()
flushoff = (`input` SetFlush False)
flushon = (`input` SetFlush True)

--------------------------------------------------------------------------------

position :: Turtle -> IO (Double, Double)
position t = fmap (posX &&& posY) $ info t S.position >>= convertPosition t

xcor, ycor :: Turtle -> IO Double
xcor = fmap fst . position
ycor = fmap snd . position

distance :: Turtle -> Double -> Double -> IO Double
distance t x0 y0 = do
	(x, y) <- position t
	return $ ((x - x0) ** 2 + (y - y0) ** 2) ** (1 / 2)

heading :: Turtle -> IO Double
heading t = do
	deg <- info t S.degrees
	dir <- fmap (* (deg / (2 * pi))) $ info t direction
	return $ dir `mod'` deg

towards :: Turtle -> Double -> Double -> IO Double
towards t x0 y0 = do
	(x, y) <- position t
	deg <- info t S.degrees
	let	dir = atan2 (y0 - y) (x0 - x) * deg / (2 * pi)
	return $ if dir < 0 then dir + deg else dir

isdown, isvisible :: Turtle -> IO Bool
isdown = flip info S.pendown
isvisible = flip info visible

windowWidth, windowHeight :: Turtle -> IO Double
windowWidth = fmap fst . windowSize
windowHeight = fmap snd . windowSize

windowSize :: Turtle -> IO (Double, Double)
windowSize = fieldSize . field