/*
 * 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.android.tools.build.bundletool.commands;

import static com.android.tools.build.bundletool.commands.CommandUtils.ANDROID_SERIAL_VARIABLE;
import static com.android.tools.build.bundletool.model.utils.SdkToolsLocator.ANDROID_HOME_VARIABLE;
import static com.android.tools.build.bundletool.model.utils.SdkToolsLocator.SYSTEM_PATH_VARIABLE;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.android.bundle.Devices.DeviceSpec;
import com.android.tools.build.bundletool.commands.CommandHelp.CommandDescription;
import com.android.tools.build.bundletool.commands.CommandHelp.FlagDescription;
import com.android.tools.build.bundletool.device.AdbServer;
import com.android.tools.build.bundletool.device.DeviceAnalyzer;
import com.android.tools.build.bundletool.flags.Flag;
import com.android.tools.build.bundletool.flags.ParsedFlags;
import com.android.tools.build.bundletool.model.exceptions.InvalidCommandException;
import com.android.tools.build.bundletool.model.utils.DefaultSystemEnvironmentProvider;
import com.android.tools.build.bundletool.model.utils.SystemEnvironmentProvider;
import com.android.tools.build.bundletool.model.utils.files.FilePreconditions;
import com.google.auto.value.AutoValue;
import com.google.common.io.MoreFiles;
import com.google.protobuf.util.JsonFormat;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.logging.Logger;

/** Command to fetch the configuration of the connected device. */
@AutoValue
public abstract class GetDeviceSpecCommand {

  private static final Logger logger = Logger.getLogger(GetDeviceSpecCommand.class.getName());

  public static final String COMMAND_NAME = "get-device-spec";

  private static final Flag<Path> ADB_PATH_FLAG = Flag.path("adb");
  private static final Flag<String> DEVICE_ID_FLAG = Flag.string("device-id");
  private static final Flag<Path> OUTPUT_FLAG = Flag.path("output");
  private static final Flag<Boolean> OVERWRITE_OUTPUT_FLAG = Flag.booleanFlag("overwrite");

  private static final SystemEnvironmentProvider DEFAULT_PROVIDER =
      new DefaultSystemEnvironmentProvider();

  private static final String JSON_EXTENSION = "json";

  public abstract Path getAdbPath();

  public abstract Optional<String> getDeviceId();

  public abstract Path getOutputPath();

  public abstract boolean getOverwriteOutput();

  abstract AdbServer getAdbServer();

  public static Builder builder() {
    return new AutoValue_GetDeviceSpecCommand.Builder().setOverwriteOutput(false);
  }

  /** Builder for the {@link GetDeviceSpecCommand}. */
  @AutoValue.Builder
  public abstract static class Builder {
    public abstract Builder setOutputPath(Path outputPath);

    /**
     * Sets whether to overwrite the contents of the output file.
     *
     * <p>The default is {@code false}. If set to {@code false} and the output file is present,
     * exception is thrown.
     */
    public abstract Builder setOverwriteOutput(boolean overwriteOutput);

    public abstract Builder setAdbPath(Path adbPath);

    public abstract Builder setDeviceId(String deviceId);

    /** The caller is responsible for the lifecycle of the {@link AdbServer}. */
    public abstract Builder setAdbServer(AdbServer adbServer);

    abstract GetDeviceSpecCommand autoBuild();

    public GetDeviceSpecCommand build() {
      GetDeviceSpecCommand command = autoBuild();

      if (!JSON_EXTENSION.equals(MoreFiles.getFileExtension(command.getOutputPath()))) {
        throw InvalidCommandException.builder()
            .withInternalMessage(
                "Flag --output should be the path where to generate the device spec file. "
                    + "Its extension must be '.json'.")
            .build();
      }

      return command;
    }
  }

  public static GetDeviceSpecCommand fromFlags(ParsedFlags flags, AdbServer adbServer) {
    return fromFlags(flags, DEFAULT_PROVIDER, adbServer);
  }

  public static GetDeviceSpecCommand fromFlags(
      ParsedFlags flags, SystemEnvironmentProvider systemEnvironmentProvider, AdbServer adbServer) {
    GetDeviceSpecCommand.Builder builder =
        builder().setAdbServer(adbServer).setOutputPath(OUTPUT_FLAG.getRequiredValue(flags));

    Optional<String> deviceSerialName =
        CommandUtils.getDeviceSerialName(flags, DEVICE_ID_FLAG, systemEnvironmentProvider);
    deviceSerialName.ifPresent(builder::setDeviceId);

    Path adbPath = CommandUtils.getAdbPath(flags, ADB_PATH_FLAG, systemEnvironmentProvider);
    builder.setAdbPath(adbPath);

    OVERWRITE_OUTPUT_FLAG.getValue(flags).ifPresent(builder::setOverwriteOutput);
    flags.checkNoUnknownFlags();

    return builder.build();
  }

  public DeviceSpec execute() {
    if (!getOverwriteOutput()) {
      FilePreconditions.checkFileDoesNotExist(getOutputPath());
    }

    Path pathToAdb = getAdbPath();
    FilePreconditions.checkFileExistsAndExecutable(pathToAdb);

    AdbServer adb = getAdbServer();
    adb.init(getAdbPath());
    DeviceSpec deviceSpec = new DeviceAnalyzer(adb).getDeviceSpec(getDeviceId());
    writeDeviceSpecToFile(deviceSpec, getOutputPath());
    return deviceSpec;
  }

  private void writeDeviceSpecToFile(DeviceSpec deviceSpec, Path outputFile) {
    try {
      if (getOverwriteOutput()) {
        Files.deleteIfExists(getOutputPath());
      }

      Path outputDirectory = getOutputPath().getParent();
      if (outputDirectory != null && !Files.exists(outputDirectory)) {
        logger.info("Output directory '" + outputDirectory + "' does not exist, creating it.");
        Files.createDirectories(outputDirectory);
      }

      Files.write(outputFile, JsonFormat.printer().print(deviceSpec).getBytes(UTF_8));
    } catch (IOException e) {
      throw new UncheckedIOException(
          String.format("Error while writing the output file '%s'.", outputFile), e);
    }
  }

  public static CommandHelp help() {
    return CommandHelp.builder()
        .setCommandName(COMMAND_NAME)
        .setCommandDescription(
            CommandDescription.builder()
                .setShortDescription(
                    "Writes out a JSON file containing the device specifications (i.e. features "
                        + "and properties) of the connected Android device.")
                .build())
        .addFlag(
            FlagDescription.builder()
                .setFlagName(OUTPUT_FLAG.getName())
                .setExampleValue("device-spec.json")
                .setDescription(
                    "Path to the output device spec file. Must have the .json extension.")
                .build())
        .addFlag(
            FlagDescription.builder()
                .setFlagName(ADB_PATH_FLAG.getName())
                .setExampleValue("path/to/adb")
                .setOptional(true)
                .setDescription(
                    "Path to the adb utility. If absent, an attempt will be made to locate it if "
                        + "the %s or %s environment variable is set.",
                    ANDROID_HOME_VARIABLE, SYSTEM_PATH_VARIABLE)
                .build())
        .addFlag(
            FlagDescription.builder()
                .setFlagName(DEVICE_ID_FLAG.getName())
                .setExampleValue("device-serial-name")
                .setOptional(true)
                .setDescription(
                    "Device serial name. If absent, this uses the %s environment variable. Either "
                        + "this flag or the environment variable is required when more than one "
                        + "device or emulator is connected.",
                    ANDROID_SERIAL_VARIABLE)
                .build())
        .addFlag(
            FlagDescription.builder()
                .setFlagName(OVERWRITE_OUTPUT_FLAG.getName())
                .setOptional(true)
                .setDescription("If set, any previous existing output will be overwritten.")
                .build())
        .build();
  }
}