{-|
Module      : Gargantext.Database.Root
Description : Main requests to get root of users
Copyright   : (c) CNRS, 2017-Present
License     : AGPL + CECILL v3
Maintainer  : team@gargantext.org
Stability   : experimental
Portability : POSIX
-}

{-# LANGUAGE Arrows #-}

module Gargantext.Database.Query.Tree.Root
  where

import Control.Arrow (returnA)
import Gargantext.Core (HasDBid(..))
import Gargantext.Core.Types.Individu (User(..))
import Gargantext.Core.Types.Main (CorpusName)
import Gargantext.Database.Action.Node ( mkNodeWithParent )
import Gargantext.Database.Action.User (getUserId, getUsername)
import Gargantext.Database.Admin.Config ( corpusMasterName, userMaster )
import Gargantext.Database.Admin.Types.Hyperdata.User ( HyperdataUser )
import Gargantext.Database.Admin.Types.Node
import Gargantext.Database.Prelude (runOpaQuery, DBCmd, DBCmd')
import Gargantext.Database.Query.Table.Node
import Gargantext.Database.Query.Table.Node.Error
import Gargantext.Database.Query.Table.User (queryUserTable, UserPoly(..))
import Gargantext.Database.Schema.Node (NodePoly(..), NodeRead, queryNodeTable)
import Gargantext.Prelude
import Opaleye (restrict, (.==), Select)
import Opaleye.SqlTypes (sqlStrictText, sqlInt4)


getRootId :: (HasNodeError err) => User -> DBCmd err NodeId
getRootId u = do
  maybeRoot <- head <$> getRoot u
  case maybeRoot of
    Nothing -> errorWith "[G.D.Q.T.R.getRootId] No root id"
    Just  r -> pure (_node_id r)

getRoot :: User -> DBCmd err [Node HyperdataUser]
getRoot = runOpaQuery . selectRoot

getOrMkRoot :: (HasNodeError err)
            => User
            -> DBCmd' env err (UserId, RootId)
getOrMkRoot user = do
  userId <- getUserId user

  rootId' <- map _node_id <$> getRoot user

  rootId'' <- case rootId' of
        []  -> mkRoot user
        n   -> case length n >= 2 of
            True  -> nodeError $ NodeLookupFailed $ UserHasTooManyRoots userId n
            False -> pure rootId'

  rootId <- maybe (nodeError NoRootFound) pure (head rootId'')
  pure (userId, rootId)


-- | Datatype for the `getOrMkRootWithCorpus`.
-- There are only 3 possibilities:
-- - User is userMaster and then there is no corpus name
-- - User is a normal user and then we pass corpus name
-- - User is a normal user and then we pass corpus ids
data MkCorpusUser =
    MkCorpusUserMaster
  | MkCorpusUserNormalCorpusName User CorpusName
  | MkCorpusUserNormalCorpusIds  User [CorpusId]
  deriving (Eq, Show)

userFromMkCorpusUser :: MkCorpusUser -> User
userFromMkCorpusUser MkCorpusUserMaster = UserName userMaster
userFromMkCorpusUser (MkCorpusUserNormalCorpusIds u _cids) = u
userFromMkCorpusUser (MkCorpusUserNormalCorpusName u _cname) = u


getOrMkRootWithCorpus :: (HasNodeError err, MkCorpus a)
                      => MkCorpusUser
                      -> Maybe a
                      -> DBCmd' env err (UserId, RootId, CorpusId)
getOrMkRootWithCorpus MkCorpusUserMaster c = do
  (userId, rootId) <- getOrMkRoot (UserName userMaster)
  corpusId'' <- do
    ns <- getCorporaWithParentId rootId
    pure $ map _node_id ns
  case corpusId'' of
    []   -> mkCorpus corpusMasterName c rootId userId
    cIds -> do
      corpusId <- maybe (nodeError NoCorpusFound) pure (head cIds)
      pure (userId, rootId, corpusId)
getOrMkRootWithCorpus (MkCorpusUserNormalCorpusName user cName) c = do
  (userId, rootId) <- getOrMkRoot user
  mkCorpus cName c rootId userId
getOrMkRootWithCorpus (MkCorpusUserNormalCorpusIds user []) c = do
  getOrMkRootWithCorpus (MkCorpusUserNormalCorpusName user "Default") c
getOrMkRootWithCorpus (MkCorpusUserNormalCorpusIds user cIds) _c = do
  (userId, rootId) <- getOrMkRoot user
  corpusId <- maybe (nodeError NoCorpusFound) pure (head cIds)
  pure (userId, rootId, corpusId)


-- | Helper function for `getOrMkRootWithCorpus`.
mkCorpus :: (HasNodeError err, MkCorpus a)
         => CorpusName
         -> Maybe a
         -> RootId
         -> UserId
         -> DBCmd err (UserId, RootId, CorpusId)
mkCorpus cName c rootId userId = do
  c' <- mk (Just cName) c rootId userId
  _tId <- case head c' of
            Nothing  -> errorWith "[G.D.Q.T.Root.getOrMk...] mk Corpus failed"
            Just c'' -> insertDefaultNode NodeTexts c'' userId

  corpusId <- maybe (nodeError NoCorpusFound) pure (head c')
  pure (userId, rootId, corpusId)


mkRoot :: (HasNodeError err)
       => User
       -> DBCmd' env err [RootId]
mkRoot user = do

  -- TODO
  -- udb <- getUserDb user
  -- let uid = user_id udb
  uid <- getUserId user

  -- TODO ? Which name for user Node ?
  una <- getUsername user

  case isPositive uid of
     False -> nodeError $ NodeCreationFailed (UserHasNegativeId uid)
     True  -> do
       rs <- mkNodeWithParent NodeUser Nothing uid una
       _ <- case rs of
         [r] -> do
           _ <- insertNode NodeFolderPrivate Nothing Nothing r uid
           _ <- insertNode NodeFolderShared Nothing Nothing r uid
           _ <- insertNode NodeFolderPublic Nothing Nothing r uid
           pure rs
         _   -> pure rs
       pure rs

selectRoot :: User -> Select NodeRead
selectRoot (UserName username) = proc () -> do
    row   <- queryNodeTable -< ()
    users <- queryUserTable -< ()
    restrict -< _node_typename row   .== sqlInt4 (toDBid NodeUser)
    restrict -< user_username  users .== sqlStrictText username
    restrict -< _node_user_id   row   .== user_id users
    returnA  -< row

selectRoot (UserDBId uid) = proc () -> do
    row   <- queryNodeTable -< ()
    restrict -< _node_typename row   .== sqlInt4 (toDBid NodeUser)
    restrict -< _node_user_id   row   .== sqlInt4 (_UserId uid)
    returnA  -< row

selectRoot (RootId nid) =
 proc () -> do
    row   <- queryNodeTable -< ()
    restrict -< _node_typename row   .== sqlInt4 (toDBid NodeUser)
    restrict -< _node_id   row   .== pgNodeId nid
    returnA  -< row
