{-# LANGUAGE RecursiveDo #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleContexts #-}
{-# OPTIONS_GHC -fmax-simplifier-iterations=5 -ddump-simpl -ddump-to-file -dsuppress-coercions -dsuppress-idinfo #-}
import Control.Monad.State
import Data.Monoid
import Data.IntMap (IntMap, assocs, elems, empty, fromList, size, singleton)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Vector (Vector)
import qualified Data.Vector as Vector
import Reflex.Dom
import Data.FastMutableIntMap
import System.Random

type RNG = StdGen
type Entropy   = Int
type RowNumber = Int
type RowIndex  = Int
data Row   = Row { num :: RowNumber, txt :: Text, selected :: Bool } deriving (Eq, Show)
type Table = IntMap Row
data Model = Model { rng :: RNG, nextNum :: RowNumber, selection :: Maybe Row } deriving Show
type TableDiff = PatchIntMap Row

main :: IO ()
main = do
  seed <- randomIO
  mainWidgetWithHead' (\_ -> headW, \_ -> bodyW seed)

titleW :: DomBuilder t m => m ()
titleW = text "Reflex-dom keyed"

headW :: DomBuilder t m => m ()
headW = do
  el "title" titleW
  elAttr "link" ("href" =: "/css/currentStyle.css" <> "rel" =: "stylesheet") blank

bodyW :: forall t m. (MonadFix m, MonadHold t m, DomBuilder t m) => Entropy -> m ()
bodyW seed = divClass "main" $ divClass "container" $ mdo
  buttonEvents <- divClass "jumbotron" $ divClass "row" $ do
    divClass "col-md-6" $ el "h1" titleW
    divClass "col-md-6" $ divClass "row" $ sequence
      [ buttonW "run"      "Create 1,000 rows"     $ resetRows 1000
      , buttonW "runlots"  "Create 10,000 rows"    $ resetRows 10000
      , buttonW "add"      "Append 1,000 rows"     $ appendRows 1000
      , buttonW "update"   "Update every 10th row" $ updateRows (\i -> mod i 10 == 0) (<> " !!!")
      , buttonW "clear"    "Clear"                 $ clearRows
      , buttonW "swaprows" "Swap Rows"             $ swapRows (1, 998)
      ]

  let initial = Model { rng = mkStdGen seed, nextNum = 1, selection = Nothing }
  let events = leftmost $ fmap (foldl1 sequenceSteps) rowEvents : buttonEvents

  rowEvents <- tableW dynMT

  dynMT <- mapAccum_ step (initial, empty) events

  elAttr "span" ("class" =: "preloadicon glyphicon glyphicon-remove" <> "aria-hidden" =: "true") blank

  blank

type Step = (Model, Table) -> (Model, TableDiff)

sequenceSteps :: Step -> Step -> Step
sequenceSteps a b (m, t) =
  let (ma, tda) = a (m, t)
      ta = applyAlways tda t
      (mb, tdb) = b (ma, ta)
  in (mb, tdb <> tda)

step :: (Model, Table) -> Step -> ((Model, Table), TableDiff)
step (m, t) f = ((m', t'), dt)
  where
    (m', dt) = f (m, t)
    t' = applyAlways dt t

buttonW :: DomBuilder t m => Text -> Text -> a -> m (Event t a)
buttonW id txt val = divClass "col-sm-6 smallpad" $ buttonW' ("class" =: "btn btn-primary btn-block" <> "id" =: id)
  where
    buttonW' attrs = do
      (b, _) <- elAttr' "button" attrs $ text txt
      pure $ val <$ domEvent Click b

tableW :: (MonadHold t m, DomBuilder t m) => Event t TableDiff -> m (Event t (IntMap Step))
tableW diff = do
  elClass "table" "table table-hover table-striped test-data" $ do
    el "tbody" $ do
      (v0, v') <- traverseIntMapWithKeyWithAdjust (\_ -> rowW) empty diff
      rowEvents <- holdIncremental v0 v'
      return $ mergeIntIncremental rowEvents

rowW :: DomBuilder t m => Row -> m (Event t Step)
rowW row = elClass "tr" (if selected row then "danger" else "") $
  do
    elClass "td" "col-md-1" $ do
      text $ T.pack $ show $ num row
    (sel, _) <- elClass' "td" "col-md-4". el "a" . text $ txt row
    (del, _) <- elClass' "td" "col-md-1" deleteW
    elClass "td" "col-md-6" blank
    pure . leftmost $ zipWith tagClick [selectRow, deleteRow] [sel, del]
  where
    tagClick f el = f row <$ (domEvent Click el)

deleteW :: DomBuilder t m => m ()
deleteW = el "a" $ elAttr "span" ("aria-hidden" =: "true" <> "class" =: "glyphicon glyphicon-remove") blank


{- Domain logic -}
clearRows :: (Model, Table) -> (Model, TableDiff)
clearRows (m, t) = (m { selection = Nothing }, PatchIntMap $ Nothing <$ t)

updateRows :: (RowIndex -> Bool) -> (Text -> Text) -> (Model, Table) -> (Model, TableDiff)
updateRows p f (m, t) = (m, PatchIntMap $ Just . update <$> targets)
  where
    targets = fromList . filterByIndex p . assocs $ t
    update  = \r -> r { txt = f $ txt r }

filterByIndex :: (Int -> Bool) -> [a] -> [a]
filterByIndex p = map snd . filter (\(i, _) -> p i) . zip [0..]

swapRows :: (RowIndex, RowIndex) -> (Model, Table) -> (Model, TableDiff)
swapRows (a, b) (m, t) = (m, PatchIntMap $ if max a b < length t then swap else empty)
  where
    swap = point a b <> point b a
    point x y = singleton (fst (val x)) $ Just $ snd $ val y
    val = (assocs t !!)

selectRow :: Row -> (Model, Table) -> (Model, TableDiff)
selectRow r (m, t) = (m { selection = Just r}, PatchIntMap $ dr <> ds)
  where
    dr = sel True r
    ds = maybe empty (sel False) $ selection m
    sel b r' = singleton (num r') $ Just r' { selected = b }

deleteRow :: Row -> (Model, Table) -> (Model, TableDiff)
deleteRow r (m, t) = (m { selection = mfilter (/= r) (selection m) }, PatchIntMap $ singleton (num r) Nothing)

resetRows :: Int -> (Model, Table) -> (Model, TableDiff)
resetRows n (m, t) = (m'', dt <> dt')
  where
    (m',  dt ) = addRows n (m, t)
    (m'', dt') = clearRows (m', t)

appendRows :: Int -> (Model, Table) -> (Model, TableDiff)
appendRows n (m, t) = addRows n (m, t)

addRows :: Int -> (Model, Table) -> (Model, TableDiff)
addRows count (m, t) = (m { rng = rng', nextNum = nextNum' }, PatchIntMap diff)
  where
    rowsST = sequence (rowST <$ [0..count-1])
    (rows, (nextNum', rng')) = runState rowsST (nextNum m, rng m)
    diff = fromList . fmap (\r -> (num r, Just r)) $ rows

rowST :: State (RowNumber, RNG) Row
rowST  = state (\(n, g) -> let (e, g') = next g in (Row {num = n, txt = randomName e, selected = False}, (n+1, g')))

randomName :: Entropy -> Text
randomName e = T.intercalate " " $ (`modIndex` e) <$> [adjectives, colours, nouns]

-- | Index into a Vector, modulo its length
--
-- WARNING: The vector must have length >= 1, and the index must be >= 0.
modIndex :: Vector a -> Int -> a
modIndex v n = Vector.unsafeIndex v $ n `mod` Vector.length v

adjectives, colours, nouns :: Vector Text
adjectives = Vector.fromList
  [ "pretty"
  , "large"
  , "big"
  , "small"
  , "tall"
  , "short"
  , "long"
  , "handsome"
  , "plain"
  , "quaint"
  , "clean"
  , "elegant"
  , "easy"
  , "angry"
  , "crazy"
  , "helpful"
  , "mushy"
  , "odd"
  , "unsightly"
  , "adorable"
  , "important"
  , "inexpensive"
  , "cheap"
  , "expensive"
  , "fancy"
  ]
colours = Vector.fromList
  [ "red"
  , "yellow"
  , "blue"
  , "green"
  , "pink"
  , "brown"
  , "purple"
  , "brown"
  , "white"
  , "black"
  , "orange"
  ]
nouns = Vector.fromList
  [ "table"
  , "chair"
  , "house"
  , "bbq"
  , "desk"
  , "car"
  , "pony"
  , "cookie"
  , "sandwich"
  , "burger"
  , "pizza"
  , "mouse"
  , "keyboard"
  ]