// Copyright 2014 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.lib.analysis.config;

import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.util.RegexFilter;
import com.google.devtools.build.lib.util.RegexFilter.RegexFilterConverter;
import com.google.devtools.common.options.Converter;
import com.google.devtools.common.options.OptionsParsingException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Models options that can be added to a command line when a label matches a given {@link
 * RegexFilter}.
 */
@AutoCodec
public class PerLabelOptions implements Serializable {
  public static final ObjectCodec<PerLabelOptions> CODEC = new PerLabelOptions_AutoCodec();

  /** The filter used to match labels */
  private final RegexFilter regexFilter;

  /** The list of options to add when the filter matches a label */
  private final List<String> optionsList;

  /**
   * Converts a String to a {@link PerLabelOptions} object. The syntax of the
   * string is {@code regex_filter@option_1,option_2,...,option_n}. Where
   * regex_filter stands for the String representation of a {@link RegexFilter},
   * and {@code option_1} to {@code option_n} stand for arbitrary command line
   * options. If an option contains a comma it has to be quoted with a
   * backslash. Options can contain @. Only the first @ is used to split the
   * string.
   */
  public static class PerLabelOptionsConverter implements Converter<PerLabelOptions> {

    @Override
    public PerLabelOptions convert(String input) throws OptionsParsingException {
      int atIndex = input.indexOf('@');
      RegexFilterConverter converter = new RegexFilter.RegexFilterConverter();
      if (atIndex < 0) {
        return new PerLabelOptions(converter.convert(input), ImmutableList.<String> of());
      } else {
        String filterPiece = input.substring(0, atIndex);
        String optionsPiece = input.substring(atIndex + 1);
        List<String> optionsList = new ArrayList<>();
        for (String option : optionsPiece.split("(?<!\\\\),")) { // Split on ',' but not on '\,'
          if (option != null && !option.trim().isEmpty()) {
            optionsList.add(option.replace("\\,", ","));
          }
        }
        return new PerLabelOptions(converter.convert(filterPiece), optionsList);
      }
    }

    @Override
    public String getTypeDescription() {
      return "a comma-separated list of regex expressions with prefix '-' specifying"
      + " excluded paths followed by an @ and a comma separated list of options";
    }
  }

  public PerLabelOptions(RegexFilter regexFilter, List<String> optionsList) {
    this.regexFilter = regexFilter;
    this.optionsList = optionsList;
  }

  /**
   * @return true if the given label is matched by the {@link RegexFilter}.
   */
  public boolean isIncluded(Label label) {
    return regexFilter.isIncluded(label.toString());
  }

  /**
   * @return true if the execution path (which includes the base name of the file)
   * of the given file is matched by the {@link RegexFilter}.
   */
  public boolean isIncluded(Artifact artifact) {
    return regexFilter.isIncluded(artifact.getExecPathString());
  }

  /**
   * Returns the list of options to add to a command line.
   */
  public List<String> getOptions() {
    return optionsList;
  }

  RegexFilter getRegexFilter() {
    return regexFilter;
  }

  @Override
  public String toString() {
    return regexFilter + " Options: " + optionsList;
  }
  
  @Override
  public boolean equals(Object other) {
    PerLabelOptions otherOptions = 
        other instanceof PerLabelOptions ? (PerLabelOptions) other : null;
    return this == other || (otherOptions != null && 
        this.regexFilter.equals(otherOptions.regexFilter) &&
        this.optionsList.equals(otherOptions.optionsList));
  }
  
  @Override
  public int hashCode() {
    return Objects.hash(regexFilter, optionsList);
  }
}
