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

import java.util.List;

import javax.annotation.Nullable;

/**
 * A node representing a custom GSS function call.
 *
 * @author oana@google.com (Oana Florescu)
 * @author dgajda@google.com (Damian Gajda)
 */
public class CssCustomFunctionNode extends CssFunctionNode
    implements Proxiable<CssCustomFunctionNode> {

  private final String gssFunctionName;
  protected List<CssValueNode> resultNodes;

  /**
   * Constructor of the node.
   *
   * @param gssFunctionName name of the function "called" by this node
   * @param sourceCodeLocation location of this node
   */
  public CssCustomFunctionNode(
      String gssFunctionName,
      SourceCodeLocation sourceCodeLocation) {
    super(Function.CUSTOM, sourceCodeLocation);
    this.gssFunctionName = gssFunctionName;
  }

  /**
   * Copy constructor.
   *
   * @param function the copied custom function node
   */
  public CssCustomFunctionNode(CssCustomFunctionNode function) {
    super(function);
    this.gssFunctionName = function.getFunctionName();
  }

  /**
   * Constructor used by the proxy mechanism, which avoids creation of child
   * nodes.
   *
   * <p>NOTE(dgajda): The signature of this constructor only differs in argument
   * order from the main constructor of this class.
   *
   * @param gssFunctionName name of the function "called" by this node
   * @param sourceCodeLocation location of this node
   */
  private CssCustomFunctionNode(
      @Nullable SourceCodeLocation sourceCodeLocation,
      String gssFunctionName) {
    super(sourceCodeLocation, Function.CUSTOM);
    this.gssFunctionName = gssFunctionName;
  }

  @Override
  public CssCustomFunctionNode deepCopy() {
    return new CssCustomFunctionNode(this);
  }


  @Override
  public String getFunctionName() {
    return gssFunctionName;
  }

  @Override
  public String toString() {
    return gssFunctionName + super.toString();
  }

  /** {@inheritDoc} */
  @Override
  public void setArguments(CssFunctionArgumentsNode arguments) {
    resultNodes = null;
    super.setArguments(arguments);
  }

  /**
   * @return the copy-on-write proxy for this node
   */
  @Override
  public CssCustomFunctionNode createProxy() {
    return new CssCustomFunctionNodeProxy(this);
  }

  /**
   * @return cached result of function value computation
   */
  public List<CssValueNode> getResult() {
    return resultNodes;
  }

  /**
   * Saves the value of function computation for later use.
   *
   * @param functionResult the list of nodes returned by the call to the
   *     {@link GssFunction}
   */
  public void setResult(List<CssValueNode> functionResult) {
    resultNodes = functionResult;
  }

  /**
   * Copy-on-Write proxy for {@link CssCustomFunctionNode} used to avoid copying
   * of function call subtrees in
   * {@link com.google.common.css.compiler.passes.ReplaceConstantReferences}
   * pass. It behaves as a proxy of the original node until a write operation
   * happens. When the first write operation happens, proxy node copies the
   * original node and since then acts as a plain {@link CssCustomFunctionNode}.
   *
   * <p>NOTE(dgajda): Proxied custom function nodes are also replaced by proxies
   * of themselves to disallow changes, which would be visible to all proxy
   * nodes. Only one kind of changes is propagated to proxy nodes - this is
   * setting of function computation results.
   */
  public static class CssCustomFunctionNodeProxy extends CssCustomFunctionNode {

    private CssCustomFunctionNode proxiedNode;

    /**
     * Constructs the node proxy. It uses a special super class constructor
     * which avoids unnecessary initialization of arguments node.
     *
     * @param function the proxied node
     */
    public CssCustomFunctionNodeProxy(CssCustomFunctionNode function) {
      super(function.getSourceCodeLocation(),
          function.getFunctionName());
      this.proxiedNode = function;
      setChunk(function.getChunk());
    }

    /** {@inheritDoc} */
    @Override
    public CssFunctionArgumentsNode getArguments() {
      return proxiedNode != null ?
          proxiedNode.getArguments() : super.getArguments();
    }

    /** {@inheritDoc} */
    @Override
    public void setArguments(CssFunctionArgumentsNode arguments) {
      proxiedNode = null;
      super.setArguments(arguments);
    }

    /** {@inheritDoc} */
    @Override
    public CssCustomFunctionNode createProxy() {
      CssCustomFunctionNode newProxiedNode = proxiedNode != null ?
          proxiedNode : this;
      return new CssCustomFunctionNodeProxy(newProxiedNode);
    }

    /** {@inheritDoc} */
    @Override
    public List<CssValueNode> getResult() {
      if (proxiedNode == null) {
        return super.getResult();
      }

      List<CssValueNode> proxiedNodeResults = proxiedNode.getResult();
      if (proxiedNodeResults == null) {
        return null;
      }

      // Copy results because they are not owned by this node.
      List<CssValueNode> result =
          Lists.newArrayListWithCapacity(proxiedNodeResults.size());
      for (CssValueNode cssValueNode : proxiedNodeResults) {
        result.add(cssValueNode.deepCopy());
      }
      return result;
    }

    /**
     * Sets the results of function computation for this node, can set results
     * on the proxied node. This is harmless because proxied node is exactly
     * the same as proxy node, and function computation results are a
     * derivative of function arguments.
     */
    @Override
    public void setResult(List<CssValueNode> functionResult) {
      if (proxiedNode == null) {
        super.setResult(functionResult);
      } else {
        proxiedNode.setResult(functionResult);
      }
    }
  }
}
