package jnormaliz;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import javax.swing.SwingWorker;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.ExecuteResultHandler;
import org.apache.commons.exec.PumpStreamHandler;

/**
 * Worker class that launches and controls Normaliz command line
 */
public class NormalizWorker extends SwingWorker<Boolean, String> {

    private boolean executionOk = false;
    private boolean interrupted = false;

    private final NormalizConfig normalizConfig;
    private final MainView view;
    private final NormalizProcessDestroyer processDestroyer =
            new NormalizProcessDestroyer();

    static private File executable;
//    static private File executableBig;

    static private final SimpleDateFormat dateFormat = new SimpleDateFormat(
            "dd.MM.yyyy HH:mm");

    /**
     * Tests whether the Normaliz executables are reachable
     * @return true if all where found, false otherwise
     */
    static public boolean findNormalizExecutables() {

        final String exec64 = "./normaliz";
     //   final String execBig = "./normaliz";
        String extension;

        if (System.getProperty("os.name").startsWith("Windows")) {
            extension = ".exe";
        } else {
            extension = "";
        }

        executable = new File(exec64 + extension);
      //  executableBig = new File(execBig + extension);

        if ( !executable.canExecute()) {
            return false;
        }

        return true;
    }

    /**
     * Creates a new instance of NormalizWorker
     * @param normalizConfig parameters for this Normaliz execution
     */
    public NormalizWorker(NormalizConfig normalizConfig) {
        super();

        this.normalizConfig = normalizConfig;
        view = (MainView) JNormalizApplication.getApplication().getMainView();
    }

    /**
     * Executes Normaliz in background
     * @return true if execution finished well, false if there was some error
     * @throws Exception
     */
    @Override
    protected Boolean doInBackground() throws Exception {

        CommandLine commandLine;
        commandLine = new CommandLine(executable);
        
        if ( !executable.canExecute()) {
            commandLine = new CommandLine("normaliz");
        }

        //precision
        switch (normalizConfig.precision) {
            case PRECISION64:
                break;


            case PRECISIONBIG:
                commandLine.addArgument("-B");
                break;

            default:
                throw new RuntimeException("Unknown precision");
        }

        commandLine.addArgument("-c"); // verbose
        //commandLine.addArgument("-i"); // ignore setup file for Normliz version 2.1, 2.2

        // Computation type
        switch (normalizConfig.computationType) {
            case DEFAULT:
                break;
                
            case SUPPORT_HYPERPLANES:
                commandLine.addArgument("-s");
                break;

            case TRIANGULATION:
                commandLine.addArgument("-t");
                break;

            case VOLUME:
                commandLine.addArgument("-v");
                break;
                
            case VOLUME_DEGREE_1_ELEMENTS:
                commandLine.addArgument("-v1");
                break;

            case HILBERT_BASIS_VOLUME:
                commandLine.addArgument("-n");
                break;

            case HILBERT_BASIS:
                commandLine.addArgument("-N");
                break;

            case DEGREE_1_ELEMENTS:
                commandLine.addArgument("-1");
                break;

            case HILBERT_SERIES:
                commandLine.addArgument("-q");
                break;
                
            case HILBERT_SERIES_DEGREE_1_ELEMENTS:
                commandLine.addArgument("-p");
                break;


            case HILBERT_BASIS_SERIES:
                commandLine.addArgument("-h");
                break;


            default:
                throw new RuntimeException("Unknown computation type");
        }
        
       //algorithm 
       switch (normalizConfig.algorithm) {
            case PRIMAL:
                break;

            case DUAL:
                commandLine.addArgument("-d");
                break;
                
            case APPROXIMATION:
                commandLine.addArgument("-r");
                break;

            default:
                throw new RuntimeException("Unknown algorithm");
        }

        // Output files options

        switch (normalizConfig.outputFiles) {
            case SOME:
                commandLine.addArgument("-f");
                break;

            case ALL:
                commandLine.addArgument("-a");
                break;

            case ONLY_OUT:
                break;

            default:
                throw new RuntimeException("Unknown output files");
        }
        
                // Output files options

        switch (normalizConfig.nmzIntegrateOptions) {
            case EHRHART:
                commandLine.addArgument("-E");
                break;

            case LEADING:
                commandLine.addArgument("-L");
                break;
                
            case LEBESQUE:
                commandLine.addArgument("-I");
                break;

            case NONE:
                break;

            default:
                throw new RuntimeException("Unknown output files");
        }

     /*   if (normalizConfig.saveMemory) {   // option for Normliz version 2.1, 2.2
            commandLine.addArgument("-m");
        } */

        if (normalizConfig.testArithmeticOverflow) {
            commandLine.addArgument("-e");
        }
        
        if (normalizConfig.writeTriangulationFile) {
            commandLine.addArgument("-T");
        }
        
        if (normalizConfig.writeStanleyDecompositionFile) {
            commandLine.addArgument("-y");
        }

        if (normalizConfig.controlParalellization) {
            commandLine.addArgument("-x="+normalizConfig.numberParallelThreads);
        }

        commandLine.addArgument(normalizConfig.getInFileWithoutExt());

        DefaultExecutor executor = new DefaultExecutor();

        executor.setProcessDestroyer(processDestroyer);

        ExecuteResultHandler executeResultHandler = new ExecuteResultHandler() {

            public void onProcessComplete(int exitValue) {
                executionOk = true;
            }

            public void onProcessFailed(ExecuteException e) {
                executionOk = false;
            }
        };

        PipedInputStream inputStream = new PipedInputStream();
        PipedOutputStream outputStream = new PipedOutputStream(inputStream);

        PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);

        executor.setStreamHandler(streamHandler);

        publish("\n=====<<<<< " + dateFormat.format(Calendar.getInstance().getTime()) + " >>>>>=====\n");
        publish(commandLine.toString() + "\n");
       
        executor.execute(commandLine, executeResultHandler);
      

        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

        String consoleText;
        do {
            consoleText = bufferedReader.readLine();

            if (consoleText != null) {
                publish(consoleText + "\n");
            }
 
        } while (consoleText != null);
        return executionOk;
    }

    @Override
    protected void process(List<String> chunks) {

        for (String s : chunks) {
            view.updateConsole(s);
        }
    }

    /**
     * Notifies the interface of the result of the execution, unless it
     * was interrupted by the interface. This method is called when
     * doInBackground finishes
     */
    @Override
    protected void done() {
        if (!interrupted) {
            view.jobFinished(executionOk);
        }
    }

    /**
     * Interrupts Normaliz
     */
    public void stop() {
        interrupted = true;
        processDestroyer.stop();
    }
}
