-----------------------------------------------------------------------------
-- Copyright 2019, Advise-Me project team. This file is distributed under 
-- the terms of the Apache License 2.0. For more information, see the files
-- "LICENSE.txt" and "NOTICE.txt", which are included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-- Provides reports of the user models.
--
-----------------------------------------------------------------------------

module Bayes.StudentReport where

import Text.Printf (printf)
import Data.Maybe ( listToMaybe )
import Control.Monad ( forM )
import qualified Ideas.Text.XML as XML
import qualified Data.Map as M

import Util.String ( trim )
import Bayes.Evidence (allProbabilities, Evidence)
import Bayes.Network (Network, Node, nodes, parentIds, nodeId, parents, label)
import Bayes.NetworkReader (readNetwork)


data StudentReport = StudentReport
     { studentID :: String
     , competences :: [Competence]
     }

data Translation = Translation
   { translationLabel :: XML.XMLBuilder
   , translationDescription :: Maybe XML.XMLBuilder
   , translationExample :: Maybe XML.XMLBuilder
   }

data Competence = Competence
     { skillID :: String
     , skillText :: Translation
     , skillValue :: Maybe Double
     , subskills :: [Competence]
     }


-- | Limit nested competences to n levels.
limitNesting :: Int -> StudentReport -> StudentReport
limitNesting n report = report { competences = map (limit n) $ competences report}
   where
   limit :: Int -> Competence -> Competence
   limit 0 c = c { subskills =  [] }
   limit i c = c { subskills = map (limit (i-1)) $ subskills c }


buildStudentReport :: M.Map String Translation -> String -> Network () -> Evidence -> StudentReport
buildStudentReport dictionary sID nw sm = StudentReport
   { studentID = sID
   , competences = map makeCompetence roots
   }

   where

   makeCompetence :: Node () -> Competence
   makeCompetence node = Competence
      { skillID = nodeId node
      , skillText = M.findWithDefault defaultTranslation (nodeId node) dictionary
      , skillValue = listToMaybe
         [ fromRational p :: Double
         | (nodeId', stateLabel, p) <- allProbabilities sm
         , nodeId node == nodeId'
         , stateLabel == "Yes"
         ]
      , subskills = map makeCompetence $ children node
      }

      where
      defaultTranslation = Translation
         { translationLabel = XML.string $ label node
         , translationDescription = mempty
         , translationExample = mempty
         }

   -- Top-level nodes have no parents
   roots :: [Node ()]
   roots = filter (null . parents nw)
         $ nodes nw

   -- Each successive level is a child of one of the previous levels
   children :: Node () -> [Node ()]
   children node = filter ((nodeId node `elem`) . parentIds)
                  $ nodes nw




-- | Get a mapping from NodeIDs to labels and descriptions in the requested
-- language.
getTranslations :: String -> IO (M.Map String Translation)
getTranslations requestedLanguage = do
   xml <- XML.parseXMLFile "networks/labels.xml"
   fmap M.fromList . forM (XML.findChildren "node" xml) $ \nodeXml -> do
      nodeID <- XML.findAttribute "id" nodeXml
      title <- XML.findChild "title" nodeXml >>= getDataLang
      description <- maybe (return Nothing) (return . getDataLang) $ XML.findChild "description" nodeXml
      example <- maybe (return Nothing) (return . getDataLang) $ XML.findChild "example" nodeXml
      return (nodeID, Translation { translationLabel = title, translationDescription = description, translationExample = example })

   where
   getDataLang :: Monad m => XML.XML -> m XML.XMLBuilder
   getDataLang =
      maybe (fail $ "No such language " ++ requestedLanguage) return
         . listToMaybe
         . map (mconcat . map (either (XML.string . trim) XML.builder) . XML.content )
         . filter (\lang -> XML.findAttribute "id" lang == Just requestedLanguage)
         . XML.findChildren "lang"


-- | Create a student report from a student model.
toReport :: String -> String -> Evidence -> IO StudentReport
toReport sID lang sm = do
   nw <- snd <$> readNetwork "networks/StudentModel.xdsl"
   dictionary <- getTranslations lang
   return . limitNesting 2 $ buildStudentReport dictionary sID nw sm


instance XML.ToXML Competence where
   toXML c = XML.makeXML "competence" . mconcat $
      [ "id"    XML..=. skillID c
      , "value" XML..=. (maybe "0.5" (printf "%.3f") . skillValue $ c)
      , mconcat $
         XML.tag "title" (translationLabel text')
         : (maybe mempty (XML.tag "description") (translationDescription text'))
         : (maybe mempty (XML.tag "example") (translationExample text'))
         : map XML.builderXML (subskills c)
      ]

      where
      text' = skillText c

instance XML.ToXML StudentReport where
   toXML report = XML.makeXML "user" $  mconcat
      [ "id" XML..=. studentID report
      , mconcat $ map XML.builderXML (competences report)
      ]