// 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.eval;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.ObjectArrays;
import java.util.AbstractCollection;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Iterator;
import net.starlark.java.annot.StarlarkBuiltin;

/** A Tuple is an immutable finite sequence of values. */
@StarlarkBuiltin(
    name = "tuple",
    category = "core",
    doc =
        "The built-in tuple type. Example tuple expressions:<br>"
            + "<pre class=language-python>x = (1, 2, 3)</pre>"
            + "Accessing elements is possible using indexing (starts from <code>0</code>):<br>"
            + "<pre class=language-python>e = x[1]   # e == 2</pre>"
            + "Lists support the <code>+</code> operator to concatenate two tuples. Example:<br>"
            + "<pre class=language-python>x = (1, 2) + (3, 4)   # x == (1, 2, 3, 4)\n"
            + "x = (\"a\", \"b\")\n"
            + "x += (\"c\",)            # x == (\"a\", \"b\", \"c\")</pre>"
            + "Similar to lists, tuples support slice operations:"
            + "<pre class=language-python>('a', 'b', 'c', 'd')[1:3]   # ('b', 'c')\n"
            + "('a', 'b', 'c', 'd')[::2]  # ('a', 'c')\n"
            + "('a', 'b', 'c', 'd')[3:0:-1]  # ('d', 'c', 'b')</pre>"
            + "Tuples are immutable, therefore <code>x[1] = \"a\"</code> is not supported.")
public final class Tuple extends AbstractList<Object>
    implements Sequence<Object>, Comparable<Tuple> {

  private final Object[] elems;

  private Tuple(Object[] elems) {
    this.elems = elems;
  }

  // The shared (sole) empty tuple.
  private static final Tuple EMPTY = new Tuple(new Object[0]);

  /** Returns the empty tuple. */
  public static Tuple empty() {
    return EMPTY;
  }

  /** Returns a Tuple that wraps the specified array, which must not be subsequently modified. */
  static Tuple wrap(Object[] array) {
    return array.length == 0 ? empty() : new Tuple(array);
  }

  /** Returns a tuple containing the given elements. */
  public static Tuple copyOf(Iterable<?> seq) {
    if (seq instanceof Tuple) {
      return (Tuple) seq;
    }
    return wrap(Iterables.toArray(seq, Object.class));
  }

  /** Returns a tuple containing the given elements. */
  public static Tuple of(Object... elems) {
    if (elems.length == 0) {
      return empty();
    }
    return new Tuple(Arrays.copyOf(elems, elems.length));
  }

  /** Returns a two-element tuple. */
  public static Tuple pair(Object a, Object b) {
    // Equivalent to of(a, b) but avoids variadic array allocation.
    return wrap(new Object[] {a, b});
  }

  /** Returns a three-element tuple. */
  public static Tuple triple(Object a, Object b, Object c) {
    // Equivalent to of(a, b, c) but avoids variadic array allocation.
    return wrap(new Object[] {a, b, c});
  }

  /** Returns a tuple that is the concatenation of two tuples. */
  public static Tuple concat(Tuple x, Tuple y) {
    if (x.isEmpty()) {
      return y;
    } else if (y.isEmpty()) {
      return x;
    } else {
      return wrap(ObjectArrays.concat(x.elems, y.elems, Object.class));
    }
  }

  @Override
  public boolean isImmutable() {
    for (Object x : elems) {
      if (!Starlark.isImmutable(x)) {
        return false;
      }
    }
    return true;
  }

  @Override
  public void checkHashable() throws EvalException {
    for (Object x : elems) {
      Starlark.checkHashable(x);
    }
  }

  @Override
  public int hashCode() {
    return 9857 + 8167 * Arrays.hashCode(elems);
  }

  @Override
  public boolean equals(Object that) {
    // This slightly violates the java.util.List equivalence contract
    // because it considers the class, not just the elements.
    return this == that
        || (that instanceof Tuple && Arrays.equals(this.elems, ((Tuple) that).elems));
  }

  @Override
  public int compareTo(Tuple that) {
    return Sequence.compare(this, that);
  }

  @Override
  public Object get(int i) {
    return elems[i];
  }

  @Override
  public int size() {
    return elems.length;
  }

  @Override
  public Tuple subList(int from, int to) {
    return wrap(Arrays.copyOfRange(elems, from, to));
  }

  /** Returns a new array of class Object[] containing the tuple elements. */
  @Override
  public Object[] toArray() {
    return elems.length != 0 ? Arrays.copyOf(elems, elems.length, Object[].class) : elems;
  }

  @SuppressWarnings("unchecked")
  @Override
  public <T> T[] toArray(T[] a) {
    if (a.length < elems.length) {
      return (T[]) Arrays.copyOf(elems, elems.length, a.getClass());
    } else {
      System.arraycopy(elems, 0, a, 0, elems.length);
      Arrays.fill(a, elems.length, a.length, null);
      return a;
    }
  }

  @Override
  public void repr(Printer printer) {
    printer.append('(');
    String sep = "";
    for (Object elem : elems) {
      printer.append(sep);
      sep = ", ";
      printer.repr(elem);
    }
    if (elems.length == 1) {
      printer.append(',');
    }
    printer.append(')');
  }

  // TODO(adonovan): StarlarkValue has 3 String methods yet still we need this fourth. Why?
  @Override
  public String toString() {
    return Starlark.repr(this);
  }

  @Override
  public ImmutableList<Object> getImmutableList() {
    // Share the array with this (immutable) Tuple.
    return wrapImmutable(elems);
  }

  /**
   * Returns a new ImmutableList<T> backed by {@code array}, which must not be subsequently
   * modified.
   */
  // TODO(adonovan): move this somewhere more appropriate.
  static <T> ImmutableList<T> wrapImmutable(Object[] array) {
    // Construct an ImmutableList that shares the array.
    // ImmutableList relies on the implementation of Collection.toArray
    // not subsequently modifying the returned array.
    return ImmutableList.copyOf(
        new AbstractCollection<T>() {
          @Override
          public Object[] toArray() {
            return array;
          }

          @Override
          public int size() {
            return array.length;
          }

          @Override
          public Iterator<T> iterator() {
            throw new UnsupportedOperationException();
          }
        });
  }

  @Override
  public Tuple getSlice(Mutability mu, int start, int stop, int step) throws EvalException {
    RangeList indices = new RangeList(start, stop, step);
    int n = indices.size();
    if (step == 1) { // common case
      return subList(indices.at(0), indices.at(n));
    }
    Object[] res = new Object[n];
    for (int i = 0; i < n; ++i) {
      res[i] = elems[indices.at(i)];
    }
    return wrap(res);
  }

  /** Returns a Tuple containing n consecutive repeats of this tuple. */
  Tuple repeat(StarlarkInt n) throws EvalException {
    if (n.signum() <= 0 || isEmpty()) {
      return empty();
    }

    int ni = n.toInt("repeat");
    long sz = (long) ni * elems.length;
    if (sz > StarlarkList.MAX_ALLOC) {
      throw Starlark.errorf("excessive repeat (%d * %d elements)", elems.length, ni);
    }
    Object[] res = new Object[(int) sz];
    for (int i = 0; i < ni; i++) {
      System.arraycopy(elems, 0, res, i * elems.length, elems.length);
    }
    return wrap(res);
  }
}
