/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed 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 com.googlecode.android_scripting;

import com.googlecode.android_scripting.interpreter.InterpreterConstants;
import com.trilead.ssh2.StreamGobbler;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;

public class Process {

  private static final int DEFAULT_BUFFER_SIZE = 8192;

  private final List<String> mArguments;
  private final Map<String, String> mEnvironment;

  private static final int PID_INIT_VALUE = -1;

  private File mBinary;
  private String mName;
  private long mStartTime;
  private long mEndTime;

  protected final AtomicInteger mPid;
  protected FileDescriptor mFd;
  protected OutputStream mOut;
  protected InputStream mIn;
  protected File mLog;

  public Process() {
    mArguments = new ArrayList<String>();
    mEnvironment = new HashMap<String, String>();
    mPid = new AtomicInteger(PID_INIT_VALUE);
  }

  public void addArgument(String argument) {
    mArguments.add(argument);
  }

  public void addAllArguments(List<String> arguments) {
    mArguments.addAll(arguments);
  }

  public void putAllEnvironmentVariables(Map<String, String> environment) {
    mEnvironment.putAll(environment);
  }

  public void putEnvironmentVariable(String key, String value) {
    mEnvironment.put(key, value);
  }

  public void setBinary(File binary) {
    if (!binary.exists()) {
      throw new RuntimeException("Binary " + binary + " does not exist!");
    }
    mBinary = binary;
  }

  public Integer getPid() {
    return mPid.get();
  }

  public FileDescriptor getFd() {
    return mFd;
  }

  public OutputStream getOut() {
    return mOut;
  }

  public OutputStream getErr() {
    return getOut();
  }

  public File getLogFile() {
    return mLog;
  }

  public InputStream getIn() {
    return mIn;
  }

  public void start(final Runnable shutdownHook) {
    if (isAlive()) {
      throw new RuntimeException("Attempted to start process that is already running.");
    }

    String binaryPath = mBinary.getAbsolutePath();
    Log.v("Executing " + binaryPath + " with arguments " + mArguments + " and with environment "
        + mEnvironment.toString());

    int[] pid = new int[1];
    String[] argumentsArray = mArguments.toArray(new String[mArguments.size()]);
    mLog = new File(String.format("%s/%s.log", InterpreterConstants.SDCARD_SL4A_ROOT, getName()));

    mFd =
        Exec.createSubprocess(binaryPath, argumentsArray, getEnvironmentArray(),
            getWorkingDirectory(), pid);
    mPid.set(pid[0]);
    mOut = new FileOutputStream(mFd);
    mIn = new StreamGobbler(new FileInputStream(mFd), mLog, DEFAULT_BUFFER_SIZE);
    mStartTime = System.currentTimeMillis();

    new Thread(new Runnable() {
      public void run() {
        int result = Exec.waitFor(mPid.get());
        mEndTime = System.currentTimeMillis();
        int pid = mPid.getAndSet(PID_INIT_VALUE);
        Log.v("Process " + pid + " exited with result code " + result + ".");
        try {
          mIn.close();
        } catch (IOException e) {
          Log.e(e);
        }
        try {
          mOut.close();
        } catch (IOException e) {
          Log.e(e);
        }
        if (shutdownHook != null) {
          shutdownHook.run();
        }
      }
    }).start();
  }

  private String[] getEnvironmentArray() {
    List<String> environmentVariables = new ArrayList<String>();
    for (Entry<String, String> entry : mEnvironment.entrySet()) {
      environmentVariables.add(entry.getKey() + "=" + entry.getValue());
    }
    String[] environment = environmentVariables.toArray(new String[environmentVariables.size()]);
    return environment;
  }

  public void kill() {
    if (isAlive()) {
      android.os.Process.killProcess(mPid.get());
      Log.v("Killed process " + mPid);
    }
  }

  public boolean isAlive() {
    return (mFd != null && mFd.valid()) && mPid.get() != PID_INIT_VALUE;
  }

  public String getUptime() {
    long ms;
    if (!isAlive()) {
      ms = mEndTime - mStartTime;
    } else {
      ms = System.currentTimeMillis() - mStartTime;
    }
    StringBuilder buffer = new StringBuilder();
    int days = (int) (ms / (1000 * 60 * 60 * 24));
    int hours = (int) (ms % (1000 * 60 * 60 * 24)) / 3600000;
    int minutes = (int) (ms % 3600000) / 60000;
    int seconds = (int) (ms % 60000) / 1000;
    if (days != 0) {
      buffer.append(String.format("%02d:%02d:", days, hours));
    } else if (hours != 0) {
      buffer.append(String.format("%02d:", hours));
    }
    buffer.append(String.format("%02d:%02d", minutes, seconds));
    return buffer.toString();
  }

  public String getName() {
    return mName;
  }

  public void setName(String name) {
    mName = name;
  }

  public String getWorkingDirectory() {
    return null;
  }
}