package performa.utils;

import java.util.*;
import oneit.logging.*;
import oneit.objstore.*;
import oneit.objstore.rdbms.filters.*;
import oneit.utils.*;
import oneit.utils.filter.Filter;
import oneit.utils.math.*;
import oneit.utils.parsers.FieldException;
import performa.orm.*;
import performa.orm.types.*;

/**
 *
 * @author nilu
 */
public class AnalysisEngine 
{
    private static final    Long    MAX_VALID_FACTOR_NUMBER =   49L; //Don't consider unusally high/low answer factors i.e 50/51
    
    public static void analyseAnswers(JobApplication jobApplication) throws StorageException, FieldException
    {
        LogMgr.log(JobApplication.LOG, LogLevel.PROCESSING1, "Inside AnalysisEngine --> analyseAnswers");
        
        ObjectTransaction           objTran         =   jobApplication.getTransaction();
        Set<Factor>                 levelFactors    =   jobApplication.pipelineJobApplication().toJob().toLevel().toFactors().toFactor().uniqueVals();
        Filter<FactorQuestionLink>  factorFilter    =   FactorQuestionLink.SearchByAll().andFactor(new InFilter(levelFactors));
        
        //Preloading Data
        jobApplication.pipelineJobApplication().toProfileAssessmentAnswers().toQuestion().toSection().uniqueVals();
        jobApplication.pipelineJobApplication().toProfileAssessmentAnswers().toQuestion().toFactors().toFactor().uniqueVals();
        Factor.pipesFactor(levelFactors).toResults().uniqueVals();
        Factor.pipesFactor(levelFactors).toLevelFactorTypes().uniqueVals();

        LogMgr.log(JobApplication.LOG, LogLevel.PROCESSING1, "Processing Job Application ", jobApplication);

        Map<Factor, Integer>    factorScoreMap  =   new HashMap();
            
        for (Answer answer : jobApplication.getProfileAssessmentAnswersSet())
        {
            Set<FactorQuestionLink> links   =   answer.pipelineAnswer().toQuestion().toFactors(factorFilter).uniqueVals();

            for (FactorQuestionLink link : links) 
            {
                int     factorScore =   0;
                Factor  factor      =   link.getFactor();
                
                if(factor.getID().longID() <= MAX_VALID_FACTOR_NUMBER)  //Don't consider unusally high/low answer factors i.e 50/51
                {
                    if(factorScoreMap.containsKey(factor))
                    {
                        factorScore =   factorScoreMap.get(factor);
                    }

                    if(link.isTrue(link.getReverseScore()))
                    {
                        if(answer.getQuestion().getQuestionType() == QuestionType.IPSATIVE)
                        {
                            factorScore += (10 - answer.getAnswerNo());
                        }
                        else
                        {
                            factorScore += (8 - answer.getAnswerNo());
                        }
                    }
                    else
                    {
                        factorScore +=  StringUtils.subNulls(answer.getAnswerNo(), 0);
                    }
                    factorScoreMap.put(factor, factorScore);
                }
            }
        }

        //Unusually High Answers/Unusually Low Answers
        Filter<Question>                questFilter             =   Question.SearchByAll().andHighLowFactor(new IsNotNullFilter());
        MultiHashtable<Factor, Answer>  highLowAnswersByFactor  =   new MultiHashtable();
        
        highLowAnswersByFactor.groupValues(jobApplication.getProfileAssessmentAnswersSet(), Answer.pipesAnswer().toQuestion(questFilter).toHighLowFactor());
        
        for(Factor factor : highLowAnswersByFactor.keySet())
        {
            if(factor != null)
            {
                double  answerTotal =   Statistics.sum(Answer.pipesAnswer(highLowAnswersByFactor.getValuesForKeyNN(factor)).toAnswerNo().vals());
                
                factorScoreMap.put(factor, Double.valueOf(answerTotal).intValue());
            }
        }
        
        TestAnalysis    testAnalysis    =   TestAnalysis.createTestAnalysis(objTran);
            
        jobApplication.getJob().getLevel().addToTestAnalysises(testAnalysis);
        jobApplication.getCandidate().addToTestAnalysises(testAnalysis);
        testAnalysis.setJob(jobApplication.getJob());
        
        for(Factor factor : factorScoreMap.keySet())
        {
            int score   =   factorScoreMap.get(factor);

            LogMgr.log(JobApplication.LOG, LogLevel.PROCESSING1, "Candidate:", jobApplication.getCandidate(), " Factor:", factor, " Score:", score);

            Filter<FactorScoreResult>   factorScoreFilter   =   FactorScoreResult.SearchByAll().andLevel(new EqualsFilter<>(jobApplication.getJob().getLevel()))
                                                                                            .andFromScore(new LessThanEqualFilter<>(score))
                                                                                            .andToScore(new GreaterThanEqualFilter<>(score));

            List<FactorScoreResult>     factorScoreResults  =   (List<FactorScoreResult>) factor.pipelineFactor().toResults(factorScoreFilter).vals();

            if(factorScoreResults != null && !factorScoreResults.isEmpty())
            {
                FactorScoreResult   factorScoreResult   =   factorScoreResults.get(0);

                FactorScore factorScore =   FactorScore.createFactorScore(objTran);

                factorScore.setFactor(factor);
                factorScore.setLevel(jobApplication.getJob().getLevel());
                factorScore.setScore(score);
                factorScore.setColorCode(factorScoreResult.getColorCode());
                factorScore.setColorRank(factorScoreResult.getColorCode() != null ? factorScoreResult.getColorCode().getColorRank() : 0);
                factorScore.setNarrative(factorScoreResult.getNarrative());
                
                /**
                 *  Weighted Score.
                 * 
                 *  10 where color rank = 1 and flag = Primary
                 *  5 where color rank = 1 and flag = Secondary
                 *  4 where color rank = 2 and flag = Primary
                 *  2 where color rank = 2 and flag = Secondary
                */
                Integer weightedScore   =   0;
                
                if(factorScoreResult.getColorCode() == ColorCode.GREEN || factorScoreResult.getColorCode() == ColorCode.AMBER)
                {
                    Filter<LevelFactorType> levelFactorFilter   =   LevelFactorType.SearchByAll().andLevel(new EqualsFilter(jobApplication.getJob().getLevel()));
                    
                    LevelFactorType levelFactorType =   factor.pipelineFactor().toLevelFactorTypes(levelFactorFilter).val();
                    
                    if(levelFactorType != null && levelFactorType.getTypeFlag() != null)
                    {
                        if(factorScoreResult.getColorCode() == ColorCode.GREEN)//Color Code: Green  --> Color Rank = 1
                        {
                            weightedScore   =   (levelFactorType.getTypeFlag() == TypeFlag.PRIMARY) ? 10 : 5;
                        }
                        else    //Color Code: Amber  --> Color Rank = 2
                        {
                            weightedScore   =   (levelFactorType.getTypeFlag() == TypeFlag.PRIMARY) ? 4 : 2;
                        }
                    }
                }
                factorScore.setWghtdScore(weightedScore);

                testAnalysis.addToFactorScores(factorScore);
                jobApplication.getCandidate().addToFactorScores(factorScore);
            }
        }
        
        LogMgr.log(JobApplication.LOG, LogLevel.PROCESSING1, "AnalysisEngine --> analyseAnswers completed");
    }
    
    public static Map<CultureClass, Long> getCultureFit(Set<CultureCriteriaAnswer> cultureCriteriaAnswers)
    {
        LogMgr.log(JobApplication.LOG, LogLevel.PROCESSING1, "AnalysisEngine --> getCultureFit called");
        
        //Preloading data
        CultureCriteriaAnswer.pipesCultureCriteriaAnswer(cultureCriteriaAnswers).toSelectedQuestion().toNarratives().toCultureElementRating().uniqueVals();
        CultureCriteriaAnswer.pipesCultureCriteriaAnswer(cultureCriteriaAnswers).toCultureCriteria().toCultureElementRating().toCultureElement().uniqueVals();
        
        Map<CultureClass, Integer>  scoreMap    =   new HashMap();
        Map<CultureClass, Integer>  maxScoreMap =   new HashMap();
        
        for(CultureClass cultureClass : CultureClass.getCultureClassArray())
        {
            scoreMap.put(cultureClass, 0);
            maxScoreMap.put(cultureClass, 0);
        }
        
        for(CultureCriteriaAnswer answer : cultureCriteriaAnswers)
        {
            CultureCriteria criteria    =   answer.getCultureCriteria();
            
            if(criteria != null && criteria.getImportance() != null && criteria.getImportance() != Importance.NOT_APPLICABLE)
            {
                CultureElementRating    rating  =   criteria.getCultureElementRating();
                
                if(rating != null && rating.getCultureElement() != null && rating.getCultureElement().getCultureClass() != null)
                {
                    CultureElement          element         =   rating.getCultureElement();
                    CultureClass            cultureClass    =   element.getCultureClass();
                    CultureElementQuestion  question        =   answer.getSelectedQuestion();
                    Integer                 weightingScore  =   criteria.getImportance().getWeightingScore();
                    Integer                 ratingScore     =   0;

                    if(question != null)
                    {
                        Filter              narrativeFilter =   CultureNarrative.SearchByAll().andCultureElementRating(new EqualsFilter(rating));
                        CultureNarrative    narrative       =   (CultureNarrative) question.pipelineCultureElementQuestion().toNarratives(narrativeFilter).val();

                        if(narrative != null && narrative.getColorCode() != null)
                        {
                            ratingScore =   narrative.getColorCode().getRatingScore();
                        }
                    }
                    
                    int score       =   (weightingScore * ratingScore);
                    int maxScore    =   (weightingScore * ColorCode.GREEN.getRatingScore());

                    scoreMap.put(cultureClass, (scoreMap.get(cultureClass) + score));
                    maxScoreMap.put(cultureClass, (maxScoreMap.get(cultureClass) + maxScore));
                }
            }
        }

        Map<CultureClass, Long> cultureFitMap   =   getFinalFitMap(scoreMap, maxScoreMap);
                
        LogMgr.log(JobApplication.LOG, LogLevel.PROCESSING1, "AnalysisEngine --> getCultureFit completed");
        
        return cultureFitMap;
    }
    
    private static <T> Map<T, Long> getFinalFitMap(Map<T, Integer> scoreMap, Map<T, Integer> maxScoreMap)
    {
        Map<T, Long>    fitMap          =   new HashMap();
        int             totalScore      =   0;
        int             totalMaxScore   =   0;
        
        for(T key : scoreMap.keySet())
        {
            int score       =   scoreMap.get(key);
            int maxScore    =   maxScoreMap.get(key);
            
            LogMgr.log(JobApplication.LOG, LogLevel.DEBUG1, "getCultureFit score for ", key, " ", score, " max:", maxScore);
            
            totalScore      +=  score;
            totalMaxScore   +=  maxScore;
            
            if(maxScore != 0)       //To prevent divide by zero
            {
                fitMap.put(key, Math.round(NullArith.divide(score, maxScore) * 100));
            }
            else
            {
                fitMap.put(key, 0L);
            }
        }
        
        if(totalMaxScore != 0)
        {
            fitMap.put(null, Math.round(NullArith.divide(totalScore, totalMaxScore) * 100));  //Using NULL key for final total
        }
        else
        {
            fitMap.put(null, 0L);  //Using NULL key for final total
        }
        return fitMap;
    }
    
    public static Map<Importance, Long> getRequirementFit(Set<AssessmentCriteriaAnswer> assessmentCriteriaAnswers)
    {
        LogMgr.log(JobApplication.LOG, LogLevel.PROCESSING1, "AnalysisEngine --> getRequirementFit called");
        
        Map<Importance, Integer>    scoreMap    =   new HashMap();
        Map<Importance, Integer>    maxScoreMap =   new HashMap();
        
        for(Importance importance : Importance.getImportanceArray())
        {
            if(importance.getWeightingScore() > 0)      //Skiping NOT_APPLICABLE
            {
                scoreMap.put(importance, 0);
                maxScoreMap.put(importance, 0);
            }
        }
        
        for(AssessmentCriteriaAnswer answer : assessmentCriteriaAnswers)
        {
            if(answer.getAssessmentCriteria() != null && answer.getAssessmentCriteria().getImportance() != null)
            {
                Importance  importance  =   answer.getAssessmentCriteria().getImportance();
                int         maxScore    =   importance.getWeightingScore();
                int         score       =   answer.isCorrectAnswer() ? maxScore : 0;

                scoreMap.put(importance, (scoreMap.get(importance) + score));
                maxScoreMap.put(importance, (maxScoreMap.get(importance) + maxScore));
            }
        }
        
        Map<Importance, Long>  requirementFitMap =   getFinalFitMap(scoreMap, maxScoreMap);
        
        LogMgr.log(JobApplication.LOG, LogLevel.PROCESSING1, "AnalysisEngine --> getRequirementFit completed");
        
        return requirementFitMap;
    }
}