package performa.orm;

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.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.orm.types.*;
import performa.utils.AnalysisEngine;


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
    }

    
    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.
        }
        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(cultureCompleted())
        {
            return safeRedirect;
        }
        
        for(CultureCriteria cultureCriteria : getJob().getCultureCriteriasSet())
        {
            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 initAssessmentAnswers(List<Question> questions) throws StorageException, FieldException
    {
        boolean         redirect        =   false;
        List<Question>  allQuestions    =   new ArrayList<>();
        Candidate       candidate       =   getCandidate();
        
        for(Question question : questions)
        {
            allQuestions.add(question);
            
            if(question.getRightQuestion() != null)
            {
                allQuestions.add(question.getRightQuestion());
            }
        }
        
        for(Question question : allQuestions)            
        {
            boolean available   = false;

            for(Answer answer : candidate.getProfileAssessmentAnswersSet())
            {
                if(question.equals(answer.getQuestion()))
                {
                    available = true;
                    break;
                }
            }

            if(!available)
            {
                Answer  answer  =   Answer.createAnswer(getTransaction());
            
                candidate.addToProfileAssessmentAnswers(answer);
                answer.setQuestion(question);                
                redirect    =   true;
            }
        } 
        
        return redirect;
    }
  
    
    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 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 cultureCompleted()
    {
        return getCandidate().cultureCompleted(getJob());
    }
    
    
    public boolean assessmentCompleted() //role
    {
        return getCandidate().assessmentCompleted(getJob());
    }
    
    
    public int getActualQuestionNumber()
    {
        return getCandidate().pipelineCandidate().toProfileAssessmentAnswers().uniqueVals().size() + 1;
    }
    
    
    public int getQuestionNumber()
    {
        return getCandidate().pipelineCandidate().toProfileAssessmentAnswers().uniqueVals().size() 
                - getCandidate().pipelineCandidate().toProfileAssessmentAnswers().toQuestion().toRightQuestion().uniqueVals().size();
    }
    
    
    public boolean isIncludeAssessmentCriteria()
    {
        return getJob() != null && isTrue(getJob().getIncludeAssessmentCriteria());
    }

    @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();
    }
    
    public Double getRoleFitScore()
    {
        return getRoleFit() != null ? (Double)getRoleFit().get(null) : 0d;
    }
    
    public Long getCultureFitScore()
    {
        return getCultureFit() != null ? (Long) getCultureFit().get(null) : 0;
    }
    
    public Long getRequirementFitScore()
    {
        return getRequirementFit() != null ? (Long) getRequirementFit().get(null) : 0;
    }
    
    //This will return relative percentage considering topper as 100%
    public Double getRoleFitPercentage()
    {
        JobApplication  jobTopper   =   getJob() != null ? getJob().getTopper() : null;
        Double          myScore     =   getRoleFitScore();
        Double          topScore    =   jobTopper.getRoleFitScore();
        
        return NullArith.round(NullArith.divide(NullArith.multiply(myScore, 100), topScore), 2);
    }
    
    public List<AppProcessOption> getValidProcessOptions()
    {
        List<AppProcessOption>  options = new ArrayList<>();
        
        if(getApplicationStatus() == ApplicationStatus.SUBMITTED)
        {
            options.add(AppProcessOption.TO_SHORTLIST);
        }
        else if(getApplicationStatus() == ApplicationStatus.SHORTLISTED)
        {
            options.add(AppProcessOption.REMOVE_FROM_SHORTLIST);
        }
        
        return options;
    }
    
    
    public String getApplicantStatusStr()
    {
        return getApplicationStatus().getDescription();
    }
    
    
    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 Set<Answer> getCompletedAnswers()
    {
        return getCandidate().getProfileAssessmentAnswersSet();
    }
    
    
    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-07 min
        //cul-07 min
        //req-26 min
        
        int remainingTime   =   0;
        
        //REQ
        if(getJob()!=null && isTrue(getJob().getIncludeAssessmentCriteria()))
        {
            if(!selectionCompleted())
            {
                remainingTime   +=  7;
            }
        }
        
        if(!cultureCompleted())
        {
            remainingTime   +=  7;
        }
        
        //ROLE
        remainingTime   +=  getRoleTestRemainingTime() ;
        
        if(!assessmentCompleted())
        {
            remainingTime   += 6;
        }
        
        return remainingTime;
    }
    
    
    //to get remaining time for role test
    public Integer getRoleTestRemainingTime()
    {
        int remainingTime   =   20;
        
        if(assessmentCompleted())
        {
            remainingTime   -=  20;
        }
        else //partially completed test for role
        {
            int allAnswersCount =   getJob().getAllQuestions().size(); 

            if(allAnswersCount > 0)
            {
                Filter      filter          =   Answer.SearchByAll().andAnswerNo(new IsNotNullFilter<>());
                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, 20), 0);
                }
            }
        }
        return remainingTime;
    }

    @Override
    public boolean filterCandidateJob(Candidate candidate, Job job) throws StorageException
    {
        return (CollectionUtils.equals(candidate, getCandidate()) && CollectionUtils.equals(job, getJob()));
    }
}