package org.msh.tb.cases.calculated;

import org.jboss.seam.Component;
import org.msh.tb.application.TransactionManager;
import org.msh.tb.application.tasks.AsyncTaskImpl;
import org.msh.tb.entities.TaskLog;
import org.msh.tb.ng.entities.TbCaseNG;

import javax.persistence.EntityManager;
import java.util.Date;

/**
 * Async task class that updates all tbcases rif resistance field according
 * to the current business rule defined in RifResistanceCalcService.
 *
 * Before starting this async task the first case id is calculated, and then,
 * this task will update each case after this id.
 *
 * This should be used when the rule in RifResistanceCalcService changes and the already affected cases needs to be updated.
 *
 * Created by msantos on 2018-04-24
 */
public class RifResistanceFixAsyncTask extends AsyncTaskImpl {

    protected TransactionManager transaction;

    private Integer firstCaseIdUpdated;
    private Integer lastCaseIdUpdated;
    private int totalCasesUpdated = 0;

    @Override
    protected void starting() {

    }

    @Override
    protected void execute() {
        firstCaseIdUpdated = (Integer) getParameter("firstCaseId");
        Integer currentCaseId = firstCaseIdUpdated;

        // start the first transaction
        if (!getTransaction().isActive()) {
            getTransaction().begin();
        }

        while (currentCaseId != null) {
            // fix rif resistance
            fixRifResistance(currentCaseId);

            // get next case id
            currentCaseId = getNextCaseId(currentCaseId);

            // count total cases fixed
            totalCasesUpdated++;

            // commit and start a new transaction for each 400 cases updated
            if (totalCasesUpdated % 1500 == 0) {
                commitAndStartNewTransaction();
            }
        }
    }

    /**
     * Query the case that matches caseId in database and uses RifResistanceCalcService.calcRifResistance
     * to calculate the rif resistance.
     *
     * @param caseId
     * @return
     */
    private boolean fixRifResistance(Integer caseId) {
        EntityManager em = getTransaction().getEntityManager();

        // get tbcase instance
        TbCaseNG tbcase = em.find(TbCaseNG.class, caseId);

        // if case not found, just go ahead
        if (tbcase == null) {
            return false;
        }

        // calculate and set value in tbcase
        tbcase.setRifResistance(RifResistanceCalcService.calcRifResistance(tbcase));

        // save and commit transaction
        em.persist(tbcase);
        return true;
    }

    /**
     * Get next case id after the currentCaseId
     * @param currentCaseId
     * @return
     */
    private Integer getNextCaseId(Integer currentCaseId) {
        EntityManager em = getTransaction().getEntityManager();

        Integer next = (Integer) em.createNativeQuery("select min(id) from tbcase where id > :currentCaseId")
                .setParameter("currentCaseId", currentCaseId)
                .getSingleResult();

        // update the last case id updated
        lastCaseIdUpdated = currentCaseId;

        return next;
    }

    private void commitAndStartNewTransaction() {
        getTransaction().getEntityManager().flush();

        getTransaction().commit();

        if (!getTransaction().isActive()) {
            getTransaction().begin();
        }
    }

    @Override
    protected void finishing() {
        addlogData("Fixed cases from id ".concat(firstCaseIdUpdated.toString()).concat(" to ").concat(lastCaseIdUpdated.toString()).concat(". "));
        addlogData("Total updated: ".concat(Integer.toString(totalCasesUpdated)).concat("."));

        // registering task log here because this task is controlling its EntityManager transactions.
        Date execStart = getExecutionTimestamp();

        TaskLog taskLog = new TaskLog();
        taskLog.setTaskId(getId());
        taskLog.setTaskClass(this.getClass().getCanonicalName());
        taskLog.setExecStart(execStart);
        taskLog.setExecEnd(new Date());
        taskLog.setData(this.getLogData());
        taskLog.setStatus(this.getStatus());

        EntityManager em = getTransaction().getEntityManager();
        em.persist(taskLog);
        em.flush();

        // commit last transaction
        getTransaction().commit();
    }

    /**
     * Return the transaction in use by the task
     * @return
     */
    protected TransactionManager getTransaction() {
        if (transaction == null)
            transaction = (TransactionManager) Component.getInstance("transactionManager");
        return transaction;
    }

    /**
     * Avoid Task Manager of registering task log because this task is controlling the DB transaction
     */
    @Override
    public boolean isLogged() {
        return false;
    }
}
