/*
 * The Kuali Financial System, a comprehensive financial management system for higher education.
 *
 * Copyright 2005-2016 The Kuali Foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kuali.kfs.sys.context;


import java.util.Arrays;


/**
 * BatchStepTrigger writes .run files containing a job name and step name for BatchContainerStep to read.
 * It loops and sleeps until either a .success or an .error file is written for the Step. Once a result file is found tt logs the results and exits.
 * <p>
 * BatchStepTrigger also checks for BatchContainerStep's .runlock file. If it doesn't find one (indicating the batch container is not running) it exits.
 * BatchStepTrigger adds a ConsoleAppender to its Logger if one hasn't been configured.
 * <p>
 * Note that this class runs without starting the SpringContext. KFS Services and Beans are not available for use.
 */
public class BatchStepTrigger {
    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BatchStepTrigger.class);

    private static BatchStepTriggerParameters batchStepTriggerParameters;

    /**
     * BatchStepTrigger is instantiated for each step in a brte script.
     * The value used when exiting the system tells the brte script how to handle failures.
     * 0: okay, 4: the step returned false, 8: an exception occurred in the execution of the step or the trigger
     * <p>
     * - verify that the batch container is running, exit with an 8 if not
     * - for each step
     * - write a RUN semaphore file
     * - wait and listen for a result file (either SUCCESS or ERROR)
     * - if the result file is null the batch container is not running so remove the run file and exit with an 8
     * - if the result file is an ERROR
     * - if the file is EMPTY remove the result file and exit with a 4
     * - otherwise log the error contained in the result file, remove the result file, and exit with an 8
     * - otherwise remove the result file and exit with a 0
     *
     * @param args - refer to BatchStepTriggerParameters for details
     */
    public static void main(String[] args) {
        try {
            Log4jConfigurer.configureLogging(false);

            batchStepTriggerParameters = new BatchStepTriggerParameters(args);

            String[] stepNames = getStepNames();
            String jobName = getJobName();
            int stepIndex = getStepIndex();
            long sleepInterval = getSleepInterval();
            BatchContainerDirectory batchContainerDirectory = getBatchContainerDirectory();

            LOG.info("Executing Job: " + jobName + ", STEP" + stepIndex + ", Step(s): " + Arrays.toString(stepNames));

            if (!batchContainerDirectory.isBatchContainerRunning()) {
                //an instance of the batch container is not running - exit. Exit status: 8
                LOG.error("The BatchContainer is not running - exiting without executing the steps: " + Arrays.toString(stepNames));
                LOG.info("Exit status: 8");
                System.exit(8);
            }

            //Need to humanize 'i'; the index
            for (int i = (stepIndex - 1); i < stepNames.length; i++) {
                String stepName = stepNames[i];
                BatchStepFileDescriptor batchStepFile = new BatchStepFileDescriptor(jobName, stepName, BatchStepFileDescriptor.getFileExtensionRun());

                //write step start file
                batchContainerDirectory.writeBatchStepRunFile(batchStepFile, i);

                //wait for a result file from BatchContainer
                BatchStepFileDescriptor resultFile = listenForResultFile(batchContainerDirectory, batchStepFile, sleepInterval);

                if (resultFile == null) {
                    //result file is null - something unexpected happened. Exit status: 8
                    batchContainerDirectory.removeBatchStepFileFromSystem(batchStepFile);

                    LOG.error("No result files were returned- exiting without knowing whether the Step was executed");
                    LOG.info("Exit status: 8");
                    System.exit(8);
                }

                if (resultFile.isStepFileAnErrorResultFile()) {

                    if (batchContainerDirectory.isFileEmpty(resultFile)) {
                        //do not execute any more steps, but job should succeed. Exit status: 4
                        LOG.error(batchStepFile + " failed");
                        batchContainerDirectory.removeBatchStepFileFromSystem(resultFile);

                        LOG.info("Exit status: 4");
                        System.exit(4);
                    } else {

                        //if file is not empty do not execute any more steps and fail job (write exception to log). Exit status: 8
                        LOG.error(batchStepFile + " failed with the following error message: ");
                        batchContainerDirectory.logFileContents(resultFile, LOG);
                        batchContainerDirectory.removeBatchStepFileFromSystem(resultFile);

                        LOG.info("Exit status: 8");
                        System.exit(8);
                    }
                }

                batchContainerDirectory.removeBatchStepFileFromSystem(resultFile);
                LOG.info("Exiting " + batchStepFile);

            }

            //continue executing steps in the job. Exit status: 0
            LOG.info("Exit status: 0");

            System.exit(0);
        } catch (Throwable t) {
            System.err.println("ERROR: Exception caught: ");
            t.printStackTrace(System.err);
            LOG.error(t);

            System.exit(8);
        }
    }

    /**
     * Loop - look for a result file in the directory, if none is found then sleep.
     * If the batch container is not running then write an error result file and return it.
     *
     * @param batchContainerDirectory the directory in which the semaphore files are located
     * @param batchStepFile           the step descriptor for the current step
     * @param sleepInterval           the amount of time to sleep while waiting for a result file
     * @return the step descriptor of the result file
     */
    private static BatchStepFileDescriptor listenForResultFile(BatchContainerDirectory batchContainerDirectory, BatchStepFileDescriptor batchStepFile, long sleepInterval) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Waiting for result file for " + batchStepFile);
        }

        while (true) {
            //look for a result file in file-system
            BatchStepFileDescriptor resultFile = batchContainerDirectory.getResultFile(batchStepFile);
            if (resultFile != null) {
                LOG.info("Found result file: " + resultFile.getName());

                return resultFile;
            }

            if (batchContainerDirectory.isBatchContainerRunning()) {
                sleep(sleepInterval);
            } else {
                //the batch container is not running - return an error file with an exception
                batchContainerDirectory.writeBatchStepErrorResultFile(batchStepFile, new RuntimeException("The BatchContainer is not running - exiting without knowing whether the Step executed"));

                resultFile = batchContainerDirectory.getResultFile(batchStepFile);
                return resultFile;
            }
        }
    }

    /**
     * Sleep for the specified amount of time
     *
     * @param sleepInterval the amount of time (in milliseconds) to wait before looking for a result file
     */
    private static void sleep(long sleepInterval) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Sleeping...");
        }
        try {
            Thread.sleep(sleepInterval);
        } catch (InterruptedException e) {
            throw new RuntimeException("BatchStepTrigger encountered interrupt exception while trying to wait for the specified batch step semaphore processing interval", e);
        }
    }

    /**
     * @return the names of the steps to be executed
     */
    private static String[] getStepNames() {
        return batchStepTriggerParameters.getStepNames();
    }

    /**
     * @return the name of the job in which the steps are running
     */
    private static String getJobName() {
        return batchStepTriggerParameters.getJobName();
    }

    /**
     * @return the index of the step in the job
     */
    private static int getStepIndex() {
        return batchStepTriggerParameters.getStepIndex();
    }

    /**
     * @return the amount of time to sleep (in milliseconds) while waiting for a result file
     */
    private static long getSleepInterval() {
        return batchStepTriggerParameters.getSleepInterval();
    }

    /**
     * @return the directory in which the semaphore files are located
     */
    private static BatchContainerDirectory getBatchContainerDirectory() {
        return batchStepTriggerParameters.getBatchContainerDirectory();
    }
}
