/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.sysds.validation;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.lang.Process;
import java.lang.ProcessBuilder;
import java.time.format.DateTimeFormatter;
import java.time.LocalDateTime;


/**
 * This is an utility class to include any utility functios.
 */
public class Utility
{
	/**
	 * This will be used to output on console in consistent and controlled based on DEBUG flag.
	 *
	 * @param	debugLevel is the debuglevel message user wants to prrint message.
	 * @param 	message is the message to be displayed.
	 * @return
	 */
	public static void debugPrint(int debugLevel, String message) {
		debugPrint(debugLevel, message, null);
	}

	/**
	 * This will be used to output on console in consistent and controlled based on DEBUG flag.
	 *
	 * @param	debugLevel is the debuglevel message user wants to prrint message.
	 * @param 	message is the message to be displayed.
	 * @param   strOutputFile is the filename to print output.
	 * @return
	 */
	public static void debugPrint(int debugLevel, String message, String strOutputFile) {

		if(debugLevel > Constants.DEBUG_PRINT_LEVEL)
			return;

		String displayMessage = "";
		switch (debugLevel) {
			case Constants.DEBUG_ERROR:
				displayMessage = "ERROR: " + message;
				break;
			case Constants.DEBUG_WARNING:
				displayMessage = "WARNING: " + message;
				break;
			case Constants.DEBUG_INFO:
				displayMessage = "INFO: " + message;
				break;
			case Constants.DEBUG_INFO2:
				displayMessage = "INFO2: " + message;
				break;
			case Constants.DEBUG_INFO3:
				displayMessage = "INFO3: " + message;
				break;
			case Constants.DEBUG_CODE:
				displayMessage = "DEBUG: " + message;
				break;
			default:
				break;
		}

		LocalDateTime currentDateTime = LocalDateTime.now();
		String strDateTime = currentDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);

		if(strOutputFile == null || strOutputFile.trim().length() == 0)
			System.out.println(strDateTime + ": " + displayMessage);
		else {
			PrintWriter printWriter = null;
			try {
				printWriter = new PrintWriter(new BufferedWriter(new FileWriter(strOutputFile, true)));
				printWriter.println(strDateTime + ": " + displayMessage);
			} catch (IOException ioe) {
				debugPrint(Constants.DEBUG_ERROR, "Exception while printing message to file " + strOutputFile + ioe);
				debugPrint(debugLevel, message);
			}
			if(printWriter != null)
				printWriter.close();
		}
	}

	/**
	 * This function will run command specified abd redirect stdout/stderr per instruction.
	 *
	 * @param
	 * @param
	 * @return
	 */
	public static int runCommand(String [] command, String strCurDir, String strOutputFile, String strErrorFile, String strMessage)
		throws IOException
	{
		if(strMessage != null && strMessage.trim().length() > 0)
			debugPrint(Constants.DEBUG_INFO, strMessage, strOutputFile);

		debugPrint(Constants.DEBUG_CODE, "Running command: '" + String.join(" ", command) + "'", strOutputFile);

		ProcessBuilder processBuilder = new ProcessBuilder(command);

		// Set current working directory for command to run.
		if(strCurDir != null && strCurDir.trim().length() > 0)
			processBuilder.directory(new File(strCurDir));

		// Start the process
		Process process = processBuilder.start();

		// Read the output from runtime and redirect to file/stdout
		InputStreamReader insIn = new InputStreamReader(process.getInputStream());
		BufferedReader bufIn = new BufferedReader(insIn);

		boolean bWriteOutToFile = false;
		PrintWriter writerOut = null;
		try {
			if(strOutputFile != null && strOutputFile.trim().length() > 0)
				bWriteOutToFile = true;
			if(bWriteOutToFile)
				writerOut = new PrintWriter(new BufferedWriter(new FileWriter(strOutputFile,true)));

			// Read standard output after running the command.
			String strLine;
			while ((strLine = bufIn.readLine()) != null) {
				if (bWriteOutToFile)
					writerOut.println(strLine);
				else
					debugPrint(Constants.DEBUG_INFO2, "StdOut: " + strLine);
			}
		} catch (IOException ioe) {
			debugPrint(Constants.DEBUG_ERROR, "Exception occured while reading from process output: " + ioe, strOutputFile);
		}
		if(bWriteOutToFile && writerOut != null)
			writerOut.close();

		// Read the output from runtime and redirect to file/stdout
		InputStreamReader insErr = new InputStreamReader(process.getErrorStream());
		BufferedReader bufErr = new BufferedReader(insErr);

		boolean bWriteErrToFile = false;
		PrintWriter writerErr = null;
		try {
			if(strErrorFile != null && strErrorFile.trim().length() > 0)
				bWriteErrToFile = true;
			if(bWriteErrToFile)
				writerErr = new PrintWriter(new BufferedWriter(new FileWriter(strErrorFile,true)));

			// Read standard output after running the command.
			String strLine;
			while ((strLine = bufErr.readLine()) != null) {
				if (bWriteErrToFile)
					writerErr.println(strLine);
				else
					debugPrint(Constants.DEBUG_INFO2, "StdErr: " + strLine);
			}
		} catch (IOException ioe) {
			debugPrint(Constants.DEBUG_ERROR, "Exception occured while reading from process error stream: " + ioe, strOutputFile);
		}
		if(bWriteErrToFile && writerErr != null)
			writerErr.close();

		// Wait for program to exit.
		int exitValue = -1;
		try {
			exitValue = process.waitFor();
		} catch (InterruptedException ie) {
			Thread.currentThread().interrupt();
			debugPrint(Constants.DEBUG_ERROR, "Program interrunpted: " + ie);
		}
		debugPrint(Constants.DEBUG_CODE, "Program '" + String.join(" ", command) + "' exited with exit status " + exitValue, strOutputFile);

		return exitValue;
	}

}
