/*
 * Copyright 2016 Google Inc.
 *
 * 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.googlejavaformat.java;

import com.google.common.collect.ImmutableList;
import com.sun.source.tree.AnnotatedTypeTree;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ArrayTypeTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.tree.JCTree;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;

/**
 * Utilities for working with array dimensions.
 *
 * <p>javac's parser does not preserve concrete syntax for mixed-notation arrays, so we have to
 * re-lex the input to extra it.
 *
 * <p>For example, {@code int [] a;} cannot be distinguished from {@code int [] a [];} in the AST.
 */
class DimensionHelpers {

  /** The array dimension specifiers (including any type annotations) associated with a type. */
  static class TypeWithDims {
    final Tree node;
    final ImmutableList<List<AnnotationTree>> dims;

    public TypeWithDims(Tree node, ImmutableList<List<AnnotationTree>> dims) {
      this.node = node;
      this.dims = dims;
    }
  }

  enum SortedDims {
    YES,
    NO
  }

  /** Returns a (possibly re-ordered) {@link TypeWithDims} for the given type. */
  static TypeWithDims extractDims(Tree node, SortedDims sorted) {
    Deque<List<AnnotationTree>> builder = new ArrayDeque<>();
    node = extractDims(builder, node);
    Iterable<List<AnnotationTree>> dims;
    if (sorted == SortedDims.YES) {
      dims = reorderBySourcePosition(builder);
    } else {
      dims = builder;
    }
    return new TypeWithDims(node, ImmutableList.copyOf(dims));
  }

  /**
   * Rotate the list of dimension specifiers until all dimensions with type annotations appear in
   * source order.
   *
   * <p>javac reorders dimension specifiers in method declarations with mixed-array notation, which
   * means that any type annotations don't appear in source order.
   *
   * <p>For example, the type of {@code int @A [] f() @B [] {}} is parsed as {@code @B [] @A []}.
   *
   * <p>This doesn't handle cases with un-annotated dimension specifiers, so the formatting logic
   * checks the token stream to figure out which side of the method name they appear on.
   */
  private static Iterable<List<AnnotationTree>> reorderBySourcePosition(
      Deque<List<AnnotationTree>> dims) {
    int lastAnnotation = -1;
    int lastPos = -1;
    int idx = 0;
    for (List<AnnotationTree> dim : dims) {
      if (!dim.isEmpty()) {
        int pos = ((JCTree) dim.get(0)).getStartPosition();
        if (pos < lastPos) {
          List<List<AnnotationTree>> list = new ArrayList<>(dims);
          Collections.rotate(list, -(lastAnnotation + 1));
          return list;
        }
        lastPos = pos;
        lastAnnotation = idx;
      }
      idx++;
    }
    return dims;
  }

  /**
   * Accumulates a flattened list of array dimensions specifiers with type annotations, and returns
   * the base type.
   *
   * <p>Given {@code int @A @B [][] @C []}, adds {@code [[@A, @B], [@C]]} to dims and returns {@code
   * int}.
   */
  private static Tree extractDims(Deque<List<AnnotationTree>> dims, Tree node) {
    switch (node.getKind()) {
      case ARRAY_TYPE:
        return extractDims(dims, ((ArrayTypeTree) node).getType());
      case ANNOTATED_TYPE:
        AnnotatedTypeTree annotatedTypeTree = (AnnotatedTypeTree) node;
        if (annotatedTypeTree.getUnderlyingType().getKind() != Tree.Kind.ARRAY_TYPE) {
          return node;
        }
        node = extractDims(dims, annotatedTypeTree.getUnderlyingType());
        dims.addFirst(ImmutableList.copyOf(annotatedTypeTree.getAnnotations()));
        return node;
      default:
        return node;
    }
  }
}