{-|
Module      : Gargantext.Database.Metrics
Description : Get Metrics from Storage (Database like)
Copyright   : (c) CNRS, 2017-Present
License     : AGPL + CECILL v3
Maintainer  : team@gargantext.org
Stability   : experimental
Portability : POSIX

Node API
-}

{-# LANGUAGE QuasiQuotes          #-}

module Gargantext.Database.Action.Metrics
  where

import Database.PostgreSQL.Simple.SqlQQ (sql)
import Data.HashMap.Strict (HashMap)
import Data.Map.Strict (Map)
import Data.Set (Set)
import Database.PostgreSQL.Simple (Query, Only(..))
import Database.PostgreSQL.Simple.Types (Values(..), QualifiedIdentifier(..))
import Data.Vector (Vector)
import Gargantext.Core (HasDBid(toDBid))
import Gargantext.API.Ngrams.Tools (filterListWithRoot, groupNodesByNgrams, Diagonal(..), getCoocByNgrams, mapTermListRoot, RootTerm, getRepo)
import Gargantext.Database.Prelude (runPGSQuery{-, formatPGSQuery-})
import Gargantext.API.Ngrams.Types (TabType(..), ngramsTypeFromTabType, NgramsTerm(..))
import Gargantext.Core.Mail.Types (HasMail)
import Gargantext.Core.NodeStory hiding (runPGSQuery)
import Gargantext.Core.Text.Metrics (scored, Scored(..), {-localMetrics, toScored-})
import Database.PostgreSQL.Simple.ToField (toField, Action{-, ToField-})
import Gargantext.Core.Types (ListType(..), Limit, NodeType(..), ContextId)
import Gargantext.Database.Action.Flow.Types (FlowCmdM)
import Gargantext.Database.Action.Metrics.NgramsByContext (getContextsByNgramsOnlyUser{-, getTficfWith-})
import Gargantext.Database.Admin.Config (userMaster)
import Gargantext.Database.Admin.Types.Node (ListId, CorpusId)
import Gargantext.Database.Query.Table.Node (defaultList)
import Gargantext.Database.Query.Table.Node.Select
import Gargantext.Prelude
import qualified Data.HashMap.Strict as HM
import qualified Data.Map.Strict     as Map
import qualified Data.Set            as Set
import qualified Data.List           as List
import qualified Data.Text           as Text

getMetrics :: FlowCmdM env err m
            => CorpusId -> Maybe ListId -> TabType -> Maybe Limit
            -> m (HashMap NgramsTerm (ListType, Maybe NgramsTerm), Vector (Scored NgramsTerm))
getMetrics cId maybeListId tabType maybeLimit = do
  (ngs, _, myCooc) <- getNgramsCooc cId maybeListId tabType maybeLimit
  -- TODO HashMap
  pure (ngs, scored myCooc)


getNgramsCooc :: (FlowCmdM env err m)
            => CorpusId -> Maybe ListId -> TabType -> Maybe Limit
            -> m ( HashMap NgramsTerm (ListType, Maybe NgramsTerm)
                 , HashMap NgramsTerm (Maybe RootTerm)
                 , HashMap (NgramsTerm, NgramsTerm) Int
                 )
getNgramsCooc cId maybeListId tabType maybeLimit = do

  lId <- case maybeListId of
    Nothing   -> defaultList cId
    Just lId' -> pure lId'

  (ngs', ngs) <- getNgrams lId tabType

  lIds <- selectNodesWithUsername NodeList userMaster

  myCooc <- HM.filter (>1) <$> getCoocByNgrams (Diagonal True)
                           <$> groupNodesByNgrams ngs
                           <$> getContextsByNgramsOnlyUser cId
                                                           (lIds <> [lId])
                                                           (ngramsTypeFromTabType tabType)
                                                           (take' maybeLimit $ HM.keys ngs)
  pure $ (ngs', ngs, myCooc)

------------------------------------------------------------------------
------------------------------------------------------------------------
updateNgramsOccurrences :: (FlowCmdM env err m)
             => CorpusId -> Maybe ListId
             -> m ()
updateNgramsOccurrences cId mlId = do
  _ <- mapM (updateNgramsOccurrences' cId mlId Nothing) [Terms, Sources, Authors, Institutes]
  pure ()


updateNgramsOccurrences' :: (FlowCmdM env err m)
             => CorpusId -> Maybe ListId -> Maybe Limit -> TabType
             -> m [Int]
updateNgramsOccurrences' cId maybeListId maybeLimit tabType = do

  lId <- case maybeListId of
    Nothing   -> defaultList cId
    Just lId' -> pure lId'

  result <- getNgramsOccurrences cId lId tabType maybeLimit

  let
    toInsert :: [[Action]]
    toInsert =  map (\(ngramsTerm, score)
                        -> [ toField cId
                           , toField lId
                           , toField $ unNgramsTerm ngramsTerm
                           , toField $ toDBid $ ngramsTypeFromTabType tabType
                           , toField score
                           ]
                      )
       $ HM.toList result

    queryInsert :: Query
    queryInsert = [sql|
                  WITH input(corpus_id, list_id, terms, type_id, weight) AS (?)
                  INSERT into node_node_ngrams (node1_id, node2_id, ngrams_id, ngrams_type, weight)
                  SELECT input.corpus_id,input.list_id,ngrams.id,input.type_id,input.weight FROM input
                  JOIN ngrams on ngrams.terms = input.terms
                  ON CONFLICT (node1_id, node2_id, ngrams_id, ngrams_type)
                  DO UPDATE SET weight = excluded.weight
                  RETURNING 1
                  |]

  let fields = map (\t-> QualifiedIdentifier Nothing t)
             $ map Text.pack ["int4", "int4","text","int4","int4"]

  map (\(Only a) -> a) <$> runPGSQuery queryInsert (Only $ Values fields toInsert)



------------------------------------------------------------------------
-- Used for scores in Ngrams Table
getNgramsOccurrences :: (FlowCmdM env err m)
             => CorpusId -> ListId -> TabType -> Maybe Limit
             -> m (HashMap NgramsTerm Int)
getNgramsOccurrences c l t ml = HM.map Set.size <$> getNgramsContexts c l t ml



getNgramsContexts :: (FlowCmdM env err m)
             => CorpusId -> ListId -> TabType -> Maybe Limit
             -> m (HashMap NgramsTerm (Set ContextId))
getNgramsContexts cId lId tabType maybeLimit = do
  (_ngs', ngs) <- getNgrams lId tabType
  lIds <- selectNodesWithUsername NodeList userMaster

  -- TODO maybe add an option to group here
  getContextsByNgramsOnlyUser  cId
                               (lIds <> [lId])
                               (ngramsTypeFromTabType tabType)
                               (take' maybeLimit $ HM.keys ngs)



------------------------------------------------------------------------
updateContextScore :: (FlowCmdM env err m)
             => CorpusId -> Maybe ListId
             -> m [Int]
updateContextScore cId maybeListId = do

  lId <- case maybeListId of
    Nothing   -> defaultList cId
    Just lId' -> pure lId'

  result <- getContextsNgramsScore cId lId Terms MapTerm Nothing

  let
    toInsert :: [[Action]]
    toInsert =  map (\(contextId, score)
                        -> [ toField cId
                           , toField contextId
                           , toField score
                           ]
                      )
       $ Map.toList result

    queryInsert :: Query
    queryInsert = [sql|
                  WITH input(node_id, context_id, score) AS (?)
                    UPDATE nodes_contexts nc
                    SET score = input.score
                    FROM input
                    WHERE nc.node_id = input.node_id
                    AND nc.context_id = input.context_id
                    RETURNING 1
                  |]

  let fields = map (\t-> QualifiedIdentifier Nothing t)
             $ map Text.pack ["int4", "int4","int4"]

  map (\(Only a) -> a) <$> runPGSQuery queryInsert (Only $ Values fields toInsert)




-- Used for scores in Doc Table
getContextsNgramsScore :: (FlowCmdM env err m)
             => CorpusId -> ListId -> TabType -> ListType -> Maybe Limit
             -> m (Map ContextId Int)
getContextsNgramsScore cId lId tabType listType maybeLimit
 = Map.map Set.size <$> getContextsNgrams cId lId tabType listType maybeLimit

getContextsNgrams :: (FlowCmdM env err m)
             => CorpusId -> ListId -> TabType -> ListType -> Maybe Limit
             -> m (Map ContextId (Set NgramsTerm))
getContextsNgrams cId lId tabType listType maybeLimit = do
  (ngs', ngs) <- getNgrams lId tabType
  lIds <- selectNodesWithUsername NodeList userMaster

  result <- groupNodesByNgrams ngs <$> getContextsByNgramsOnlyUser
                               cId
                               (lIds <> [lId])
                               (ngramsTypeFromTabType tabType)
                               ( take' maybeLimit
                               $ HM.keys
                               $ HM.filter (\v -> fst v == listType) ngs'
                               )
  -- printDebug "getCoocByNgrams" result
  pure $ Map.fromListWith (<>)
       $ List.concat
       $ map (\(ng, contexts) -> List.zip (Set.toList contexts) (List.cycle [Set.singleton ng]))
       $ HM.toList result


------------------------------------------------------------------------
------------------------------------------------------------------------


getNgrams :: (HasMail env, HasNodeStory env err m)
            => ListId -> TabType
            -> m ( HashMap NgramsTerm (ListType, Maybe NgramsTerm)
                 , HashMap NgramsTerm (Maybe RootTerm)
                 )
getNgrams lId tabType = do

  lists <- mapTermListRoot [lId] (ngramsTypeFromTabType tabType) <$> getRepo [lId]
  -- TODO filterListWithRoot [MapTerm, StopTerm, CandidateTerm] lists
  let maybeSyn = HM.unions $ map (\t -> filterListWithRoot t lists)
                                 [[MapTerm], [StopTerm], [CandidateTerm]]
  pure (lists, maybeSyn)

-- Some useful Tools
take' :: Maybe Int -> [a] -> [a]
take' Nothing  xs = xs
take' (Just n) xs = take n xs