// 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 net.starlark.java.syntax;

import com.google.common.base.Preconditions;
import javax.annotation.concurrent.Immutable;

/**
 * A Location denotes a position within a Starlark file.
 *
 * <p>A location is a triple {@code (file, line, column)}, where {@code file} is the apparent name
 * of the file, {@code line} is the optional 1-based line number, and {@code column} is the optional
 * 1-based column number measured in UTF-16 code units. If the column is zero it is not displayed.
 * If the line number is also zero, it too is not displayed; in this case, the location denotes the
 * file as a whole.
 */
@Immutable
public final class Location implements Comparable<Location> {

  private final String file;
  private final int line;
  private final int column;

  public Location(String file, int line, int column) {
    this.file = Preconditions.checkNotNull(file);
    this.line = line;
    this.column = column;
  }

  /** Returns the name of the file containing this location. */
  public String file() {
    return file;
  }

  /** Returns the line number of this location. */
  public int line() {
    return line;
  }

  /** Returns the column number of this location. */
  public int column() {
    return column;
  }

  /**
   * Returns a Location for the given file, line and column. If {@code column} is non-zero, {@code
   * line} too must be non-zero.
   */
  public static Location fromFileLineColumn(String file, int line, int column) {
    Preconditions.checkArgument(line != 0 || column == 0, "non-zero column but no line number");
    return new Location(file, line, column);
  }

  /** Returns a Location for the file as a whole. */
  public static Location fromFile(String file) {
    return new Location(file, 0, 0);
  }

  /**
   * Formats the location as {@code "file:line:col"}. If the column is zero, it is omitted. If the
   * line is also zero, it too is omitted.
   */
  @Override
  public String toString() {
    StringBuilder buf = new StringBuilder();
    buf.append(file);
    if (line != 0) {
      buf.append(':').append(line);
      if (column != 0) {
        buf.append(':').append(column);
      }
    }
    return buf.toString();
  }

  /** Returns a three-valued lexicographical comparison of two Locations. */
  @Override
  public int compareTo(Location that) {
    int cmp = this.file().compareTo(that.file());
    if (cmp != 0) {
      return cmp;
    }
    return Long.compare(
        ((long) this.line << 32) | this.column, ((long) that.line << 32) | that.column);
  }

  @Override
  public int hashCode() {
    return 97 * file.hashCode() + 37 * line + column;
  }

  @Override
  public boolean equals(Object that) {
    return this == that
        || (that instanceof Location
            && this.file.equals(((Location) that).file)
            && this.line == ((Location) that).line
            && this.column == ((Location) that).column);
  }

  /** A location for built-in functions. */
  public static final Location BUILTIN = fromFile("<builtin>");
}
