module Gargantext.Components.Table where

import Data.Array (filter)
import Data.Maybe (Maybe(..), maybe)
import Data.Either (Either(..))
import Data.Generic.Rep (class Generic)
import Data.Generic.Rep.Show (genericShow)
import Effect (Effect)
import Effect.Class (liftEffect)
import React (ReactElement, ReactClass, Children, createElement)
import React.DOM (a, b, b', p, i, h3, hr, div, option, select, span, table, tbody, td, text, th, thead, tr)
import React.DOM.Props (className, href, onChange, onClick, scope, selected, value, style)
import Thermite (PerformAction, Render, Spec, modifyState_, simpleSpec, StateCoTransformer, createClass)
import Unsafe.Coerce (unsafeCoerce)

import Gargantext.Prelude

type TableContainerProps =
  { pageSizeControl     :: ReactElement
  , pageSizeDescription :: ReactElement
  , paginationLinks     :: ReactElement
  , tableHead           :: ReactElement
  , tableBody           :: Array ReactElement
  }

type Rows = Array { row    :: Array ReactElement
                  , delete :: Boolean
                  }

type OrderBy = Maybe (OrderByDirection ColumnName)

type Params = { offset :: Int, limit :: Int, orderBy :: OrderBy }

newtype ColumnName = ColumnName String

derive instance genericColumnName :: Generic ColumnName _

instance showColumnName :: Show ColumnName where
  show = genericShow

derive instance eqColumnName :: Eq ColumnName

columnName :: ColumnName -> String
columnName (ColumnName c) = c

data OrderByDirection a = ASC a | DESC a

derive instance genericOrderByDirection :: Generic (OrderByDirection a) _

instance showOrderByDirection :: Show a => Show (OrderByDirection a) where
  show = genericShow

derive instance eqOrderByDirection :: Eq a => Eq (OrderByDirection a)

type Props' =
  ( colNames     :: Array ColumnName
  , totalRecords :: Int
  , setParams    :: Params -> Effect Unit
  , rows         :: Rows
  , container    :: TableContainerProps -> Array ReactElement
  )

type Props = Record Props'

type State =
  { currentPage :: Int
  , pageSize    :: PageSizes
  , orderBy     :: OrderBy
  }

initialState :: State
initialState =
  { currentPage  : 1
  , pageSize     : PS10
  , orderBy      : Nothing
  }

initialParams :: Params
initialParams = stateParams initialState

data Action
  = ChangePageSize PageSizes
  | ChangePage     Int
  | ChangeOrderBy  OrderBy

type ChangePageAction = Int -> Effect Unit

-- | Action
-- ChangePageSize
changePageSize :: PageSizes -> State -> State
changePageSize ps td =
  td { pageSize      = ps
     , currentPage   = 1
     }

-- TODO: Not sure this is the right place for this function.
renderTableHeaderLayout :: { title :: String
                           , desc  :: String
                           , query :: String
                           , date  :: String
                           , user  :: String
                           } -> Array ReactElement
renderTableHeaderLayout {title, desc, query, date, user} =
  [ div [className "row"]
    [ div [className "col-md-3"] [ h3 [] [text title] ]
    , div [className "col-md-9"] [ hr [style {height : "2px",backgroundColor : "black"}] ]
    ]
  , div [className "row"] [ div [className "jumbotron1", style {padding : "12px 0px 20px 12px"}]
        [ div [ className "col-md-8 content"]
              [ p [] [ i [className "glyphicon glyphicon-globe"] []
                     , text $ " " <> desc
                     ]
              , p [] [ i [className "glyphicon glyphicon-zoom-in"] []
                     , text $ " " <> query
                     ]
              ]
        , div [ className "col-md-4 content"]
              [ p [] [ i [className "glyphicon glyphicon-calendar"] []
                     , text $ " " <> date
                     ]
              , p [] [ i [className "glyphicon glyphicon-user"] []
                     , text $ " " <> user
                     ]
              ]
        ]
    ]
  ]

tableSpec :: Spec State Props Action
tableSpec = simpleSpec performAction render
  where
    modifyStateAndReload :: (State -> State) -> Props -> State -> StateCoTransformer State Unit
    modifyStateAndReload f {setParams} state = do
      logs "modifyStateAndReload" -- TODO rename
      modifyState_ f
      liftEffect $ setParams $ stateParams $ f state

    performAction :: PerformAction State Props Action
    performAction (ChangePageSize ps) =
      modifyStateAndReload $ changePageSize ps
    performAction (ChangePage p) =
      modifyStateAndReload $ _ { currentPage = p }
    performAction (ChangeOrderBy mc) =
      modifyStateAndReload $ _ { orderBy = mc }

    renderColHeader :: (OrderBy -> Effect Unit)
                    -> OrderBy
                    -> ColumnName -> ReactElement
    renderColHeader changeOrderBy currentOrderBy c =
      th [scope "col"] [ b' cs ]
      where
        lnk mc = effectLink (changeOrderBy mc)
        cs :: Array ReactElement
        cs =
          case currentOrderBy of
            Just (ASC d)  | c == d -> [lnk (Just (DESC c)) "DESC ",  lnk Nothing (columnName c)]
            Just (DESC d) | c == d -> [lnk (Just (ASC  c)) "ASC ", lnk Nothing (columnName c)]
            _ -> [lnk (Just (ASC c)) (columnName c)]

    render :: Render State Props Action
    render dispatch {container, colNames, totalRecords, rows}
                    {pageSize, currentPage, orderBy} _ =
      container
        { pageSizeControl: sizeDD pageSize dispatch
        , pageSizeDescription: textDescription currentPage pageSize totalRecords
        , paginationLinks: pagination (dispatch <<< ChangePage) totalPages currentPage
        , tableHead:
            tr [] (renderColHeader (dispatch <<< ChangeOrderBy) orderBy <$> colNames)
        , tableBody:
            map (tr [] <<< map (\c -> td [] [c]) <<< _.row) rows
        }
      where
        ps = pageSizes2Int pageSize
        totalPages = (totalRecords / ps) + min 1 (totalRecords `mod` ps)

defaultContainer :: {title :: String} -> TableContainerProps -> Array ReactElement
defaultContainer {title} props =
  [ div [className "row"]
    [ div [className "col-md-4"] [props.pageSizeDescription]
    , div [className "col-md-4"] [props.paginationLinks]
    , div [className "col-md-4"] [props.pageSizeControl]
    ]
  , table [ className "table"]
    [ thead [className "thead-dark"] [ props.tableHead ]
    , tbody [] props.tableBody
    ]
  ]

-- TODO: this needs to be in Gargantext.Pages.Corpus.Graph.Tabs
graphContainer :: {title :: String} -> TableContainerProps -> Array ReactElement
graphContainer {title} props =
  [ -- TODO title in tabs name (above)
    table [ className "table"]
    [ thead [className "thead-dark"] [ props.tableHead ]
    , tbody [] props.tableBody
    ]
   -- TODO better rendering of the paginationLinks
   -- , props.pageSizeControl
   -- , props.pageSizeDescription
   -- , props.paginationLinks
  ]



stateParams :: State -> Params
stateParams {pageSize, currentPage, orderBy} = {offset, limit, orderBy}
  where
    limit = pageSizes2Int pageSize
    offset = limit * (currentPage - 1)

tableClass :: ReactClass {children :: Children | Props'}
tableClass = createClass "Table" tableSpec (const initialState)

tableElt :: Props -> ReactElement
tableElt props = createElement tableClass props []

sizeDD :: PageSizes -> (Action -> Effect Unit) -> ReactElement
sizeDD ps d
  = span []
    [ select [ className "form-control"
             , onChange (\e -> d (ChangePageSize $ string2PageSize $ (unsafeCoerce e).target.value))
             ] $ map (optps ps) aryPS
    ]

textDescription :: Int -> PageSizes -> Int -> ReactElement
textDescription currPage pageSize totalRecords
  =  div [className "row1"]
          [ div [className ""] -- TODO or col-md-6 ?
                [ text $ "Showing " <> show start <> " to " <> show end <> " of " <> show totalRecords ]
          ]
    where
      start = (currPage - 1) * pageSizes2Int pageSize + 1
      end' = currPage * pageSizes2Int pageSize
      end  = if end' > totalRecords then totalRecords else end'

effectLink :: Effect Unit -> String -> ReactElement
effectLink eff msg = a [onClick $ const eff] [text msg]

pagination :: ChangePageAction -> Int -> Int -> ReactElement
pagination changePage tp cp
  = span [] $
    [ text " ", prev, first, ldots]
    <>
    lnums
    <>
    [b' [text $ " " <> show cp <> " "]]
    <>
    rnums
    <>
    [ rdots, last, next ]
    where
      prev = if cp == 1 then
               text " Prev. "
             else
               changePageLink (cp - 1) "Prev."
      next = if cp == tp then
               text " Next "
             else
               changePageLink (cp + 1) "Next"
      first = if cp == 1 then
                text ""
              else
                changePageLink' 1
      last = if cp == tp then
               text ""
             else
               changePageLink' tp
      ldots = if cp >= 5 then
                text " ... "
                else
                text ""
      rdots = if cp + 3 < tp then
                text " ... "
                else
                text ""
      lnums = map changePageLink' $ filter (1  < _) [cp - 2, cp - 1]
      rnums = map changePageLink' $ filter (tp > _) [cp + 1, cp + 2]

      changePageLink :: Int -> String -> ReactElement
      changePageLink i s = span []
          [ text " "
          , effectLink (changePage i) s
          , text " "
          ]

      changePageLink' :: Int -> ReactElement
      changePageLink' i = changePageLink i (show i)

data PageSizes = PS10 | PS20 | PS50 | PS100 | PS200

derive instance eqPageSizes :: Eq PageSizes

instance showPageSize :: Show PageSizes where
  show PS10  = "10"
  show PS20  = "20"
  show PS50  = "50"
  show PS100 = "100"
  show PS200 = "200"

pageSizes2Int :: PageSizes -> Int
pageSizes2Int PS10  = 10
pageSizes2Int PS20  = 20
pageSizes2Int PS50  = 50
pageSizes2Int PS100 = 100
pageSizes2Int PS200 = 200

aryPS :: Array PageSizes
aryPS = [PS10, PS20, PS50, PS100, PS200]

string2PageSize :: String -> PageSizes
string2PageSize "10" = PS10
string2PageSize "20" = PS20
string2PageSize "50" = PS50
string2PageSize "100" = PS100
string2PageSize "200" = PS200
string2PageSize _    = PS10

optps :: PageSizes -> PageSizes -> ReactElement
optps cv val = option [ selected (cv == val), value $ show val ] [text $ show val]