module Render where

import Model
import Graphics.Rendering.OpenGL hiding (Rect)
import Control.Monad
import qualified Data.Set as Set

type Point = Vertex2 GLdouble
type Rect = (Point,Point)

data Geometry = Geometry {
    g_size :: Size,
    g_tbox :: (StepID,ChannelID) -> Rect,
    g_lbox :: LoopID -> Rect,
    g_bwidth :: GLdouble,
    g_bheight :: GLdouble,
    g_edgeMargin :: GLdouble,
    g_gap :: GLdouble
}

geometry :: Size -> Model -> Geometry
geometry sz@(Size w h) m = Geometry {
    g_size = sz,
    g_tbox = boxfn,
    g_lbox = \l -> boxfn ((m_stepRange m - m_loopRange m) `div` 2 + l,channels), 
    g_bwidth = bw,
    g_bheight = bh,
    g_edgeMargin = edgeMargin,
    g_gap = gap
    }
  where
    r = (Vertex2 0 0, Vertex2 (fi w) (fi h))
    boxfn (s,c) = (Vertex2 x y, Vertex2 (x+bw) (y+bh))
      where
        x = edgeMargin + (fi s) * (gap+bw)
        y = edgeMargin + (fi c) * (gap+bh)
    bw = let n = fi (m_stepRange m) in (width r - 2*edgeMargin - (n-1)*gap)/ n
    bh = let n = fi (length (m_channels m) + 1) in (height r - 2*edgeMargin - (n-1)*gap)/ n
    edgeMargin = 10
    gap = 10
    channels = length (m_channels m)

data Picture = LineRect (Color3 Double) GLdouble Rect
             | FillRect (Color3 Double) GLdouble Rect
             | WithTranslation (Vector3 Double) Picture
             | Over Picture Picture

overlay :: [Picture] -> Picture
overlay = foldl1 Over

picture :: Geometry -> Time -> Model -> Picture
picture g t m = overlay [drawLoopButtons,drawTriggerButtons,drawTimeMarker]
  where 
    steps = m_stepRange m
    stepTime = m_stepTime m
    period = steps * stepTime
    periodi = periodti t m

    em = g_edgeMargin g
    gap = g_gap g
    channels = length (m_channels m)

    drawTriggerButtons =
      case loopTransition t m of
        Nothing -> buttons
        Just (k,ploop) ->
          let size = (g_bheight g + gap) * fi channels
              pbuttons = overlay (map drawButton (loopTriggers ploop m))  in
              WithTranslation (Vector3 0 (k * size) 0) pbuttons
              `Over`
              WithTranslation (Vector3 0 (k * size -size) 0) buttons
      where
        buttons = overlay (map drawButton (loopTriggers (m_loop m) m))


    loopTransition t m = do
        (t0,lid) <- m_prevLoop m
        let td = t - t0
        let tt = 2 * m_stepTime m
        if  td < tt
            then Just (fi td/fi tt,lid)
            else Nothing
       
    drawTimeMarker = FillRect mcolor radius box
      where
        box = (Vertex2 x y, Vertex2 (x+w) (y+h))
        x,y :: GLdouble
        x =  em + fi periodi / fi period * (bwidth+gap) * fi steps
        w = bwidth+gap
        y =  em - gap/2
        h = fi channels*(bheight+gap)

    drawButton (l,s,c) = if active
                           then FillRect (bcolorf s) radius bbox
                           else LineRect bcolor1 radius bbox
      where
        bbox = g_tbox g (s,c)
        active = Set.member (l,s,c) (m_triggers m)

    drawLoopButtons = overlay (map drawLoopButton [0..m_loopRange m - 1])

    drawLoopButton l = if l == m_loop m
            then FillRect bcolor2a radius bbox
            else LineRect bcolor1 radius  bbox `Over`
                 FillRect (Color3 0 0 0) radius bbox
      where
        bbox = g_lbox g l

    radius = 5
    mcolor = Color3 0.5 0.5 0.5
    bcolor1 = Color3 0 0.5 0.5
    bcolor2a = Color3 0.0 0.8 0.8
    bcolor2b = Color3 1.0 0.5 0.5
    bcolorf s = if tx >= 0 && tx <= fade
                  then blendc (fi tx / fi fade) bcolor2a bcolor2b
                  else bcolor2a
      where tx = periodi - s * stepTime
            fade = stepTime * 4

    (bwidth,bheight) = (g_bwidth g, g_bheight g)

fi :: (Num b, Integral a) => a -> b
fi = fromIntegral

width, height :: Rect -> GLdouble
width  (Vertex2 v1 _,Vertex2 v2 _) = v2 - v1
height (Vertex2 _ v1,Vertex2 _ v2) = v2 - v1

blendc :: GLdouble -> Color3 GLdouble -> Color3 GLdouble -> Color3 GLdouble
blendc f (Color3 r1 g1 b1) (Color3 r2 g2 b2) =
  let f' = 1-f in Color3 (f*r1+f'*r2) (f*g1+f'*g2) (f*b1+f'*b2)

inBox :: Point -> Rect -> Bool
inBox (Vertex2 x y) (Vertex2 x0 y0, Vertex2 x1 y1)  = 
    (x >= x0) && (x <= x1) && (y >= y0) && (y <= y1)

click:: Time -> Point -> Geometry -> Model -> Model
click t p g = (loops.triggers)
  where
    triggers m = foldr (tfn g) m (loopTriggers (m_loop m) m)
    tfn g t@(l,s,c) m = if inBox p (g_tbox g (s,c))
                          then updateTrigger not t m
                          else m

    loops m = foldr (lfn g) m [0..m_loopRange m-1]
    lfn g l m = if inBox p (g_lbox g l)
                then m{m_loop=l,m_prevLoop=Just (t,m_loop m)}
                else m

-- Here's the impure code that actually makes the openGL calls.
render :: Picture -> IO ()
render (FillRect c r rect) = renderPrimitive Polygon (roundedRectPath c r rect)
render (LineRect c r rect) = renderPrimitive LineLoop (roundedRectPath c r rect)
render (WithTranslation v p) = preservingMatrix $ (translate v >> render p)
render (Over p1 p2 ) = render p2 >> render p1

roundedRectPath :: Color3 GLdouble -> GLdouble -> Rect -> IO ()
roundedRectPath c r (Vertex2 x1 y1,Vertex2 x2 y2) = do
        color $ c
        vertex $ Vertex2 (x1+r) y1
        vertex $ Vertex2 (x2-r) y1
        vertex $ Vertex2 x2 (y1+r)
        vertex $ Vertex2 x2 (y2-r)
        vertex $ Vertex2 (x2-r) y2
        vertex $ Vertex2 (x1+r) y2
        vertex $ Vertex2 x1 (y2-r)
        vertex $ Vertex2 x1 (y1+r)

