// Copyright 2018 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.build.android;

import static com.google.common.base.Predicates.not;

import com.android.SdkConstants;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Objects;
import java.util.Optional;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

/** Represents an AndroidManifest in the context of resource link and compilation. */
public class AndroidManifest {

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

  private static final QName PACKAGE_NAME = QName.valueOf("package");
  private static final QName MANIFEST = QName.valueOf("manifest");
  private static final QName USES_SDK = QName.valueOf("uses-sdk");
  private static final QName MIN_SDK =
      new QName(SdkConstants.ANDROID_URI, "minSdkVersion", "android");
  private final String packageName;
  private final String minSdk;

  private AndroidManifest(String packageName, String minSdk) {
    this.packageName = packageName;
    this.minSdk = minSdk;
  }

  /** Parses the manifest from the path. */
  public static AndroidManifest parseFrom(Path manifest) {
    try (InputStream input = Files.newInputStream(manifest)) {
      final XMLEventReader xmlEventReader =
          XmlResourceValues.getXmlInputFactory().createXMLEventReader(input);

      String packageName = "";
      String minSdk = "";
      while (xmlEventReader.hasNext()) {
        final XMLEvent xmlEvent = xmlEventReader.nextEvent();
        if (xmlEvent.isStartElement()) {
          final StartElement element = xmlEvent.asStartElement();
          if (MANIFEST.equals(element.getName())) {
            packageName = getAttributeValue(xmlEvent, PACKAGE_NAME).orElse(packageName);
          }
          if (USES_SDK.equals(element.getName())) {
            minSdk = getAttributeValue(xmlEvent, MIN_SDK).orElse(minSdk);
          }
        }
      }

      if (minSdk.isEmpty()) {
        logger.warning(
            String.format(
                "\n\u001B[31mCONFIGURATION:\u001B[0m" + " %s has no minSdkVersion. Using 1.",
                manifest));
      }

      return new AndroidManifest(packageName, minSdk.isEmpty() ? "1" : minSdk);
    } catch (IOException | XMLStreamException e) {
      throw new AndroidManifestProcessor.ManifestProcessingException(e);
    }
  }

  private static Optional<String> getAttributeValue(XMLEvent xmlEvent, QName attributeName) {
    return Optional.ofNullable(xmlEvent.asStartElement().getAttributeByName(attributeName))
        .map(Attribute::getValue)
        .filter(not(String::isEmpty));
  }

  public static AndroidManifest asEmpty() {
    return new AndroidManifest("", "1");
  }

  public static AndroidManifest of(String packageName, String minSdk) {
    return new AndroidManifest(packageName, minSdk);
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (!(o instanceof AndroidManifest)) {
      return false;
    }
    AndroidManifest that = (AndroidManifest) o;
    return Objects.equals(packageName, that.packageName) && Objects.equals(minSdk, that.minSdk);
  }

  @Override
  public String toString() {
    return MoreObjects.toStringHelper(this)
        .add("packageName", packageName)
        .add("minSdk", minSdk)
        .toString();
  }

  @Override
  public int hashCode() {
    return Objects.hash(packageName, minSdk);
  }

  /** Creates a dummy manifest for processing without any manifest template variables. */
  public Path writeDummyManifestForAapt(Path workingDirectory, String packageForR) {
    try {
      return Files.write(
          Files.createDirectories(workingDirectory).resolve("AndroidManifest.xml"),
          ImmutableList.of(
              "<?xml version='1.0' encoding='utf-8'?>",
              "<manifest xmlns:android='http://schemas.android.com/apk/res/android'",
              String.format(
                  "package='%s'>", Strings.isNullOrEmpty(packageForR) ? packageName : packageForR),
              "<application/>",
              Strings.isNullOrEmpty(minSdk)
                  ? ""
                  : String.format("<uses-sdk android:minSdkVersion='%s'/>", minSdk),
              "</manifest>"),
          StandardOpenOption.CREATE_NEW);
    } catch (IOException e) {
      throw new AndroidManifestProcessor.ManifestProcessingException(e);
    }
  }
}
