/*
 * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
 * (the "License"). You may not use this work except in compliance with the License, which is
 * available at www.apache.org/licenses/LICENSE-2.0
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied, as more fully set forth in the License.
 *
 * See the NOTICE file distributed with this work for information regarding copyright ownership.
 */

package alluxio.collections;

import alluxio.annotation.SuppressFBWarnings;

import com.google.common.base.Preconditions;

import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Consumer;
import javax.annotation.concurrent.NotThreadSafe;

/**
 * This class provides an unmodifiable List proxy for an underlying array.
 * In other words, this helps you use the array like a List, but not being able to modify it.
 *
 * Create an instance with the constructor and get the underlying array with {@link #toArray()}.
 *
 * The difference to {@code Collections.unmodifiableList(Arrays.asList(array))} is,
 * in some Java implementations like adoptjdk, Arrays.asList() copies the array with
 * {@code new ArrayList<>(array)} so it is not performant.
 * Also this class makes it possible to recover the original array from the List,
 * {@code list.toArray()} returns the underlying array instead of a copy.
 * Use the underlying array for modification if needed.
 *
 * Use this when you just want to return an unmodifiable view of the array.
 *
 * There is no extra thread safety guarantee on this List.
 * When the underlying array changes, the view will change too,
 * and there is no extra thread safety.
 *
 * @param <T> element type of the underlying array
 */
@NotThreadSafe
public class UnmodifiableArrayList<T> implements List<T> {
  T[] mElements;

  /**
   * @param elements the underlying array
   */
  @SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"})
  public UnmodifiableArrayList(T[] elements) {
    // Explicitly disable null array because that makes no sense for this wrapper
    Preconditions.checkNotNull(elements);
    mElements = elements;
  }

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

  @Override
  public boolean isEmpty() {
    return mElements.length == 0;
  }

  @Override
  public boolean contains(Object o) {
    return indexOf(o) >= 0;
  }

  @Override
  public Iterator<T> iterator() {
    return new Itr();
  }

  @Override
  public Object[] toArray() {
    return mElements;
  }

  @Override
  public <T1> T1[] toArray(T1[] a) {
    // An exception will be thrown if the target type is not convertible
    return (T1[]) mElements;
  }

  @Override
  public boolean add(T t) {
    throw new UnsupportedOperationException(
        "modification is not supported in UnmodifiableArrayList");
  }

  @Override
  public void add(int index, T element) {
    throw new UnsupportedOperationException(
        "modification is not supported in UnmodifiableArrayList");
  }

  @Override
  public boolean remove(Object o) {
    throw new UnsupportedOperationException(
        "modification is not supported in UnmodifiableArrayList");
  }

  @Override
  public T remove(int index) {
    throw new UnsupportedOperationException(
        "modification is not supported in UnmodifiableArrayList");
  }

  @Override
  public boolean containsAll(Collection<?> c) {
    for (Object element : c) {
      if (!contains(element)) {
        return false;
      }
    }
    return true;
  }

  @Override
  public boolean addAll(Collection<? extends T> c) {
    throw new UnsupportedOperationException(
        "modification is not supported in UnmodifiableArrayList");
  }

  @Override
  public boolean addAll(int index, Collection<? extends T> c) {
    throw new UnsupportedOperationException(
        "modification is not supported in UnmodifiableArrayList");
  }

  @Override
  public boolean removeAll(Collection<?> c) {
    throw new UnsupportedOperationException(
        "modification is not supported in UnmodifiableArrayList");
  }

  @Override
  public boolean retainAll(Collection<?> c) {
    throw new UnsupportedOperationException(
        "modification is not supported in UnmodifiableArrayList");
  }

  @Override
  public void clear() {
    throw new UnsupportedOperationException(
        "modification is not supported in UnmodifiableArrayList");
  }

  @Override
  public T get(int index) {
    return mElements[index];
  }

  @Override
  public T set(int index, T element) {
    throw new UnsupportedOperationException(
        "modification is not supported in UnmodifiableArrayList");
  }

  @Override
  public int indexOf(Object o) {
    if (o == null) {
      for (int i = 0; i < mElements.length; i++) {
        if (mElements[i] == null) {
          return i;
        }
      }
    } else {
      for (int i = 0; i < mElements.length; i++) {
        if (o.equals(mElements[i])) {
          return i;
        }
      }
    }
    return -1;
  }

  @Override
  public int lastIndexOf(Object o) {
    if (o == null) {
      for (int i = mElements.length - 1; i >= 0; i--) {
        if (mElements[i] == null) {
          return i;
        }
      }
    } else {
      for (int i = mElements.length - 1; i >= 0; i--) {
        if (o.equals(mElements[i])) {
          return i;
        }
      }
    }
    return -1;
  }

  @Override
  public ListIterator<T> listIterator() {
    return new ListItr(0);
  }

  @Override
  public ListIterator<T> listIterator(int index) {
    if (index < 0 || index > mElements.length) {
      throw new IndexOutOfBoundsException("Index: " + index);
    }
    return new ListItr(index);
  }

  @Override
  public List<T> subList(int fromIndex, int toIndex) {
    throw new UnsupportedOperationException(
        "subList is not supported in UnmodifiableArrayList, use the underlying array instead");
  }

  private class Itr implements Iterator<T> {
    int mCursor;       // index of next element to return
    int mLastRet = -1; // index of last element returned; -1 if no such

    Itr() {}

    @Override
    public boolean hasNext() {
      return mCursor != mElements.length;
    }

    @Override
    public T next() {
      int i = mCursor;
      if (i >= mElements.length) {
        throw new NoSuchElementException();
      }
      mCursor = i + 1;
      mLastRet = i;
      return mElements[mLastRet];
    }

    @Override
    public void remove() {
      throw new UnsupportedOperationException(
          "modification is not supported in UnmodifiableArrayList");
    }

    @Override
    public void forEachRemaining(Consumer<? super T> consumer) {
      Objects.requireNonNull(consumer);
      final int size = mElements.length;
      int i = mCursor;
      if (i >= size) {
        return;
      }
      while (i != size) {
        consumer.accept(mElements[i++]);
      }
      // update once at end of iteration to reduce heap write traffic
      mCursor = i;
      mLastRet = i - 1;
    }
  }

  private class ListItr extends Itr implements ListIterator<T> {
    ListItr(int index) {
      super();
      mCursor = index;
    }

    @Override
    public boolean hasPrevious() {
      return mCursor != 0;
    }

    @Override
    public int nextIndex() {
      return mCursor;
    }

    @Override
    public int previousIndex() {
      return mCursor - 1;
    }

    @Override
    public T previous() {
      int i = mCursor - 1;
      if (i < 0) {
        throw new NoSuchElementException();
      }
      if (i >= mElements.length) {
        throw new ConcurrentModificationException();
      }
      mCursor = i;
      mLastRet = i;
      return mElements[mLastRet];
    }

    @Override
    public void set(T e) {
      throw new UnsupportedOperationException(
          "modification is not supported in UnmodifiableArrayList");
    }

    @Override
    public void add(T e) {
      throw new UnsupportedOperationException(
          "modification is not supported in UnmodifiableArrayList");
    }
  }
}
