/*===========================================================================
  Copyright (C) 2010 by the Okapi Framework contributors
-----------------------------------------------------------------------------
  This library is free software; you can redistribute it and/or modify it 
  under the terms of the GNU Lesser General Public License as published by 
  the Free Software Foundation; either version 2.1 of the License, or (at 
  your option) any later version.

  This library 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 Lesser 
  General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License 
  along with this library; if not, write to the Free Software Foundation, 
  Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

  See also the full LGPL text here: http://www.gnu.org/copyleft/lesser.html
===========================================================================*/

package net.sf.okapi.steps.externalcommand;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.sf.okapi.common.Event;
import net.sf.okapi.common.IParameters;
import net.sf.okapi.common.UsingParameters;
import net.sf.okapi.common.exceptions.OkapiIOException;
import net.sf.okapi.common.pipeline.BasePipelineStep;
import net.sf.okapi.common.pipeline.annotations.StepParameterMapping;
import net.sf.okapi.common.pipeline.annotations.StepParameterType;
import net.sf.okapi.common.resource.RawDocument;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.Executor;
import org.apache.commons.exec.PumpStreamHandler;

/**
 * Run a Command line tool on {@link RawDocument} {@link Event}s. The step returns a RawDocument Event generated by the
 * external command. <code>${inputPath}</code> and <code>${outputPath}</code> variables must be defined in the command
 * line string. For example:
 * <h6>"sort ${inputPath} /O ${outputPath}"</h6>
 * is a valid windows command which sorts lines on a file.
 * 
 * @author HARGRAVEJE
 */
@UsingParameters(Parameters.class)
public class ExternalCommandStep extends BasePipelineStep {
	
	private static final Logger LOGGER = Logger.getLogger(ExternalCommandStep.class.getName());

	private static final String INPUT_FILE_VAR = "inputPath";
	private static final String OUTPUT_FILE_VAR = "outputPath";

	private boolean done = false;
	private Parameters parameters;
	private Executor executor;
	private ExecuteWatchdog watchdog;
	private URI outputURI;

	public ExternalCommandStep () {
		parameters = new Parameters();
	}

	@StepParameterMapping(parameterType = StepParameterType.OUTPUT_URI)
	public void setOutputURI(URI outputURI) {
		this.outputURI = outputURI;
	}

	@Override
	public IParameters getParameters() {
		return parameters;
	}

	@Override
	public void setParameters(IParameters params) {
		this.parameters = (Parameters) params;
	}

	@Override
	public boolean isDone() {
		return done;
	}

	@Override
	public String getDescription() {
		return "Execute an external command line.";
	}

	@Override
	public String getName() {
		return "External Command";
	}

	@Override
	protected Event handleStartBatch(Event event) {
		done = false;
		executor = new DefaultExecutor();
		// set process timeout if value is greater than zero
		if (parameters.getTimeout() > 0) {
			// convert from seconds to milliseconds
			watchdog = new ExecuteWatchdog(parameters.getTimeout() * 1000L);
			executor.setWatchdog(watchdog);
		}

		return event;
	}

	@Override
	protected Event handleRawDocument(Event event) {
		
		int exitValue;
		Map<String, String> subtitutions = new HashMap<String, String>();
		RawDocument rawDoc = event.getRawDocument();
		
		// Set input path variable
		String inputPath = (new File(rawDoc.getInputURI()).getPath());
		subtitutions.put(INPUT_FILE_VAR, inputPath);
		
		// Set output path variable
		String outputPath = inputPath + ".out"; // Default
		if ( isLastOutputStep() && outputURI != null && !outputURI.getPath().isEmpty() ) {
			outputPath = (new File(outputURI).getPath());
		}
		subtitutions.put(OUTPUT_FILE_VAR, outputPath);

		// Set source-related variables
		subtitutions.put("srcLangName", rawDoc.getSourceLocale().toJavaLocale().getDisplayLanguage(Locale.ENGLISH));
		subtitutions.put("srcLang", rawDoc.getSourceLocale().getLanguage());
		
		// Set target-related variables
		subtitutions.put("trgLangName", rawDoc.getTargetLocale().toJavaLocale().getDisplayLanguage(Locale.ENGLISH));
		subtitutions.put("trgLang", rawDoc.getTargetLocale().getLanguage());
		
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		ByteArrayOutputStream err = new ByteArrayOutputStream();
		PumpStreamHandler psh = new PumpStreamHandler(out, err);

		try {
			psh.start();
			CommandLine cl = CommandLine.parse(parameters.getCommand(), subtitutions);
			LOGGER.log(Level.INFO, "External Command: " + cl.toString());
			exitValue = executor.execute(cl);
		}
		catch (ExecuteException e) {
			throw new RuntimeException(e);
		}
		catch (IOException e) {
			throw new OkapiIOException(e);
		}

		if (watchdog != null && watchdog.killedProcess()) {
			throw new RuntimeException("Command line process timed out: " + out.toString());
		}

		if (executor.isFailure(exitValue)) {
			throw new RuntimeException("Command line process failed: " + err.toString());
		}

		psh.stop();
		try {
			out.close();
		} catch (IOException e) {
			throw new OkapiIOException("Error closing process output streamm.", e);
		}
		try {
			err.close();
		} catch (IOException e) {
			throw new OkapiIOException("Error closing process error streamm", e);
		}

		// Create new event resource pointing to the output
		RawDocument outRawDoc = null;
		outRawDoc = new RawDocument((new File(outputPath)).toURI(), rawDoc.getEncoding(),
			rawDoc.getSourceLocale(), rawDoc.getTargetLocale());

		event.setResource(outRawDoc);
		done = true;
		return event;
	}

	@Override
	protected Event handleEndBatch(Event event) {
		return event;
	}

}
