/*
 * Copyright 2008 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.common.css.compiler.ast;

import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import javax.annotation.Nullable;

/**
 * Represents a list of nodes. This is meant to represent a succession of
 * statements that logically group together as one node in the tree. Examples
 * include:
 * <ul>
 * <li>all the statements in a stylesheet, except for the ones that can only
 * appear on top ({@code charset}, {@code import}, etc.)
 * <li>all the declarations inside a ruleset, a font face or a page rule
 * <li>all the nodes that form a chain of if-elseif-else rules
 * <li>the block that belongs to some of the at rules, such as {@code media}
 * </ul>
 *
 * @param <T> the list is restricted to nodes of this type
 */
public abstract class CssNodesListNode<T extends CssNode> extends CssNode {
  protected List<T> children = Lists.newArrayList();
  private final boolean isEnclosedWithBraces;

  /**
   * Constructor of a list of nodes alike.
   *
   * @param isEnclosedWithBraces
   */
  public CssNodesListNode(boolean isEnclosedWithBraces) {
    this(isEnclosedWithBraces, null);
  }

  /**
   * Constructor of a list of nodes alike.
   *
   * @param isEnclosedWithBraces
   * @param comments
   */
  public CssNodesListNode(boolean isEnclosedWithBraces,
                   @Nullable List<CssCommentNode> comments) {
    super(null, comments, null);
    this.isEnclosedWithBraces = isEnclosedWithBraces;
  }

  /**
   * Constructor of a list of nodes alike.
   *
   * @param isEnclosedWithBraces
   * @param comments
   * @param childrenList list of children
   */
  public CssNodesListNode(boolean isEnclosedWithBraces,
                          List<T> childrenList,
                          @Nullable List<CssCommentNode> comments) {
    super(null, comments, null);
    for (T child : childrenList) {
      @SuppressWarnings("unchecked")
      T childCopy = (T) child.deepCopy();
      addChildToBack(childCopy);
    }

    this.isEnclosedWithBraces = isEnclosedWithBraces;
  }

  /**
   * Copy constructor.
   *
   * @param node
   */
  public CssNodesListNode(CssNodesListNode<? extends CssNode> node) {
    super(
        node.getParent(),
        node.getComments(),
        node.getSourceCodeLocation());
    this.isEnclosedWithBraces = node.isEnclosedWithBraces;

    for (CssNode child : node.childIterable()) {
      @SuppressWarnings("unchecked")
      T childCopy = (T) child.deepCopy();
      addChildToBack(childCopy);
    }
  }

  public List<T> getChildren() {
    return Collections.unmodifiableList(children);
  }

  public Iterator<T> getChildIterator() {
    return children.iterator();
  }

  public Iterable<T> childIterable() {
    return Iterables.unmodifiableIterable(children);
  }

  void setChildren(List<T> children) {
    Preconditions.checkArgument(!children.contains(null));
    removeAsParentOfNodes(this.children);
    this.children = copyToList(children);
    becomeParentForNodes(this.children);
  }

  T removeChildAt(int index) {
    Preconditions.checkState(index >= 0 && index < children.size());
    T child = children.get(index);
    removeAsParentOfNode(child);
    children.remove(index);
    return child;
  }

  // TODO(dgajda): Make it package private once we can walk the tree backwards
  //     and ReplaceConstantReferences won't need to use this method directly.
  public void replaceChildAt(int index, List<? extends T> newChildren) {
    Preconditions.checkState(index >= 0 && index < children.size());
    Preconditions.checkArgument(!newChildren.contains(null));
    removeChildAt(index);
    children.addAll(index, newChildren);
    becomeParentForNodes(newChildren);
  }

  public T getChildAt(int index) {
    Preconditions.checkState(index >= 0 && index < children.size());
    return children.get(index);
  }

  public int numChildren() {
    return children.size();
  }

  T removeLastChild() {
    return removeChildAt(children.size() - 1);
  }

  public T getLastChild() {
    return children.get(children.size() - 1);
  }

  public void addChildToBack(T child) {
    Preconditions.checkNotNull(child);
    this.children.add(child);
    becomeParentForNode(child);
  }

  public boolean isEmpty() {
    return children.isEmpty();
  }

  public boolean isEnclosedWithBraces() {
    return isEnclosedWithBraces;
  }

  /**
   * For debugging only.
   */
  @Override
  public String toString() {
    StringBuffer output = new StringBuffer();
    if (!getComments().isEmpty()) {
      output.append("[");
      output.append(getComments().toString());
      output.append(children.toString());
      output.append("]");
    } else {
      output.append(children.toString());
    }

    return output.toString();
  }
}
