module FWGL.Input (
        module FWGL.Key,
        -- * FRP
        keyUp,
        keyDown,
        key,
        mouseDown,
        mouseUp,
        mouse,
        click,
        mouseMove,
        pointer,
        resize,
        size,
        custom,
        -- * Raw
        Input(..),
        InputEvent(..),
        EventData(..),
) where

import Data.Maybe
import Data.Hashable
import qualified Data.HashMap.Strict as H
import FWGL.Key
import FRP.Yampa

-- | An event.
data InputEvent = KeyUp | KeyDown | MouseUp | MouseDown | MouseMove | Resize
                  deriving (Show, Eq, Enum)

-- | The data carried by an event. They're all together in the same structure
-- because this is how it works in JavaScript.
data EventData = EventData {
        dataFramebufferSize :: Maybe (Int, Int),
        dataPointer :: Maybe (Int, Int),
        dataButton :: Maybe MouseButton,
        dataKey :: Maybe Key,
        dataTime :: Double -- ^ The unit of time is unspecified, this is only
                           -- used to determine the sequence of different
                           -- events.
}

-- | The general input.
data Input a = Input {
        inputEvents :: H.HashMap InputEvent [EventData],
        inputCustom :: a
}

instance Hashable InputEvent where
        hashWithSalt salt = hashWithSalt salt . fromEnum

-- | Keyboard release.
keyUp :: Key -> SF (Input a) (Event ())
keyUp k = evEdge KeyUp $ isKey k

-- | Keyboard press.
keyDown :: Key -> SF (Input a) (Event ())
keyDown k = evEdge KeyDown $ isKey k

-- | Keyboard down.
key :: Key -> SF (Input a) (Event ())
key k = sscan upDown NoEvent <<<
        evEdgeTime KeyUp (isKey k) &&& evEdgeTime KeyDown (isKey k)

-- | Mouse press.
mouseDown :: MouseButton -> SF (Input a) (Event (Int, Int))
mouseDown b = evEdgePointer MouseDown (isButton b) >>^ fmap snd

-- | Mouse release.
mouseUp :: MouseButton -> SF (Input a) (Event (Int, Int))
mouseUp b = evEdgePointer MouseUp (isButton b) >>^ fmap snd

-- | Mouse down.
mouse :: MouseButton -> SF (Input a) (Event (Int, Int))
mouse b = sscan upDown NoEvent <<< evEdgePointer MouseUp (isButton b) &&&
                                   evEdgePointer MouseDown (isButton b)

-- | Left click.
click :: SF (Input a) (Event (Int, Int))
click = mouseDown MouseLeft

-- | Mouse move.
mouseMove :: SF (Input a) (Event (Int, Int))
mouseMove = evPointer MouseMove (const True)

-- | Pointer location in pixels.
pointer :: SF (Input a) (Int, Int)
pointer = mouseMove >>> hold (0, 0)

-- | Window/framebuffer/canvas/etc. resize.
resize :: SF (Input a) (Event (Int, Int))
resize = evSearch Resize (isJust . dataFramebufferSize) >>^
         fmap (fromJust . dataFramebufferSize)

-- | Window/framebuffer/canvas size.
size :: SF (Input a) (Int, Int)
size = resize >>> hold (0, 0)

-- | Custom input.
custom :: SF (Input a) a
custom = arr inputCustom

{- keyDownLimited :: KeyCode a => Double -> a -> SF Input (Event ())

keyLimited :: KeyCode a => Double -> a -> SF Input (Event ()) -}

-- TODO: remove Show a
upDown :: Show a => Event a -> (Event (Double, a), Event (Double, a)) -> Event a
upDown _ (NoEvent, Event (_, x)) = Event x
upDown _ (Event _, NoEvent) = NoEvent
upDown _ (Event (t, _), Event (t', x)) | t' > t = Event x
                                       | otherwise = noEvent
upDown e _ = e

isKey :: Key -> EventData -> Bool
isKey k ed = dataKey ed == Just k

isButton :: MouseButton -> EventData -> Bool
isButton btn evData = dataButton evData == Just btn

evSearch :: InputEvent -> (EventData -> Bool) -> SF (Input a) (Event EventData)
evSearch ev bP = arr $ \inp -> case H.lookup ev $ inputEvents inp of
                                    Just bs -> eventHead $ filter bP bs
                                    Nothing -> NoEvent

evEdge :: InputEvent -> (EventData -> Bool) -> SF (Input a) (Event ())
evEdge ev bP = (evSearch ev bP >>^ isEvent) >>> edge

evEdgeData :: InputEvent -> (EventData -> Bool)
           -> SF (Input a) (Event EventData)
evEdgeData ev bP = (evSearch ev bP >>^ event Nothing Just) >>> edgeJust

evEdgeTime :: InputEvent -> (EventData -> Bool)
           -> SF (Input a) (Event (Double, ()))
evEdgeTime ev bP = evEdgeData ev bP >>^ fmap (flip (,) () . dataTime)

evEdgePointer :: InputEvent -> (EventData -> Bool)
              -> SF (Input a) (Event (Double, (Int, Int)))
evEdgePointer ev bP = evEdgeData ev bP >>^ \med -> 
                        case med of
                             Event ed ->
                                case dataPointer ed of
                                     Just ptr -> Event (dataTime ed, ptr)
                                     Nothing -> NoEvent
                             NoEvent -> NoEvent

evPointer :: InputEvent -> (EventData -> Bool)
          -> SF (Input a) (Event (Int, Int))
evPointer ev bP = evSearch ev bP >>^ \e ->
                        case e of
                             Event ed -> case dataPointer ed of
                                              Just ptr -> Event ptr
                                              _ -> NoEvent
                             NoEvent -> NoEvent

eventHead :: [a] -> Event a
eventHead [] = NoEvent
eventHead (x : _) = Event x