module Gargantext.Components.GraphExplorer.Controls
 ( Controls
 , useGraphControls
 , controls
 , controlsCpt
 ) where

import Data.Array as A
import Data.Int as I
import Data.Maybe (Maybe(..), maybe)
import Data.Sequence as Seq
import Data.Set as Set
import Effect.Timer (setTimeout)
import Prelude
import Reactix as R
import Reactix.DOM.HTML as RH
import Toestand as T

import Gargantext.Components.Graph as Graph
import Gargantext.Components.GraphExplorer.Button (centerButton, cameraButton)
import Gargantext.Components.GraphExplorer.RangeControl (edgeConfluenceControl, edgeWeightControl, nodeSizeControl)
import Gargantext.Components.GraphExplorer.SlideButton (labelSizeButton, mouseSelectorSizeButton)
import Gargantext.Components.GraphExplorer.ToggleButton (multiSelectEnabledButton, edgesToggleButton, louvainToggleButton, pauseForceAtlasButton, resetForceAtlasButton)
import Gargantext.Components.GraphExplorer.Sidebar.Types as GEST
import Gargantext.Components.GraphExplorer.Types as GET
import Gargantext.Hooks.Sigmax as Sigmax
import Gargantext.Hooks.Sigmax.Types as SigmaxT
import Gargantext.Sessions (Session)
import Gargantext.Types as GT
import Gargantext.Utils.Range as Range
import Gargantext.Utils.Reactix as R2
import Gargantext.Utils.Toestand as T2

here :: R2.Here
here = R2.here "Gargantext.Components.GraphExplorer.Controls"

type Controls =
  ( edgeConfluence     :: T.Box Range.NumberRange
  , edgeWeight         :: T.Box Range.NumberRange
  , forceAtlasState    :: T.Box SigmaxT.ForceAtlasState
  , graph              :: SigmaxT.SGraph
  , graphId            :: GET.GraphId
  , graphStage         :: T.Box Graph.Stage
  , hyperdataGraph     :: GET.HyperdataGraph
  , multiSelectEnabled :: T.Box Boolean
  , nodeSize           :: T.Box Range.NumberRange
  , reloadForest       :: T2.ReloadS
  , removedNodeIds     :: T.Box SigmaxT.NodeIds
  , selectedNodeIds    :: T.Box SigmaxT.NodeIds
  , session            :: Session
  , showControls       :: T.Box Boolean
  , showEdges          :: T.Box SigmaxT.ShowEdgesState
  , showLouvain        :: T.Box Boolean
  , showTree           :: T.Box Boolean
  , sidePanelState     :: T.Box GT.SidePanelState
  , sideTab            :: T.Box GET.SideTab
  , sigmaRef           :: R.Ref Sigmax.Sigma
  )

type LocalControls = ( labelSize :: T.Box Number, mouseSelectorSize :: T.Box Number )

initialLocalControls :: R.Hooks (Record LocalControls)
initialLocalControls = do
  labelSize <- T.useBox 14.0
  mouseSelectorSize <- T.useBox 15.0
  pure $ { labelSize, mouseSelectorSize }

controls :: R2.Component Controls
controls = R.createElement controlsCpt
controlsCpt :: R.Component Controls
controlsCpt = here.component "controls" cpt
  where
    cpt { edgeConfluence
        , edgeWeight
        , forceAtlasState
        , graph
        , graphId
        , graphStage
        , hyperdataGraph
        , multiSelectEnabled
        , nodeSize
        , reloadForest
        , selectedNodeIds
        , session
        , showControls
        , showEdges
        , showLouvain
        , sidePanelState
        , sideTab
        , sigmaRef } _ = do
      forceAtlasState' <- T.useLive T.unequal forceAtlasState
      graphStage' <- T.useLive T.unequal graphStage
      selectedNodeIds' <- T.useLive T.unequal selectedNodeIds
      showControls' <- T.useLive T.unequal showControls
      sidePanelState' <- T.useLive T.unequal sidePanelState

      localControls <- initialLocalControls
      -- ref to track automatic FA pausing
      -- If user pauses FA before auto is triggered, clear the timeoutId
      mFAPauseRef <- R.useRef Nothing

      -- When graph is changed, cleanup the mFAPauseRef so that forceAtlas
      -- timeout is retriggered.
      R.useEffect' $ do
        case graphStage' of
          Graph.Init -> R.setRef mFAPauseRef Nothing
          _          -> pure unit

      -- Handle case when FA is paused from outside events, eg. the automatic timer.
      R.useEffect' $ Sigmax.handleForceAtlas2Pause sigmaRef forceAtlasState mFAPauseRef Graph.forceAtlas2Settings

      -- Handle automatic edge hiding when FA is running (to prevent flickering).
      -- TODO Commented temporarily: this breaks forceatlas rendering after reset
      -- NOTE This is a hack anyways. It's force atlas that should be fixed.
      R.useEffect2' sigmaRef forceAtlasState' $ do
        T.modify_ (SigmaxT.forceAtlasEdgeState forceAtlasState') showEdges

      -- Automatic opening of sidebar when a node is selected (but only first time).
      R.useEffect' $ do
        if sidePanelState' == GT.InitialClosed && (not Set.isEmpty selectedNodeIds') then do
          T.write_ GT.Opened sidePanelState
          T.write_ GET.SideTabData sideTab
        else
          pure unit

      -- Timer to turn off the initial FA. This is because FA eats up lot of
      -- CPU, has memory leaks etc.
      R.useEffect1' forceAtlasState' $ do
        if forceAtlasState' == SigmaxT.InitialRunning then do
          timeoutId <- setTimeout 9000 $ do
            case forceAtlasState' of
              SigmaxT.InitialRunning ->
                T.write_ SigmaxT.Paused forceAtlasState
              _ -> pure unit
            R.setRef mFAPauseRef Nothing
          R.setRef mFAPauseRef $ Just timeoutId
          pure unit
         else
           pure unit

      let edgesConfluenceSorted = A.sortWith (_.confluence) $ Seq.toUnfoldable $ SigmaxT.graphEdges graph
      let edgeConfluenceMin = maybe 0.0 _.confluence $ A.head edgesConfluenceSorted
      let edgeConfluenceMax = maybe 100.0 _.confluence $ A.last edgesConfluenceSorted
      let edgeConfluenceRange = Range.Closed { min: edgeConfluenceMin, max: edgeConfluenceMax }

      --let edgesWeightSorted = A.sortWith (_.weight) $ Seq.toUnfoldable $ SigmaxT.graphEdges graph
      --let edgeWeightMin = maybe 0.0 _.weight $ A.head edgesWeightSorted
      --let edgeWeightMax = maybe 100.0 _.weight $ A.last edgesWeightSorted
      --let edgeWeightRange = Range.Closed { min: edgeWeightMin, max: edgeWeightMax }
      let edgeWeightRange = Range.Closed {
           min: 0.0
         , max: I.toNumber $ Seq.length $ SigmaxT.graphEdges graph
         }

      let nodesSorted = A.sortWith (_.size) $ Seq.toUnfoldable $ SigmaxT.graphNodes graph
      let nodeSizeMin = maybe 0.0 _.size $ A.head nodesSorted
      let nodeSizeMax = maybe 100.0 _.size $ A.last nodesSorted
      let nodeSizeRange = Range.Closed { min: nodeSizeMin, max: nodeSizeMax }

      let className = "navbar navbar-expand-lg " <> if showControls' then "" else "d-none"

      pure $ RH.nav { className }
                 [ RH.ul { className: "navbar-nav mx-auto" }
                   [ -- change type button (?)
                     navItem [ centerButton sigmaRef ]
                   , navItem [ resetForceAtlasButton { forceAtlasState, sigmaRef } [] ]
                   , navItem [ pauseForceAtlasButton { state: forceAtlasState } [] ]
                   , navItem [ edgesToggleButton { state: showEdges } [] ]
                   , navItem [ louvainToggleButton { state: showLouvain } [] ]
                   , navItem [ edgeConfluenceControl { range: edgeConfluenceRange
                                                     , state: edgeConfluence } [] ]
                   , navItem [ edgeWeightControl { range: edgeWeightRange
                                                 , state: edgeWeight } [] ]
                   -- change level
                   -- file upload
                   -- run demo
                   -- search button
                   -- search topics
                   , navItem [ labelSizeButton sigmaRef localControls.labelSize ] -- labels size: 1-4
                   , navItem [ nodeSizeControl { range: nodeSizeRange
                                               , state: nodeSize } [] ]
                   -- zoom: 0 -100 - calculate ratio
                   , navItem [ multiSelectEnabledButton { state: multiSelectEnabled } [] ]  -- toggle multi node selection
                   -- save button
                   , navItem [ mouseSelectorSizeButton sigmaRef localControls.mouseSelectorSize ]
                   , navItem [ cameraButton { id: graphId
                                            , hyperdataGraph: hyperdataGraph
                                            , session: session
                                            , sigmaRef: sigmaRef
                                            , reloadForest } ]
                   ]
                 ]
      where
        navItem = RH.li { className: "nav-item" }
          --   RH.ul {} [ -- change type button (?)
          --     RH.li {} [ centerButton sigmaRef ]
          --   , RH.li {} [ pauseForceAtlasButton {state: forceAtlasState} ]
          --   , RH.li {} [ edgesToggleButton {state: showEdges} ]
          --   , RH.li {} [ louvainToggleButton showLouvain ]
          --   , RH.li {} [ edgeConfluenceControl edgeConfluenceRange edgeConfluence ]
          --   , RH.li {} [ edgeWeightControl edgeWeightRange edgeWeight ]
          --     -- change level
          --     -- file upload
          --     -- run demo
          --     -- search button
          --     -- search topics
          --   , RH.li {} [ labelSizeButton sigmaRef localControls.labelSize ] -- labels size: 1-4
          --   , RH.li {} [ nodeSizeControl nodeSizeRange nodeSize ]
          --     -- zoom: 0 -100 - calculate ratio
          --   , RH.li {} [ multiSelectEnabledButton multiSelectEnabled ]  -- toggle multi node selection
          --     -- save button
          --   , RH.li {} [ nodeSearchControl { graph: graph
          --                                  , multiSelectEnabled: multiSelectEnabled
          --                                  , selectedNodeIds: selectedNodeIds } ]
          --   , RH.li {} [ mouseSelectorSizeButton sigmaRef localControls.mouseSelectorSize ]
          --   , RH.li {} [ cameraButton { id: graphId
          --                             , hyperdataGraph: hyperdataGraph
          --                             , session: session
          --                             , sigmaRef: sigmaRef
          --                             , reloadForest: reloadForest } ]
          --   ]
          -- ]

useGraphControls :: { forceAtlasS :: SigmaxT.ForceAtlasState
                    , graph          :: SigmaxT.SGraph
                    , graphId        :: GET.GraphId
                    , hyperdataGraph :: GET.HyperdataGraph
                    , reloadForest   :: T2.ReloadS
                    , session        :: Session
                    , showTree       :: T.Box Boolean
                    , sidePanel      :: T.Box (Maybe (Record GEST.SidePanel))
                    , sidePanelState :: T.Box GT.SidePanelState }
                 -> R.Hooks (Record Controls)
useGraphControls { forceAtlasS
                 , graph
                 , graphId
                 , hyperdataGraph
                 , reloadForest
                 , session
                 , showTree
                 , sidePanel
                 , sidePanelState } = do
  edgeConfluence <- T.useBox $ Range.Closed { min: 0.0, max: 1.0 }
  edgeWeight <- T.useBox $ Range.Closed {
      min: 0.0
    , max: I.toNumber $ Seq.length $ SigmaxT.graphEdges graph
    }
  forceAtlasState <- T.useBox forceAtlasS
  graphStage      <- T.useBox Graph.Init
  nodeSize <- T.useBox $ Range.Closed { min: 0.0, max: 100.0 }
  showEdges <- T.useBox SigmaxT.EShow
  showLouvain <- T.useBox false
  sigma <- Sigmax.initSigma
  sigmaRef <- R.useRef sigma

  { multiSelectEnabled, removedNodeIds, selectedNodeIds, showControls, sideTab } <- GEST.focusedSidePanel sidePanel

  pure { edgeConfluence
       , edgeWeight
       , forceAtlasState
       , graph
       , graphId
       , graphStage
       , hyperdataGraph
       , multiSelectEnabled
       , nodeSize
       , removedNodeIds
       , selectedNodeIds
       , session
       , showControls
       , showEdges
       , showLouvain
       , sidePanelState
       , showTree
       , sideTab
       , sigmaRef
       , reloadForest
       }