class KnowledgeManager(object):
    """
    This is the class that manages students' performance and update his/her
    estimated knowledge level
    """

    #Init
    def __init__(self, KL = None, KM = None):

        #student KL (knowledge level)
        if (KL == None):
            self._KL = DecayKnowledgeLevel()
        else:
            self._KL = KL

        #student KM (knowledge model)
        if (KM == None):
            self._KM = PointKnowledgeModel()
        else:
            self._KM = KM

        #Knowledge graph
        self._KG = KnowledgeGraph()

        #Knowledge level of DB
        self._knowledgeLevel = {}

        #Knowledge graph of DB
        #This variable is for every student
        self._knowledgeGraph = {}

        #Temporary DB
        self.tmpDB = DummyDB()

        #An estimator which is used for calculate estimated KL based on the performance
        self._estimator = KnowledgeEstimator()

        #Bind a KM to the KL as its observer
        self._KL.addObserver(self._KM)

        #Bind a KM to the KG as its observer
        self._KG.addObserver(self._KM)

    @classmethod
    def initWithKL(cls, KL = None):
        if (isinstance(KL, BaseKnowledgeLevel)):
            return cls(KL, None)
        else:
            raise TypeError("Input is not an acceptable type of KL(knowledge level) object!")

    @classmethod
    def initWithKM(cls, KM = None):
        if (isinstance(KM, BaseKnowledgeModel)):
            return cls(None, KM)
        else:
            raise TypeError("Input is not an acceptable type of KM(knowledge Model) object!")

    @classmethod
    def initWithKLAndKM(cls, KL = None, KM = None):
        if (isinstance(KL, BaseKnowledgeLevel)):
            if (isinstance(KM, BaseKnowledgeModel)):
                return cls(KL, KM)
            else:
                raise TypeError("Input is not an acceptable type of KM(knowledge Model) object!")
        else:
            raise TypeError("Input is not an acceptable type of KL(knowledge level) object!")

    #Initialize the parameters about KL for a certain student
    def initKnowledgeLevel(self, studentID = None):
        if (studentID == None):
            raise ValueError("Input student Id is not available!")
        
        #Create a knowledge level table for the student
        studentKL = self._knowledgeLevel.get(studentID)
        self._KL.setKnowledge(studentKL)

        #Initialize the estimation in KM
        self._KM.setKnowledge(studentKL)

    #Initialize the parameters about KG
    def initKnowledgeGraph(self):
        self._KG.setDependency(self._knowledgeGraph)
        
        self._KG.updateConcept() #Because KG involves constant values only, its content needs to be update once only
        
    #Pass the ALEKS score
    def setALEKS(self, aleks = None):
        if (aleks == None):
            raise ValueError("Input ALEKS score is not available!")

        self._KL.setALEKS(aleks)
    
    #Save the student's performance and update his/her estimated knowledge level
    def savePerformance(self, studentID = None, conceptID = None, hints = None, prompts = None, summary = None, lcc = None):
        if (studentID == None):
            raise ValueError("Input student Id is not available!")
        
        if (conceptID == None):
            raise ValueError("Input concept Id is not available!")
    
        if (hints == None or hints < 0):
            raise ValueError("Hints value is invalid! (0, 1, 2, ..., n)")

        if (prompts == None or prompts < 0):
            raise ValueError("Prompts value is invalid! (0, 1, 2, ..., n)")

        if (summary != True and summary != False):
            raise ValueError("Prompts value is invalid! (True or False)")

        if (lcc == None or lcc < 0):
            raise ValueError("LCC score value is invalid! (0~1)")

        #Caculate the observed estimation about knowledge level based on the student's performance
        obsEst = self._estimator(hints, prompts, summary, lcc)

        #Update the estimation
        self._KL.updateKnowledge(conceptID, obsEst)

        #Save updated estimation back to the knowledge level table
        #(The table need to be saved back to the DB later)
        self._knowledgeLevel[studentID] = self._KL.getKnowledge()

    def getKnowledgeLevel(self, conceptID = None):
        if (conceptID == None):
            raise ValueError("Input concept id is not available!")

        return self._KM.getKnowledgeLevel(conceptID)

    def loadKnowledgeDB(self):
        #TODO: Replace this temorary DB by real DB
        self._knowledgeLevel = self.tmpDB._knowledge

    def saveKnowledgeDB(self):
        #TODO: Replace this temorary DB by real DB
        self.tmpDB._knowledge = self._knowledgeLevel

    def loadGraphDB(self):
        #TODO: Replace this temorary DB by real DB
        self._knowledgeGraph = self.tmpDB._dependency

    def saveGraphDB(self):
        #TODO: Replace this temorary DB by real DB
        self.tmpDB._dependency = self._knowledgeGraph