/*
 * 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.facade;

import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;

import com.googlecode.android_scripting.Log;
import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
import com.googlecode.android_scripting.rpc.Rpc;
import com.googlecode.android_scripting.rpc.RpcParameter;
import com.googlecode.android_scripting.rpc.RpcOptional;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.json.JSONException;
import org.json.JSONObject;

/**
 * Facade for managing Applications.
 *
 */
public class ApplicationManagerFacade extends RpcReceiver {

  private final Service mService;
  private final AndroidFacade mAndroidFacade;
  private final ActivityManager mActivityManager;
  private final PackageManager mPackageManager;

  public ApplicationManagerFacade(FacadeManager manager) {
    super(manager);
    mService = manager.getService();
    mAndroidFacade = manager.getReceiver(AndroidFacade.class);
    mActivityManager = (ActivityManager) mService.getSystemService(Context.ACTIVITY_SERVICE);
    mPackageManager = mService.getPackageManager();
  }

  @Rpc(description = "Returns a list of all launchable application class names.")
  public Map<String, String> getLaunchableApplications() {
    Intent intent = new Intent(Intent.ACTION_MAIN);
    intent.addCategory(Intent.CATEGORY_LAUNCHER);
    List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent, 0);
    Map<String, String> applications = new HashMap<String, String>();
    for (ResolveInfo info : resolveInfos) {
      applications.put(info.loadLabel(mPackageManager).toString(), info.activityInfo.name);
    }
    return applications;
  }

  @Rpc(description = "Start activity with the given class name.")
  public void launch(@RpcParameter(name = "className") String className) {
    Intent intent = new Intent(Intent.ACTION_MAIN);
    String packageName = className.substring(0, className.lastIndexOf("."));
    intent.setClassName(packageName, className);
    mAndroidFacade.startActivity(intent);
  }

  @Rpc(description = "Start activity with the given class name with result")
  public Intent launchForResult(@RpcParameter(name = "className") String className) {
    Intent intent = new Intent(Intent.ACTION_MAIN);
    String packageName = className.substring(0, className.lastIndexOf("."));
    intent.setClassName(packageName, className);
    return mAndroidFacade.startActivityForResult(intent);
  }

  @Rpc(description = "Launch the specified app.")
  public void appLaunch(@RpcParameter(name = "name") String name) {
      Intent LaunchIntent = mPackageManager.getLaunchIntentForPackage(name);
      mService.startActivity(LaunchIntent);
  }

  @Rpc(description = "Launch activity for result with intent")
  public Intent launchForResultWithIntent(
  @RpcParameter(name = "intent") Intent intent,
          @RpcParameter(name = "extras")@RpcOptional JSONObject extras)
          throws JSONException {
      if(extras != null) {
          mAndroidFacade.putExtrasFromJsonObject(extras, intent);
      }
      return mAndroidFacade.startActivityForResult(intent);
  }

  @Rpc(description = "Create intent given the class name")
  public Intent createIntentForClassName(
          @RpcParameter(name = "className") String className) {
      Intent intent = new Intent(Intent.ACTION_MAIN);
      String packageName = className.substring(0, className.lastIndexOf("."));
      intent.setClassName(packageName, className);
      return intent;
  }

  @Rpc(description = "Get UID of a package")
  public int getUidForPackage(
          @RpcParameter(name = "className") String className) {
      String packageName = className.substring(0, className.lastIndexOf("."));
      try {
          return mPackageManager.getApplicationInfo(packageName, 0).uid;
      } catch (PackageManager.NameNotFoundException e) {
          Log.e("Package not found", e);
      }
      return 0;
  }

  @Rpc(description = "Kill the specified app.")
  public Boolean appKill(@RpcParameter(name = "name") String name) {
      for (RunningAppProcessInfo info : mActivityManager.getRunningAppProcesses()) {
          if (info.processName.contains(name)) {
              Log.d("Killing " + info.processName);
              android.os.Process.killProcess(info.pid);
              android.os.Process.sendSignal(info.pid, android.os.Process.SIGNAL_KILL);
              mActivityManager.killBackgroundProcesses(info.processName);
              return true;
          }
      }
      return false;
  }

  @Rpc(description = "Returns a list of packages running activities or services.",
          returns = "List of packages running activities.")
  public List<String> getRunningPackages() {
    Set<String> runningPackages = new HashSet<String>();
    List<ActivityManager.RunningAppProcessInfo> appProcesses =
        mActivityManager.getRunningAppProcesses();
    for (ActivityManager.RunningAppProcessInfo info : appProcesses) {
      runningPackages.addAll(Arrays.asList(info.pkgList));
    }
    List<ActivityManager.RunningServiceInfo> serviceProcesses =
        mActivityManager.getRunningServices(Integer.MAX_VALUE);
    for (ActivityManager.RunningServiceInfo info : serviceProcesses) {
      runningPackages.add(info.service.getPackageName());
    }
    return new ArrayList<String>(runningPackages);
  }

  /**
   * Force stops a package. Equivalent to calling `am force-stop "package.name"` as root.
   * <p>
   * If you have access to adb, it is preferred to use the above command instead.
   *
   * @param packageName the name of the package to force stop
   */
  @Rpc(description = "Force stops a package. Equivalent to `adb shell am force-stop "
          + "\"package.name\"`. If possible, use that command instead.")
  public void forceStopPackage(
      @RpcParameter(name = "packageName", description = "name of package") String packageName) {
    mActivityManager.forceStopPackage(packageName);
  }

  @Override
  public void shutdown() {
  }
}