{-|
Module      : Gargantext.Core.Viz.Graph.Bridgeness
Description : Bridgeness filter
Copyright   : (c) CNRS, 2017-Present
License     : AGPL + CECILL v3
Maintainer  : team@gargantext.org
Stability   : experimental
Portability : POSIX

Let be a graph Bridgeness filters inter-communities links in two ways.
If the partitions are known, filtering is uniform to expose the communities clearly for the beginners.
But


uniformly
filters inter-communities links.

TODO use Map LouvainNodeId (Map LouvainNodeId)
-}

{-# OPTIONS_GHC -fno-warn-deprecations #-}

module Gargantext.Core.Viz.Graph.Bridgeness -- (bridgeness)
  where

import Data.IntMap qualified as Dico
import Data.List qualified as List
import Data.Map.Strict (fromListWith, lookup, toList, mapWithKey, elems)
import Data.Map.Strict qualified as Map
import Data.Set qualified as Set
import Data.Tuple.Extra qualified as Tuple
import Gargantext.Core.Viz.Graph.Types (BridgenessMethod(..))
import Gargantext.Prelude hiding (toList, filter)
import Graph.Types (ClusterNode(..))
----------------------------------------------------------------------

type Partitions = Map (Int, Int) Double -> IO [ClusterNode]
type Partitions' = Map (Int, Int) Double -> IO [Set NodeId]
----------------------------------------------------------------------
nodeId2comId :: ClusterNode -> (NodeId, CommunityId)
nodeId2comId (ClusterNode i1 i2) = (i1, i2)

type NodeId        = Int
type CommunityId   = Int

----------------------------------------------------------------------
setNodes2clusterNodes :: [Set NodeId] -> [ClusterNode]
setNodes2clusterNodes ns = List.concat $ map (\(n,ns') -> toCluster n ns') $ zip [1..] ns
  where
    toCluster :: CommunityId -> Set NodeId -> [ClusterNode]
    toCluster cId setNodeId = map (\n -> ClusterNode n cId) (Set.toList setNodeId)

clusterNodes2map :: [ClusterNode] -> Map NodeId Int
clusterNodes2map = Map.fromList . map (\(ClusterNode nId cId) -> (nId, cId))

removeNodes :: Set NodeId
            -> Map (NodeId, NodeId) Double
            -> Map (NodeId, NodeId) Double
removeNodes s = Map.filterWithKey (\(n1,n2) _v -> Set.member n1 s && Set.member n2 s)


clusterNodes2sets :: [ClusterNode] -> [Set NodeId]
clusterNodes2sets = Dico.elems
                  . Dico.fromListWith (<>)
                  . (map ((Tuple.second Set.singleton) . swap . nodeId2comId))

-- | Filter the edges of a graph based on the computed clustering
bridgeness :: [ClusterNode]               -- ^ Clustering
           -> BridgenessMethod            -- ^ basic/advanced flag
           -> Double                      -- ^ Bridgeness threshold
           -> Map (NodeId, NodeId) Double -- ^ Input graph
           -> Map (NodeId, NodeId) Double -- ^ Output graph
bridgeness partitions method filterThreshold graph =
  Map.fromList $
  List.concat  $
  Map.elems    $
  (case method of
     BridgenessBasic    -> filterComs (round filterThreshold)
     BridgenessAdvanced -> filterComsAdvanced
  )            $
  groupEdges (Map.fromList $ map nodeId2comId partitions) graph

groupEdges :: (Ord comId, Ord nodeId)
           => Map nodeId comId
           -> Map (nodeId, nodeId) value
           -> Map (comId, comId) [((nodeId, nodeId), value)]
groupEdges m = fromListWith (<>)
             . catMaybes
             . map (\((n1,n2), d)
                     -> let
                          n1n2_m = (,) <$> lookup n1 m <*> lookup n2 m
                          n1n2_d = Just [((n1,n2),d)]
                        in (,) <$> n1n2_m <*> n1n2_d
                    )
             . toList

filterComs :: (Ord n1, Eq n2)
           => Int
           -> Map (n2, n2) [(a3, n1)]
           -> Map (n2, n2) [(a3, n1)]
filterComs b m = Map.filter (not . null) $ mapWithKey filter' m
  where
    filter' (c1,c2) a
      | c1 == c2  = a
      -- TODO use n here
      | otherwise = take (b * 2*n) $ List.sortOn (Down . snd) a
           where
            n :: Int
            n = round $ 100 * a' / t
            a'= fromIntegral $ length a
            t :: Double
            t = fromIntegral $ length $ List.concat $ elems m

-- Weak links are often due to noise in the data and decrease the readability of the graph. 
-- This function prunes the links between the clusters when their weight is under a given 'threshold'.  
filterComsAdvanced :: (Ord a1, Fractional a1, Eq a2)
                   => Map (a2, a2) [(a3, a1)]
                   -> Map (a2, a2) [(a3, a1)]
filterComsAdvanced m = Map.filter (not . null) $ mapWithKey filter' m
  where
    threshold = 0.03 -- TODO make this threshold configurable
    filter' (c1,c2) xs
      | c1 == c2  = xs
      | otherwise = List.filter (\(_nn,v) -> v >= threshold) xs
