/*
 * Copyright 2009 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.passes;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.css.compiler.ast.CssTreeVisitor;
import com.google.common.reflect.Reflection;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

/**
 * Dispatches to multiple {@code CssTreeVisitor}s. All {@code enter*} methods are called in the
 * order provided to the constructor and all {@code leave*} methods are called in the opposite
 * order.
 *
 * <p>Because {@code enter*} methods' return value changes the visitor behavior, the value returned
 * by the <em>last</em> delegate is the one returned by the method.
 */
public class DelegatingVisitor {
  private DelegatingVisitor() {}

  /**
   * Creates a {@code DelegatingVisitor} from the given list of visitors. The list must have at
   * least one element.
   */
  public static CssTreeVisitor from(List<CssTreeVisitor> originalVisitors) {
    Preconditions.checkArgument(originalVisitors.size() >= 1);
    if (originalVisitors.size() == 1) {
      return originalVisitors.get(0);
    }

    final ImmutableList<CssTreeVisitor> visitors = ImmutableList.copyOf(originalVisitors);
    final ImmutableList<CssTreeVisitor> reverseVisitors = visitors.reverse();
    return Reflection.newProxy(
        CssTreeVisitor.class,
        new InvocationHandler() {
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
              Object returnValue = null;
              Iterable<CssTreeVisitor> visitorsInOrderForMethod;
              if (method.getName().startsWith("enter")) {
                visitorsInOrderForMethod = visitors;
              } else { // assume it's a leave* method
                visitorsInOrderForMethod = reverseVisitors;
              }
              for (CssTreeVisitor visitor : visitorsInOrderForMethod) {
                returnValue = method.invoke(visitor, args);
              }
              return returnValue;
            } catch (InvocationTargetException e) {
              throw e.getTargetException();
            }
          }
        });
  }

  /**
   * Creates a {@code DelegatingVisitor} from the given array of visitors. Changes to the array will
   * not be reflected after construction.
   */
  public static CssTreeVisitor from(CssTreeVisitor... visitors) {
    return from(ImmutableList.copyOf(visitors));
  }
}