/*
 * This file is part of the OSMembrane project.
 * More informations under www.osmembrane.de
 * 
 * The project is licensed under the GNU GENERAL PUBLIC LICENSE 3.0.
 * for more details about the license see http://www.osmembrane.de/license/
 * 
 * Source: $HeadURL$ ($Revision$)
 * Last changed: $Date$
 */

package de.osmembrane.tools;

import java.awt.event.ActionEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import javax.swing.Action;

import de.osmembrane.Application;
import de.osmembrane.controller.ActionRegistry;
import de.osmembrane.exceptions.ControlledException;
import de.osmembrane.exceptions.ExceptionSeverity;
import de.osmembrane.view.interfaces.IExecutionStateDialog;

/**
 * Executor for a pipeline
 * 
 * @author jakob_jarosch
 */
public class PipelineExecutor extends Thread implements WindowListener {

    private String osmosisPath;
    private File workingDirectory;
    private List<String> parameters;
    private IExecutionStateDialog dialog;

    private Class<? extends Action> callbackEvent;

    private int executionState = -1;

    /**
     * Initialize the pipeline executor.
     * 
     * @throws IllegalArgumentException
     *             when the osmosis executable is not runnable.
     */
    public PipelineExecutor(String osmosisPath, String workingDirectory,
            List<String> parameters, IExecutionStateDialog dialog) {
        setPath(osmosisPath);
        this.workingDirectory = new File(workingDirectory);
        this.parameters = parameters;
        this.dialog = dialog;
        dialog.addWindowListener(this);

        dialog.setState(I18N.getInstance().getString(
                "Tools.PipelineExecutor.ParametersSet"));
        dialog.setProgress(5);
        dialog.setCloseButtonCaption(I18N.getInstance()
                .getString("View.Cancel"));
    }

    /**
     * Sets a call back action which is called after completing of the
     * execution.
     * 
     * @param event
     */
    public void setCallbackAction(Class<? extends Action> event) {
        this.callbackEvent = event;
    }

    @Override
    public void run() {
        dialog.setState(I18N.getInstance().getString(
                "Tools.PipelineExecutor.StartingOsmosis"));
        dialog.setProgress(10);

        List<String> cmdLine = new ArrayList<String>(parameters.size() + 1);
        cmdLine.add(osmosisPath);
        cmdLine.addAll(parameters);

        try {
            ProcessBuilder processBuilder = new ProcessBuilder(cmdLine);
            Process process = processBuilder.directory(workingDirectory)
                    .redirectErrorStream(true).start();

            final BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()));

            dialog.setState(I18N.getInstance().getString(
                    "Tools.PipelineExecutor.RunningOsmosis"));
            dialog.setProgress(10);

            String line = null;
            do {
                try {
                    ReadThread thread = new ReadThread(reader);
                    thread.start();
                    thread.join();
                    line = thread.getLine();
                } catch (InterruptedException e) {
                    interrupt();
                    line = null;
                }

                if (line != null) {
                    dialog.addOutputLine(line);
                }
            } while (line != null && !isInterrupted());

            if (!isInterrupted()) {
                int exitValue = -255;
                try {
                    exitValue = process.waitFor();
                    if (exitValue == 0) {
                        dialog.setState(I18N.getInstance().getString(
                                "Tools.PipelineExecutor.Finished"));
                        dialog.setCloseButtonCaption(I18N.getInstance()
                                .getString("View.OK"));
                        dialog.setProgress(100);
                    } else {
                        dialog.setState(I18N.getInstance().getString(
                                "Tools.PipelineExecutor.Failed"));
                        dialog.setCloseButtonCaption(I18N.getInstance()
                                .getString("View.Close"));
                    }

                    executionState = exitValue;
                } catch (InterruptedException e) {
                    interrupt();
                }
            } else {
                process.destroy();
                reader.close();
            }

        } catch (IOException e) {
            dialog.setState(I18N.getInstance().getString(
                    "Tools.PipelineExecutor.IOExecption"));
            dialog.setProgress(0);
            Application.handleException(new ControlledException(this,
                    ExceptionSeverity.WARNING, e, I18N.getInstance().getString(
                            "Tools.PipelineExecutor.IOExecption")));
        }
    }

    /**
     * Sets the osmosisPath and validates it.
     * 
     * @param osmosisPath
     */
    private void setPath(String osmosisPath) {
        if (osmosisPath == null) {
            throw new IllegalArgumentException();
        }
        File f = new File(osmosisPath);
        if (!f.isFile() || !f.canExecute() || !f.canRead()) {
            throw new IllegalArgumentException();
        }
        this.osmosisPath = osmosisPath;
    }

    /**
     * The Windows-closed action of the dialog in the view.
     */
    @Override
    public void windowClosing(WindowEvent e) {
        dialog.removeWindowListener(this);

        if (this.isAlive()) {
            this.interrupt();
        } else {
            if (callbackEvent != null && executionState == 0) {
                ActionRegistry.getInstance().get(callbackEvent)
                        .actionPerformed(new ActionEvent(this, 0, "finished"));
            }
        }
    }

    @Override
    public void windowClosed(WindowEvent e) {
    }

    @Override
    public void windowOpened(WindowEvent e) {
    }

    @Override
    public void windowIconified(WindowEvent e) {
    }

    @Override
    public void windowDeiconified(WindowEvent e) {
    }

    @Override
    public void windowActivated(WindowEvent e) {
    }

    @Override
    public void windowDeactivated(WindowEvent e) {
    }
}

/**
 * ReadThread which is able to be interrupted while reading a line.
 * 
 * @author jakob_jarosch
 */
class ReadThread extends Thread {

    private BufferedReader reader;
    private String line;

    public ReadThread(BufferedReader reader) {
        this.reader = reader;
    }

    @Override
    public void run() {
        line = null;
        try {
            line = reader.readLine();
        } catch (IOException e) {
            line = null;
        }
    }

    public String getLine() {
        return line;
    }
}
