package performa.orm;

import com.google.maps.model.Distance;
import java.awt.Color;
import java.util.*;
import oneit.logging.*;
import oneit.objstore.*;
import oneit.objstore.rdbms.filters.EqualsFilter;
import oneit.objstore.rdbms.filters.IsNotNullFilter;
import oneit.objstore.rdbms.filters.NotEqualsFilter;
import oneit.objstore.utils.ObjstoreUtils;
import oneit.utils.*;
import oneit.utils.filter.CollectionFilter;
import oneit.utils.filter.Filter;
import oneit.utils.math.NullArith;
import oneit.utils.math.Rounding;
import oneit.utils.parsers.FieldException;
import performa.chart.RingChart;
import performa.orm.types.*;
import performa.orm.types.TimeZone;
import performa.utils.*;


public class JobApplication extends BaseJobApplication
{
    private static final long   serialVersionUID    = 0L;
    public  static LoggingArea  LOG                 = LoggingArea.createLoggingArea("JobApplication");
    
    // This constructor should not be called
    public JobApplication ()
    {
        // Do not add any code to this, always put it in initialiseNewObject
    }

    @Override
    public void preCommit(boolean willBeStored) throws Exception 
    {
        super.preCommit(willBeStored);
        
        if(willBeStored)
        {
            JobApplication old  =   (JobApplication) getEarliestBackup();
            
            // when application status changed
            if(getStatus() == ObjectStatus.NEW || !CollectionUtils.equals(old.getApplicationStatus(), getApplicationStatus()))
            {
                // delete previously scheduled emails for previous application status
                Filter<ScheduledEmail>  filter  =   ScheduledEmail.SearchByAll().andApplicationStatus(new EqualsFilter<>(old.getApplicationStatus()));
                
                pipelineJobApplication().toScheduledEmails(filter).uniqueVals().stream().forEach((scheduledEmail) -> {
                    scheduledEmail.delete();
                });
                
                // create scheduled emails for new application status
                int                 messageID   =   getJob().getHiringTeam().getMessageID() != null ? getJob().getHiringTeam().getMessageID() : 1;
                MessageTemplate[]   templates   =   MessageTemplate.SearchByAll()
                                                                    .andApplicationStatus(new EqualsFilter<>(getApplicationStatus()))
                                                                    .andMessageID(new EqualsFilter<>(messageID))
                                                                    .search(getTransaction());
            
            
                for(MessageTemplate template : templates)
                {
                    ScheduledEmail  scheduledEmail  =   ScheduledEmail.createScheduledEmail(getTransaction());
                    Date            now             =   new Date();
                    int             variance        =   MessagingUtils.randInt(0, template.getVariance());
                    Date            scheduledDate   =   DateDiff.add(now, Calendar.MINUTE, template.getDelayInMin() + variance);
                    
                    if(template.getBusinessHoursOnly())
                    {
                        TimeZone    jobTimeZone =   getJob().getHiringTeam().getCompany().getTimeZone();
                        Calendar    cal         =   new GregorianCalendar();
                        
                        cal.setTime(scheduledDate);
                        
                        scheduledDate   =   MessagingUtils.getWithinBusinessHours(cal, jobTimeZone != null ? java.util.TimeZone.getTimeZone(jobTimeZone.getTimeZoneCode()) : cal.getTimeZone());
                    }
                    
                    scheduledEmail.setScheduledDate(scheduledDate);
                    scheduledEmail.setSubject(template.getSubject());
                    scheduledEmail.setMessageContent(template.getMessageContent());
                    scheduledEmail.setApplicationStatus(getApplicationStatus());
                    scheduledEmail.setMessageTemplate(template);
                    
                    addToScheduledEmails(scheduledEmail);
                }
            }
        }
    }

    public static JobApplication createNewApplication(Candidate candidate, Job job) throws StorageException, FieldException
    {
        JobApplication  jobApplication  =   createJobApplication(job.getTransaction());
        
        jobApplication.setCandidate(candidate);
        jobApplication.setJob(job);
        
        return jobApplication;
    }

    @Override
    public void validate(ValidationContext context)
    {
        try
        {
            //Ideally should be managed with uniqueGroup, but uniqueGroup doesnt work without atleast 1 attribute
            searchCandidateJob(getTransaction(), getCandidate(), getJob()); //It will throw RuntimeException when more than 1 record found.
            
            if(getCV() != null)
            {
                String  contentType =   getCV().getContentType();
                context.check(contentType!= null && Utils.isValidContentType(contentType.toLowerCase()),  this, FIELD_CV, "invalid");
            }
            
            if(getCoverLetter() != null)
            {
                String  contentType =   getCoverLetter().getContentType();
                context.check(contentType!= null &&  Utils.isValidContentType(contentType.toLowerCase()) ,  this, FIELD_CoverLetter, "invalid");
            }
        }
        catch(RuntimeException ex)
        {
            context.check(false, this, SINGLEREFERENCE_Job, "alreadyApplied");
        }
        
        super.validate(context);
    }

    
    public boolean createAssessmentCriteriaObjects() throws FieldException
    {
        boolean safeRedirect = false;

        if(isIncludeAssessmentCriteria())
        {
            for(AssessmentCriteria assessmentCriteria : getJob().getAssessmentCriteriasSet())
            {
                boolean available   = false;
                
                for(AssessmentCriteriaAnswer answer : getAssessmentCriteriaAnswersSet())
                {
                    if(assessmentCriteria.equals(answer.getAssessmentCriteria()))
                    {
                        available = true;
                        break;
                    }
                }
                
                if(!available)
                {
                    AssessmentCriteriaAnswer    answer  =   AssessmentCriteriaAnswer.createAssessmentCriteriaAnswer(getTransaction());

                    addToAssessmentCriteriaAnswers(answer);
                    assessmentCriteria.addToAnswers(answer);
                    safeRedirect = true;
                }
            }
        }
        
        return safeRedirect;    
    }
    
    
    public boolean createCultureCriteriaObjects() throws FieldException
    {
        boolean     safeRedirect    = false;
        Candidate   candidate       = getCandidate();
        
        //to skip culture test
        if(!isIncludeCultureCriteria() || cultureCompleted())
        {
            return safeRedirect;
        }
        
        for(CultureCriteria cultureCriteria : getJob().getApplicableCultureSet())
        {
            boolean available   = false;

            for(CultureCriteriaAnswer answer : getCandidate().getCultureCriteriaAnswersSet())
            {
                if(cultureCriteria.getCultureElement().equals(answer.getCultureElement()))
                {
                    available = true;
                    break;
                }
            }

            if(!available)
            {
                CultureCriteriaAnswer   answer  =   CultureCriteriaAnswer.createCultureCriteriaAnswer(getTransaction());

                candidate.addToCultureCriteriaAnswers(answer);
                answer.setCultureElement(cultureCriteria.getCultureElement());

                safeRedirect = true;
            }
        }
        
        return safeRedirect;    
    }
    
    public boolean createDiversityAnswerObjects() throws FieldException
    {
        boolean     safeRedirect    = false;
        Candidate   candidate       = getCandidate();
        
        //to skip diversity test
        if(!isDiversityIncluded() || diversityCompleted())
        {
            return safeRedirect;
        }
        
        for(HTDiversityQuestion htQuestion : getJob().getDiversityQuestions())
        {
            DiversityQuestion   question    =   htQuestion.getQuestion();
            boolean             available   =   false;

            for(CandidateDiversityAnswer answer : getCandidate().getDiversityAnswersSet())
            {
                if(answer.getQuestion().equals(question))
                {
                    available = true;
                    break;
                }
            }

            if(!available)
            {
                CandidateDiversityAnswer    answer  =   CandidateDiversityAnswer.createCandidateDiversityAnswer(getTransaction());

                if(question.getMultipleAnswers())
                {
                    for(DiversityAnswer diversityAnswer : question.getAnswersSet())
                    {
                        AnswerOption    answerOption    =   AnswerOption.createAnswerOption(getTransaction());

                        answer.addToAnswers(answerOption);
                        answerOption.setAnswer(diversityAnswer);
                    }
                }
                
                candidate.addToDiversityAnswers(answer);
                answer.setQuestion(question);

                safeRedirect = true;
            }
        }
        
        return safeRedirect;    
    }
    
    public void setDiversityAnswerOptions() throws StorageException, FieldException
    {
        for(CandidateDiversityAnswer candidateAnswer : pipelineJobApplication().toCandidate().toDiversityAnswers().uniqueVals())
        {
            if(!candidateAnswer.getQuestion().getMultipleAnswers())
            {
                AnswerOption    answerOption    =   candidateAnswer.pipelineCandidateDiversityAnswer().toAnswers().val();
                
                if(answerOption != null && answerOption.getAnswer() != null && answerOption.isTrue(answerOption.getIsSelected()))
                {
                    candidateAnswer.setAnswerOption(answerOption.getAnswer());
                }
            }
        }
    }
    
    public Answer getAnswerForQuestion(Question question) throws FieldException
    {
        Filter  filter  =   Answer.SearchByAll().andQuestion(new EqualsFilter<>(question));
        
        return CollectionFilter.getFirstMatch(getCandidate().getProfileAssessmentAnswersSet(), filter);
    }
    
    
    public Collection<AssessmentCriteriaAnswer> getACAnswersByType(CriteriaType criteriaType)
    {
        Filter  filter  =   AssessmentCriteriaAnswer.SearchByCriteriaType().byCriteriaType(criteriaType);

        return  CollectionFilter.filter(getAssessmentCriteriaAnswersSet(), filter);
    }

    
    public AssessmentCriteriaAnswer getAssessmentCriteriaAnswer(AssessmentCriteria criteria)
    {
        Filter<AssessmentCriteriaAnswer>    filter  =   AssessmentCriteriaAnswer.SearchByAll().andAssessmentCriteria(new EqualsFilter<>(criteria));

        return pipelineJobApplication().toAssessmentCriteriaAnswers(filter).val();
    }
    
    
    public boolean selectionCompleted() //req   
    {
        if(getJob()!= null && getJob().getIncludeAssessmentCriteria() != Boolean.TRUE)
        {
            return Boolean.TRUE;
        }
        
        int allAnswersCount =   getAssessmentCriteriaAnswersCount();
        
        if(allAnswersCount == getJob().getAssessmentCriteriasCount())
        {
            Filter      filter          =   AssessmentCriteriaAnswer.SearchByAll().andAnswer(new IsNotNullFilter<>());
            Collection  selectedAnswers =   pipelineJobApplication().toAssessmentCriteriaAnswers(filter).vals();

            return (selectedAnswers.size() == allAnswersCount);
        }
        
        return false;
    }
       
    public boolean hasStartedApplication()
    {
        return getJob().getIncludeAssessmentCriteria() ? getAssessmentCriteriaAnswersCount() > 0 : getCandidate().getCultureCriteriaAnswersCount() > 0; 
    }
    
    public boolean cultureCompleted()
    {
        return isIncludeCultureCriteria() ? getCandidate().cultureCompleted(getJob()) : true;
    }
    
    public boolean diversityCompleted()
    {
        return getCandidate().diversityCompleted(getJob());
    }
    
    
    public boolean assessmentCompleted() //role
    {
        return getCandidate().assessmentCompleted(getJob());
    }
    
    
    public int getActualQuestionNumber()
    {
        Filter<Answer>  filter  =   new ExpressAnswerFilter(getJob().isExpressJob());

        return getCandidate().pipelineCandidate().toProfileAssessmentAnswers(filter).uniqueVals().size() + 1;
    }
    
    
    public int getQuestionNumber()
    {
        Filter<Answer>  filter  =   new ExpressAnswerFilter(getJob().isExpressJob());
        
        return getCandidate().pipelineCandidate().toProfileAssessmentAnswers(filter).uniqueVals().size() 
                - getCandidate().pipelineCandidate().toProfileAssessmentAnswers(filter).toQuestion().toRightQuestion().uniqueVals().size();
    }
    
    
    public boolean isIncludeAssessmentCriteria()
    {
        return getJob() != null && isTrue(getJob().getIncludeAssessmentCriteria());
    }
    
    public boolean isIncludeCultureCriteria()
    {
        return getJob() != null && isTrue(getJob().getIncludeCulture());
    }
    
    public boolean isDiversityIncluded()
    {
        return getJob() != null && getJob().hasDiversityQuestions();
    }

    @Override
    public Map getRoleFit()
    {
        if(super.getRoleFit() == null && getCandidate() != null && getJob() != null && getJob().getLevel() != null)
        {
            try
            {
                setRoleFit(AnalysisEngine.getRoleFitSuitability(getCandidate(), getJob().getLevel()));
            }
            catch (FieldException ex)
            {
                LogMgr.log(LOG, LogLevel.PROCESSING1, ex, "Error occured when setting RoleFit for " + this);
            }
        }
        return super.getRoleFit();
    }

    @Override
    public Map getCultureFit()
    {
        if(super.getCultureFit() == null && getCandidate() != null && getJob() != null && getJob().getLevel() != null)
        {
            try
            {
                setCultureFit(AnalysisEngine.getCultureFit(getCandidate().getCultureCriteriaAnswersSet(), getJob()));
            }
            catch (FieldException ex)
            {
                LogMgr.log(LOG, LogLevel.PROCESSING1, ex, "Error occured when setting CultureFit for " + this);
            }
        }
        return super.getCultureFit();
    }

    @Override
    public Map getRequirementFit()
    {
        if(super.getRequirementFit() == null)
        {
            try
            {
                setRequirementFit(AnalysisEngine.getRequirementFit(getAssessmentCriteriaAnswersSet()));
            }
            catch (FieldException ex)
            {
                LogMgr.log(LOG, LogLevel.PROCESSING1, ex, "Error occured when setting RequirementFit for " + this);
            }
        }
        return super.getRequirementFit();
    }

    @Override
    public Integer getOverallRank()
    {
        if(getJob() != null)
        {
            try
            {
                getJob().calculateRoleFitForRanking();
            }
            catch (FieldException ex)
            {
                LogMgr.log(LOG, LogLevel.PROCESSING1, ex, "Error occured when setting OverallRank for " + this);
            }
        }
        return super.getOverallRank();
    }

    @Override
    public Map getFactorScoreDetails()
    {
        if(super.getFactorScoreDetails() == null && getCandidate() != null && getJob() != null && getJob().getLevel() != null)
        {
            try
            {
                Map<FactorClass, Map<FactorLevelLink, Map>>             factorScoreDetails  =   new LinkedHashMap();
                Map<FactorClass, Tuple.T3<Double, ColorCode, Double>>   roleScoreMap        =   (Map<FactorClass, Tuple.T3<Double, ColorCode, Double>>) getRoleFit();
                
                for(FactorClass factorClass : getSortedFactorClasses())
                {
                    if(getJob().isExpressJob() || (roleScoreMap.get(factorClass) != null && roleScoreMap.get(factorClass).get0() > 0d))
                    {
                        factorScoreDetails.put(factorClass, AnalysisEngine.getFactorScoreDetails(getCandidate(), getJob().getLevel(), factorClass));
                    }
                }
                setFactorScoreDetails(factorScoreDetails);
            }
            catch (FieldException ex)
            {
                LogMgr.log(LOG, LogLevel.PROCESSING1, ex, "Error occured when setting FactorScoreDetails for " + this);
            }
        }
        return super.getFactorScoreDetails();
    }
    
    public Double getRoleFitScore()
    {
        return getRoleFit() != null && getRoleFit().get(null) != null ? ((Tuple.T3<Double, ColorCode, Double>)getRoleFit().get(null)).get0() : 0d;
    }
    
    public Long getCultureFitScore()
    {
        return getCultureFit() != null && getCultureFit().get(null) != null ? ((Tuple.T2<Long, Set<Tuple.T3>>)getCultureFit().get(null)).get0() : 0L;
    }
    
    public Long getRequirementFitScore()
    {
        return getRequirementFit() != null ? (Long) getRequirementFit().get(null) : 0;
    }
    
    public String getCultureFitColor()
    {
        long    score   =   getCultureFitScore();
         
        return score >= 70 ? "green" : (score >= 50 ? "yellow" : "red-b");
    }
    
    public String getRequirementFitColor()
    {
        long    score   =   getRequirementFitScore();
        
        return hasFailedEssentialRequirements() ? "red-b" : (score >= 80 ? "green" : (score >= 60 ? "yellow" : "red-b"));
    }
    
    public Boolean hasFailedEssentialRequirements()
    {
        Filter                          filter                  =   AssessmentCriteria.SearchByAll().andImportance(new EqualsFilter<>(Importance.ESSENTIAL));   
        Collection<AssessmentCriteria>  essentialRequirements   =   CollectionFilter.filter(getJob().getAssessmentCriteriasSet(), filter);
        
        if(essentialRequirements.size() > 0 && getRequirementAnswersByImportance().getValuesForKey(Importance.ESSENTIAL) != null)
        {
            Filter  negativeFilter  =   AssessmentCriteriaAnswer.SearchByAll().andAnswer(new EqualsFilter<>(Boolean.FALSE));

            return CollectionFilter.filter(getRequirementAnswersByImportance().getValuesForKey(Importance.ESSENTIAL), negativeFilter).size() > 0;
        }
        
        return Boolean.FALSE;
    }
    
    public Boolean hasAllEssentialRequirements()
    {
        Filter                          filter                  =   AssessmentCriteria.SearchByAll().andImportance(new EqualsFilter<>(Importance.ESSENTIAL));   
        Collection<AssessmentCriteria>  essentialRequirements   =   CollectionFilter.filter(getJob().getAssessmentCriteriasSet(), filter);
        
        if(essentialRequirements.size() > 0 && getRequirementAnswersByImportance().getValuesForKey(Importance.ESSENTIAL) != null)
        {
            Filter  positiveFilter  =   AssessmentCriteriaAnswer.SearchByAll().andAnswer(new EqualsFilter<>(Boolean.TRUE));

            return CollectionFilter.filter(getRequirementAnswersByImportance().getValuesForKey(Importance.ESSENTIAL), positiveFilter).size() == essentialRequirements.size();
        }
        
        return Boolean.FALSE;
    }
    
    
    public Double getRoleFitPercentage()
    {
        return getRoleFit() != null && getRoleFit().get(null) != null ? ((Tuple.T3<Double, ColorCode, Double>)getRoleFit().get(null)).get2() : 0d;
    }
    
    public List<AppProcessOption> getValidProcessOptions()
    {
        List<AppProcessOption>  options = new ArrayList<>();
        
        if(getApplicationStatus() == ApplicationStatus.SUBMITTED)
        {
            options.add(AppProcessOption.TO_SHORTLIST);
            options.add(AppProcessOption.TO_UNSUITABLE);
        }
        else if(getApplicationStatus() == ApplicationStatus.SHORTLISTED)
        {
            options.add(AppProcessOption.REMOVE_FROM_SHORTLIST);
            options.add(AppProcessOption.TO_UNSUITABLE);
        }
        else if(getApplicationStatus() == ApplicationStatus.UNSUITABLE)
        {
            options.add(AppProcessOption.REMOVE_FROM_UNSUITABLE);
            options.add(AppProcessOption.TO_SHORTLIST);
        }
        
        return options;
    }
    
    
    public String getApplicantStatusStr()
    {
        WorkFlow    workflow    =   getJob().getWorkFlowByStatus(getApplicationStatus());

        return workflow != null ? workflow.getName() : "";
    }
    
    
    public List<ApplicationStatus> getAvailableStatuses()
    {
        List<ApplicationStatus>  statuses = new ArrayList<>();
        
        statuses.add(ApplicationStatus.SUBMITTED);
        statuses.add(ApplicationStatus.SHORTLISTED);
        statuses.add(ApplicationStatus.UNSUITABLE);
        
        return statuses;
    }
        
    
    public Collection<JobApplication> getAllSubmittedApplicationsByCandidate()
    {
        Filter  filter  =   JobApplication.SearchByAll().andApplicationStatus(new NotEqualsFilter<>(ApplicationStatus.DRAFT));
        
        return  CollectionFilter.filter(getCandidate().getJobApplicationsSet(), filter);
    }
 
    
    public Collection<Answer> getCompletedAnswers()
    {
        return getCandidate().getCompletedAnswers(getJob());
    }
    
    
    public Set<FactorClass> getSortedFactorClasses()
    {
        Set<FactorClass>    factorClasses       =   new LinkedHashSet<>();
        Set<FactorClass>    levelFactorClasses  =   pipelineJobApplication().toJob().toLevel().toLevelClassCriterias().toFactorClass().uniqueVals();
        
        for(FactorClass factorClass : FactorClass.getFactorClassArray())    //getFactorClassArray returns Factor Classes sorted by SortOrder
        {
            if(levelFactorClasses.contains(factorClass))
            {
                factorClasses.add(factorClass);
            }
        }
        return factorClasses;
    }
    
    
    //to get pending time to complete job application
    public Integer  getRemainingTime()
    {
        //req-05 min
        //cul-05 min
        //req-20 min
        
        int remainingTime   =   0;
        
        //REQ
        if(getJob()!=null && isTrue(getJob().getIncludeAssessmentCriteria()))
        {
            if(!selectionCompleted())
            {
                remainingTime   +=  5;
            }
        }
        
        if(!cultureCompleted())
        {
            remainingTime   +=  5;
        }
        
        //ROLE
        if(!assessmentCompleted())
        {
            remainingTime   +=  getRoleTestRemainingTime() ;
        }
        
        return remainingTime;
    }
    
    
    //to get remaining time for role test
    public Integer getRoleTestRemainingTime()
    {
        int totalTime       =   getJob() != null && getJob().getAssessmentType() != null ? getJob().getAssessmentType().getTotalTime() : 20;    //Default to 20 as per current code
        int remainingTime   =   totalTime;
        
        if(assessmentCompleted())
        {
            remainingTime   -=  totalTime;
        }
        else //partially completed test for role
        {
            int allAnswersCount =   getJob().getAllQuestions().size(); 

            if(allAnswersCount > 0)
            {
                Filter      filter          =   new ExpressAnswerFilter(getJob().isExpressJob());
                Collection  selectedAnswers =   getCandidate().pipelineCandidate().toProfileAssessmentAnswers(filter).vals();

                if(selectedAnswers!=null && selectedAnswers.size()>0)
                {
                    Double  answerrate  =   NullArith.divide(selectedAnswers.size(), allAnswersCount);
                    
                    remainingTime   -=  Rounding.roundDouble(NullArith.multiply(answerrate, totalTime), 0);
                }
            }
        }
        return remainingTime;
    }

    @Override
    public boolean filterCandidateJob(Candidate candidate, Job job) throws StorageException
    {
        return (CollectionUtils.equals(candidate, getCandidate()) && CollectionUtils.equals(job, getJob()));
    }
    
    
    public MultiHashtable<CultureClass, CultureCriteriaAnswer> getCultureAnswersByClass()
    {
        MultiHashtable<CultureClass, CultureCriteriaAnswer> answersByClass  =   new MultiHashtable<>();

        answersByClass.groupValues(getCandidate().getCultureCriteriaAnswersSet(), CultureCriteriaAnswer.pipesCultureCriteriaAnswer().toCultureElement().toCultureClass());
    
        return answersByClass;
    }
    
    
    public MultiHashtable<Importance, AssessmentCriteriaAnswer> getRequirementAnswersByImportance()
    {
        MultiHashtable<Importance, AssessmentCriteriaAnswer>    answersByImportance =   new MultiHashtable<>();

        answersByImportance.groupValues(getAssessmentCriteriaAnswersSet(), AssessmentCriteriaAnswer.pipesAssessmentCriteriaAnswer().toAssessmentCriteria().toImportance());
    
        return answersByImportance;
    }
    
    public List<FactorScore> getRoleAreaOfConcerns()
    {
        Map<FactorClass, Map<FactorLevelLink, Map>> factorScoreDetails  =   getFactorScoreDetails();
        List<FactorScore>                           factorScores        =   new ArrayList();
        
        if(factorScoreDetails != null)
        {
            for(FactorClass factorClass : factorScoreDetails.keySet())
            {
                Map<FactorLevelLink, Map>   factorDetails   =   factorScoreDetails.get(factorClass);
                Collection<FactorLevelLink> sortedLinks     =   ObjstoreUtils.sort(factorDetails.keySet(), 
                                                                            new ObjectTransform[]{FactorLevelLink.pipesFactorLevelLink().toFactor().toObjectID()}, 
                                                                            new Comparator[]{CollectionUtils.DEFAULT_COMPARATOR});
                
                for(FactorLevelLink factorLevelLink : sortedLinks)
                {
                    Map factorLinkDetails   =   factorDetails.get(factorLevelLink);

                    if(!factorLinkDetails.isEmpty())
                    {
                        FactorScore factorScore =   (FactorScore) factorLinkDetails.get("factorScore");
                        
                        if(factorScore != null && factorScore.getFactor() != null && factorScore.getColorCode() != null && factorScore.getColorCode() != ColorCode.GREEN)      //SCORE.COLOR_RANK > 1 as per apollo
                        {
                            LevelFactorType levelFactorType =   factorLevelLink.getLevel().getLevelFactorType(factorScore.getFactor());
                            
                            if(levelFactorType != null && levelFactorType.getTypeFlag() == TypeFlag.PRIMARY)
                            {
                                factorScores.add(factorScore);
                            }
                        }
                    }
                }
            }
        }
        return factorScores;
    }
    
    public List<Tuple.T2> getCultureAreaOfConcerns()
    {
        Map<CultureClass, Tuple.T2<Long, Set<Tuple.T3>>>    cultureFitData  =   (Map<CultureClass, Tuple.T2<Long, Set<Tuple.T3>>>) getCultureFit();
        List<Tuple.T2>                                      result          =   new ArrayList();
                            
        for(CultureClass cClass : cultureFitData.keySet())
        {
            if(cClass == null || cultureFitData.get(cClass) == null)
            {
                continue;
            }

            for(Tuple.T3 tuple : cultureFitData.get(cClass).get1())
            {
                CultureNarrative    cultureNarrative    =   (CultureNarrative) tuple.get2();
                Integer             weightingScore      =   (Integer) tuple.get0();
                
                if(cultureNarrative != null && cultureNarrative.getColorCode() != null && cultureNarrative.getColorCode() != ColorCode.GREEN && weightingScore == 10)   //i.e. Importance = ESSENTIAL
                {
                    result.add(new Tuple.T2(tuple.get1(), cultureNarrative.getColorCode()));
                }
            }
        }
        return result;
    }
    
    
    public MultiHashtable<Importance, AssessmentCriteriaAnswer> getNegativeACByImportance(CriteriaType criteriaType)
    {
        MultiHashtable<Importance, AssessmentCriteriaAnswer> answerCountByImportance =   new MultiHashtable<>();
                
        answerCountByImportance.groupValues(getNegativeACAnswersByType(criteriaType), AssessmentCriteriaAnswer.pipesAssessmentCriteriaAnswer().toAssessmentCriteria().toImportance());

        return answerCountByImportance;
    }
    
    
    public Collection<AssessmentCriteriaAnswer> getNegativeACAnswersByType(CriteriaType criteriaType)
    {
        Filter  filter  =   AssessmentCriteriaAnswer.SearchByCriteriaType().byCriteriaType(criteriaType).andAnswer(new EqualsFilter<>(Boolean.FALSE));

        return  CollectionFilter.filter(getAssessmentCriteriaAnswersSet(), filter);
    }
    
    
    public String getCultureRingChart()
    {
        long    score   =   getCultureFitScore();
        Color   color   =   score >= 70 ? RingChart.GREEN : (score >= 50 ? RingChart.AMBER : RingChart.RED);
        
        return new RingChart(getID().toString() + "culture.jpeg").addData("Completed", score, color).addData("Incomplete", (100 - score), RingChart.GREY).getChartImage();
    }
    
    
    public String getRequirementRingChart()
    {
        long    score   =   getRequirementFitScore();
        Color   color   =   hasFailedEssentialRequirements() ? RingChart.RED : (score >= 80 ? RingChart.GREEN : (score >= 60 ? RingChart.AMBER : RingChart.RED));
        
        return new RingChart(getID().toString() + "requirement.jpeg").addData("Completed", score , color).addData("Incomplete", (100 - score), RingChart.GREY).getChartImage();
    }
    
    
    public String getRoleFitChart()
    {
        Tuple.T3<Double, ColorCode, Double> roleFitData     =   (getRoleFit() != null && getRoleFit().get(null) != null) ? (Tuple.T3<Double, ColorCode, Double>) getRoleFit().get(null) : null;

        double  score   =   roleFitData != null ? roleFitData.get2() > 0 ? roleFitData.get2() : 0d : 0d;
        Color   color   =   roleFitData != null ? Utils.getColor(roleFitData.get1()) : RingChart.GREEN;
        
        return new RingChart(getID().toString() + "rolefit.jpeg").addData("Completed", score , color).addData("Incomplete", (100 - score), RingChart.GREY).getChartImage();
    }
    
    public double requirementCompletedPercentage()
    {
        return getJob().getIncludeAssessmentCriteria() ? ((getAssessmentCriteriaAnswersCount() * 100) / getJob().getAssessmentCriteriasCount()) : 0d;
    }
    
    public boolean isFarFromJob()
    {
        if(getJob().getGoogleAddressText() != null && getCandidate().getGoogleAddressText() != null)
        {
            Distance    distance    =   DistanceUtils.calculateDistance(getCandidate().getGoogleAddressText() , getJob().getGoogleAddressText());

            if(distance != null)
            {
                return distance.inMeters / 1000 > getJob().getExpectedCandidateRadius().getDistanceInKM();
            }
        }
        return false;
    }
    
    @Override
    public Boolean getHappyToRelocate()
    {
        return isTrue(super.getHappyToRelocate());
    }
    
    @Override
    public Boolean getPreferRemote()
    {
        return isTrue(super.getPreferRemote());
    }
}