/*
 * 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.Lists;
import com.google.common.collect.UnmodifiableIterator;
import com.google.common.css.Locatable;
import com.google.common.css.SourceCodeLocation;

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

import javax.annotation.Nullable;

/**
 * A node of the abstract syntax tree.
 *
 * TODO(oana): Declare this class as:
 * public abstract class CssNode<T extends CssNode> {...}
 *
 */
public abstract class CssNode implements Locatable {
  /** The parent of this node. */
  private CssNode parent;
  /** The source code corresponding to this node. */
  private SourceCodeLocation sourceCodeLocation;
  /** List of comments/annotations. */
  private List<CssCommentNode> comments;
  /** Annotation of a node to show whether it should be flipped. */
  private boolean shouldBeFlipped = true;

  /**
   * Constructor of a node.
   */
  public CssNode() {
    this(null, null);
  }

  /**
   * Constructor of a node.
   *
   * @param sourceCodeLocation
   */
  public CssNode(@Nullable SourceCodeLocation sourceCodeLocation) {
    this(null, sourceCodeLocation);
  }

  /**
   * Constructor of a node.
   *
   * @param parent
   */
  public CssNode(@Nullable CssNode parent) {
    this(parent, null);
  }

  /**
   * Constructor of a node.
   *
   * @param parent
   * @param sourceCodeLocation
   */
  public CssNode(@Nullable CssNode parent,
          @Nullable SourceCodeLocation sourceCodeLocation) {
    this(parent, null, sourceCodeLocation);
  }

  /**
   * Constructor of a node.
   *
   * @param parent
   * @param comments
   * @param sourceCodeLocation
   */
  public CssNode(@Nullable CssNode parent,
          @Nullable List<CssCommentNode> comments,
          @Nullable SourceCodeLocation sourceCodeLocation) {
    this.parent = parent;
    this.sourceCodeLocation = sourceCodeLocation;
    if (comments == null) {
      this.comments = Lists.newArrayListWithExpectedSize(0);
    } else {
      this.comments = Lists.newArrayList(comments);
    }
    becomeParentForNodes(this.comments);
  }

  // TODO(oana): Declare this method as public abstract T deepCopy() to ensure
  // that the return type is always correct.
  public abstract CssNode deepCopy();

  public CssNode getParent() {
    return parent;
  }

  void setParent(CssNode parent) {
    this.parent = parent;
  }

  @Override
  public SourceCodeLocation getSourceCodeLocation() {
    return sourceCodeLocation;
  }

  public void setSourceCodeLocation(
      @Nullable SourceCodeLocation sourceCodeLocation) {
    this.sourceCodeLocation = sourceCodeLocation;
  }

  /**
   * Removes the relation between this node and its parent.
   */
  void removeParent() {
    this.parent = null;
  }

  /**
   * Removes the parent-child relation between this node and a child.
   *
   * @param child
   */
  void removeAsParentOfNode(CssNode child) {
    if (child == null) {
      return;
    }
    Preconditions.checkArgument(child.getParent() == this);
    child.removeParent();
  }

  /**
   * Removes the parent-children relations between this node and a list of
   * children.
   *
   * @param children
   */
  void removeAsParentOfNodes(List<? extends CssNode> children) {
    if (children == null) {
      return;
    }
    for (CssNode child : children) {
      removeAsParentOfNode(child);
    }
  }

  /**
   * Makes this node the parent of a child.
   *
   * @param child
   */
  final void becomeParentForNode(@Nullable CssNode child) {
    if (child == null) {
      return;
    }
    child.setParent(this);
  }

  /**
   * Makes this node the parent of a list of children.
   *
   * @param children
   */
  final void becomeParentForNodes(List<? extends CssNode> children) {
    Preconditions.checkNotNull(children);
    if (children.size() == 0) {
      // otherwise we'll spend a lot of CPU collecting garbage due to
      // the empty iterators we use below (as of June 2012, javac
      // does not optimize the Collection case of enh-for).
      return;
    }
    for (CssNode child : children) {
      becomeParentForNode(child);
    }
  }

  public void appendComment(CssCommentNode comment) {
    comments.add(comment);
    becomeParentForNode(comment);
  }

  public void setComments(List<CssCommentNode> comments) {
    Preconditions.checkNotNull(this.comments);
    removeAsParentOfNodes(this.comments);
    this.comments = Lists.newArrayList(comments);
    becomeParentForNodes(this.comments);
  }

  public List<CssCommentNode> getComments() {
    return comments;
  }

  /**
   * Returns whether one of the comments attached to this node exactly matches
   * the given string.
   */
  public boolean hasComment(String comment) {
    for (CssCommentNode c : comments) {
      if (c.getValue().equals(comment)) {
        return true;
      }
    }
    return false;
  }

  public boolean getShouldBeFlipped() {
    return shouldBeFlipped;
  }

  /**
   * Marks a node whether it should be flipped or not.
   *
   * @param shouldBeFlipped Whether the node should be flipped or not.
   */
  public void setShouldBeFlipped(boolean shouldBeFlipped) {
    this.shouldBeFlipped = shouldBeFlipped;
  }

  <T extends CssNode> List<T> copyToList(List<T> list) {
    return Lists.newArrayList(list);
  }

  /**
   * This is the default implementation of {@code equals()}. The method is
   * marked {@code final} so that nobody changes the default behavior.
   *
   * <p>The rationale for not allowing redefinition of equals is that different
   * compiler passes and different optimizations have their own notion of
   * equality. In order to avoid confusion and errors everybody that needs a
   * custom definition of equality and custom hash codes should use customized
   * collections with custom comparators or hash code providers.
   *
   * <p>In addition, this class should not implement {@link Comparable}. Use
   * {@link java.util.Comparator} instead.
   *
   * @see Object#equals(Object)
   */
  @Override
  public final boolean equals(@Nullable Object obj) {
    return super.equals(obj);
  }

  /**
   * This is the default implementation of {@code hashCode()}. The method is
   * marked {@code final} so that nobody changes the default behavior.
   *
   * @see #equals(Object)
   * @see Object#hashCode()
   */
  @Override
  public final int hashCode() {
    return super.hashCode();
  }

  /**
   * This is the default implementation of {@code toString()}.
   *
   * <p>Overriding this method should only be done for debugging or logging
   * purposes, not for the actual functionality of the compiler. If a string
   * representation of a tree is needed, define a Visitor that builds the
   * desired representation.
   *
   * @see Object#toString()
   */
  @Override
  public String toString() {
    return super.toString();
  }

  /**
   * This node and the transitive closure of its {@link #parent}s.
   */
  public Iterable<CssNode> ancestors() {
    return new Iterable<CssNode>() {
      @Override
      public Iterator<CssNode> iterator() {
        return new UnmodifiableIterator<CssNode>() {

          private CssNode current = CssNode.this;

          @Override
          public boolean hasNext() {
            return current != null;
          }

          @Override
          public CssNode next() {
            CssNode result = current;
            current = current.getParent();
            return result;
          }
        };
      }
    };
  }

  /**
   * Returns true when any of a {@code node}'s {@link #ancestors} is a function
   * node.
   */
  public boolean inFunArgs() {
    for (CssNode n : ancestors()) {
      if (n instanceof CssFunctionNode) {
        return true;
      }
    }
    return false;
  }

  public VisitController getVisitController() {
    return new DefaultVisitController(this, false /* allowMutating */);
  }

  public static <N extends CssNode> List<N> deepCopyNodes(List<N> nodes) {
    List<N> list = Lists.newArrayList();
    for (N node : nodes) {
      @SuppressWarnings("unchecked")
      N copy = (N) node.deepCopy();
      list.add(copy);
    }
    return list;
  }
}