// Copyright 2016 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 com.google.devtools.build.android.AndroidResourceMerger.MergingException;
import com.google.devtools.build.android.xml.AttrXmlResourceValue;
import com.google.devtools.build.android.xml.Namespaces;
import com.google.devtools.build.android.xml.StyleableXmlResourceValue;
import com.google.errorprone.annotations.CheckReturnValue;
import java.io.Flushable;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.namespace.QName;

/** An interface for visiting android data for writing. */
public interface AndroidDataWritingVisitor extends Flushable {
  /** Copies the AndroidManifest to the destination directory. */
  Path copyManifest(Path sourceManifest) throws IOException;

  /**
   * Copies the source asset to the relative destination path.
   *
   * @param source The source file to copy.
   * @param relativeDestinationPath The relative destination path to write the asset to.
   * @throws IOException if there are errors during copying.
   */
  void copyAsset(Path source, String relativeDestinationPath) throws IOException;

  /**
   * Copies the source resource to the relative destination path.
   *
   * @param source The source file to copy.
   * @param relativeDestinationPath The relative destination path to write the resource to.
   * @throws MergingException for errors during copying.
   */
  void copyResource(Path source, String relativeDestinationPath) throws MergingException;

  /**
   * Adds the provided attribute to the root &lt;resources&gt; tag.
   *
   * @param fqn The fully qualified name of the attribute indicating both the name of the attribute
   *     and which qualified values.xml file to be associated with.
   * @param name The simple name of the attribute given as {@code [prefix:]name}.
   * @param value The value of the attribute.
   */
  void defineAttribute(FullyQualifiedName fqn, String name, String value);

  /**
   * Adds the namespaces associated with a {@link FullyQualifiedName}.
   *
   * <p>An xml namespace consists of a prefix and a uri. They are common declared on the root
   * &lt;resources&gt; tag of each resource. The namespaces collected here will be merged together,
   * with the last uri added taking precedence over the prefix key.
   */
  void defineNamespacesFor(FullyQualifiedName fqn, Namespaces namespaces);

  /**
   * Provides a fluent interface to generate an xml resource for the values directory.
   *
   * <p>Example usage: <code>
   *    writer.define(key)
   *        .derivedFrom(source)
   *        .startTag(tagName)
   *        .named(key)
   *        .closeTag()
   *        .write(stringValue)
   *        .endTag()
   *        .save();
   * </code>
   */
  // Check return value will ensure that the value is finished being written.
  @CheckReturnValue
  ValueResourceDefinitionMetadata define(FullyQualifiedName fqn);

  /** Represents the xml values resource meta data. */
  @CheckReturnValue
  interface ValueResourceDefinitionMetadata {
    ValuesResourceDefinition derivedFrom(DataSource source);
  }

  /** Fluent interface to define the xml value for a {@link FullyQualifiedName}. */
  @CheckReturnValue
  interface ValuesResourceDefinition {
    /** Starts an xml tag with a prefix and localName. */
    StartTag startTag(String prefix, String localName);

    /** Starts an xml tag with a localName. */
    StartTag startTag(String localName);

    /** Starts an xml tag with a QName. */
    StartTag startTag(QName name);

    /** Starts an xml tag with the name "item" */
    StartTag startItemTag();

    /**
     * Takes another values xml resource and writes it as a child tag here.
     *
     * <p>This allows xml elements from other {@link XmlResourceValue} to be moved in the stream.
     * Currently, this is only necessary for {@link StyleableXmlResourceValue} which can have {@link
     * AttrXmlResourceValue} defined as child elements (yet, they are merged and treated as
     * independent resources.)
     *
     * @param fqn The {@link FullyQualifiedName} of the {@link XmlResourceValue} to be adopted. This
     *     resource doesn't have to be defined for the adopt invocation, but it must exist when
     *     {@link AndroidDataWritingVisitor#flush()} is called.
     * @return The current definition.
     */
    ValuesResourceDefinition adopt(FullyQualifiedName fqn);

    /** Adds a string as xml characters to the definition. */
    ValuesResourceDefinition addCharactersOf(String characters);

    /** Ends the last {@link StartTag}. */
    ValuesResourceDefinition endTag();

    /** Saves and validates the xml resource definition. */
    void save();
  }

  /** Represents the start of opening tag of a resource xml. */
  @CheckReturnValue
  interface StartTag {
    /** Adds name="{@link FullyQualifiedName}#name()" attribute. */
    StartTag named(FullyQualifiedName key);
    /** Adds "name" attribute to the {@link StartTag}. */
    StartTag named(String key);
    /** Adds all the {@link Entry} as key="value" to the {@link StartTag}. */
    StartTag addAttributesFrom(Iterable<Map.Entry<String, String>> entries);
    /** Starts an attribute of prefix:name. */
    Attribute attribute(String prefix, String name);
    /** Starts an attribute of name. */
    Attribute attribute(String string);
    /** Indicates the next attribute will only be written if the value is not null. */
    Optional optional();
    /** Closes the {@link StartTag} as ">" */
    ValuesResourceDefinition closeTag();
    /** Closes the {@link StartTag} as "/>", indicating it is a unary xml element. */
    ValuesResourceDefinition closeUnaryTag();
  }

  /** Adjective for an optional attribute. */
  @CheckReturnValue
  interface Optional {
    /** Starts an attribute of prefix:name. */
    Attribute attribute(String prefix, String name);
    /** Starts an attribute of name. */
    Attribute attribute(String string);
  }

  /** Represents an xml attribute of a start tag. */
  @CheckReturnValue
  interface Attribute {
    /** Sets the attribute value. */
    StartTag setTo(String value);
    /** Sets the attribute value to {@linkplain FullyQualifiedName#name()}. */
    StartTag setTo(FullyQualifiedName fqn);
    /** Start the process of setting an attribute value from an iterable of strings. * */
    ValueJoiner setFrom(Iterable<String> values);
  }

  /** Represents the concatenation step of turning an {@link Iterable} into a string. */
  @CheckReturnValue
  interface ValueJoiner {
    StartTag joinedBy(String separator);
  }
}
