/*
 * Copyright 2012 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.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.PeekingIterator;
import com.google.common.collect.Sets;
import com.google.common.collect.UnmodifiableIterator;
import com.google.common.css.SourceCode;
import com.google.common.css.SourceCodeLocation;
import com.google.common.css.compiler.ast.CssCommentNode;
import com.google.common.css.compiler.ast.CssCompilerPass;
import com.google.common.css.compiler.ast.CssCompositeValueNode;
import com.google.common.css.compiler.ast.CssDeclarationNode;
import com.google.common.css.compiler.ast.CssLiteralNode;
import com.google.common.css.compiler.ast.CssNode;
import com.google.common.css.compiler.ast.CssNumericNode;
import com.google.common.css.compiler.ast.CssPriorityNode;
import com.google.common.css.compiler.ast.CssPropertyValueNode;
import com.google.common.css.compiler.ast.CssStringNode;
import com.google.common.css.compiler.ast.CssTree;
import com.google.common.css.compiler.ast.CssValueNode;
import com.google.common.css.compiler.ast.DefaultTreeVisitor;
import com.google.common.css.compiler.ast.ErrorManager;
import com.google.common.css.compiler.ast.GssError;
import com.google.common.css.compiler.ast.MutatingVisitController;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * Compiler pass that replaces font and font-family declaration subtrees
 * so that the tree structure resembles the (rather idiosyncratic) grammar
 * of the corresponding CSS properties.
 *
 */
public class FixupFontDeclarations extends DefaultTreeVisitor
    implements CssCompilerPass {

  /**
   * Specifies how the input tree should be interpreted.
   */
  public enum InputMode {
    /**
     * Assume the input follows the grammar of CSS.
     */
    CSS,
    /**
     * Perform a best-effort parse that allows unsubstituted definition uses.
     */
    GSS;
  }

  /**
   * Properties whose values may be specified in a {@code Font} declaration.
   */
  public enum FontProperty {
    STYLE, VARIANT, WEIGHT, SIZE, LINE_HEIGHT, FAMILY
  };

  /**
   * A simple predicate on {@code CssCompositeValueNode}.
   */
  private static class WithOperator
      implements Predicate<CssCompositeValueNode> {
    private final CssCompositeValueNode.Operator op;

    public WithOperator(CssCompositeValueNode.Operator op) {
      this.op = op;
    }

    @Override public boolean apply(CssCompositeValueNode n) {
      if (n.getOperator() != op) {
        return false;
      }
      return true;
    }
  }

  private static WithOperator withOperator(CssCompositeValueNode.Operator op) {
    return new WithOperator(op);
  }

  private static final String FONT = "font";
  private static final String FONT_FAMILY = "font-family";
  private static final String NORMAL = "normal";
  private static final String INHERIT = "inherit";
  private static final ImmutableSet<String> SYSTEM_FONTS =
      ImmutableSet.of("caption", "icon", "menu", "message-box", "small-caption", "status-bar");
  private static final ImmutableSet<String> FONT_ABSOLUTE_SIZES =
      ImmutableSet.of("xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large");
  private static final ImmutableSet<String> FONT_RELATIVE_SIZES =
      ImmutableSet.of("larger", "smaller");
  private static final ImmutableSet<String> DEFINITELY_STYLE = ImmutableSet.of("italic", "oblique");
  private static final ImmutableSet<String> DEFINITELY_VARIANT = ImmutableSet.of("small-caps");
  private static final ImmutableSet<String> DEFINITELY_WEIGHT =
      ImmutableSet.of("bold", "bolder", "lighter");
  private static final ImmutableSet<String> NUMERIC_WEIGHTS =
      ImmutableSet.of("100", "200", "300", "400", "500", "600", "700", "800", "900");

  /**
   * No point looking through lots of nodes for clues that are only allowed
   * in a prefix. This limit is conservative, assuming we create as many
   * top-level nodes as tokens. As we'll see shortly, we actually expect
   * the parser to structure things slightly more elaborately, resulting
   * in fewer top-level nodes.
   */
  private static final int PRE_FAMILY_LIMIT = 6;

  /**
   * Recognizes a simple size value represented by a node in the
   * original AST of a font declaration value.
   */
  private static final Predicate<CssValueNode> IS_PLAIN_SIZE =
      new Predicate<CssValueNode>() {
    @Override public boolean apply(CssValueNode n) {
      return (n instanceof CssNumericNode
              && (!CssNumericNode.NO_UNITS.equals(
                  ((CssNumericNode) n).getUnit())))
      || FONT_ABSOLUTE_SIZES.contains(n.getValue())
      || FONT_RELATIVE_SIZES.contains(n.getValue());
    }
  };

  /**
   * Picks out the size from the original AST subtree representing
   * size/line-height in a font declaration value.
   */
  private static final
      Function<CssCompositeValueNode, CssValueNode> EXTRACT_SIZE =
      new Function<CssCompositeValueNode, CssValueNode>() {
    @Override public CssValueNode apply(CssCompositeValueNode n) {
      return n.getValues().get(0);
    }
  };

  @VisibleForTesting
  static final String SIZE_AND_FAMILY_REQUIRED =
      "Size and family are required in the absence of a system font or a "
      + "simple inherit";

  @VisibleForTesting
  static final ImmutableMap<FontProperty, String> TOO_MANY =
      ImmutableMap.<FontProperty, String>builder()
          .put(FontProperty.LINE_HEIGHT, "The '/' can occur at most once in a font shorthand value")
          .put(FontProperty.SIZE, "Font size can occur at most once in a font shorthand value")
          .put(FontProperty.STYLE, "Font style can occur at most once in a font shorthand value")
          .put(
              FontProperty.VARIANT, "Font variant can occur at most once in a font shorthand value")
          .put(FontProperty.WEIGHT, "Font weight can occur at most once in a font shorthand value")
          .build();

  private static final ImmutableSortedSet<FontProperty> SLOTTABLE_PROPERTIES =
      ImmutableSortedSet.of(
          FontProperty.STYLE, FontProperty.VARIANT, FontProperty.WEIGHT);

  @VisibleForTesting
  static final String TOO_MANY_NORMALS =
      "The keyword normal can occur at most thrice in a font shorthand value";

  @VisibleForTesting
  static final String NORMAL_TOO_LATE =
      "The keyword normal is only allowed in the first three tokens of a "
      + "font shorthand";

  @VisibleForTesting
  static final String SIZE_AFTER_HEIGHT =
      "Font size must be specified before line-height";

  @VisibleForTesting
  static final String TOO_MANY_PRE_SIZE =
      "Too many font shorthand tokens before size";

  @VisibleForTesting
  static final String PRE_SIZE_INTERLOPER_SIZE =
      "Unrecognized tokens immediately preceding size";

  /**
   * Should the input be parsed strictly or do we assume gss variables
   * might be substituted later?
   */
  private final InputMode mode;

  private final ErrorManager errorManager;

  private final MutatingVisitController visitController;

  private CssTree tree;

  public FixupFontDeclarations(
      InputMode mode, ErrorManager errorManager, CssTree tree) {
    this.mode = mode;
    this.errorManager = errorManager;
    this.tree = tree;
    visitController = tree.getMutatingVisitController();
  }

  @Override
  public boolean enterDeclaration(CssDeclarationNode decl) {
    String propertyName = decl.getPropertyName().getProperty().getName();
    if (!(FONT.equals(propertyName) || FONT_FAMILY.equals(propertyName))) {
      return false;
    }

    CssDeclarationNode d = decl.deepCopy();
    List<CssDeclarationNode> replacement = ImmutableList.of(d);
    if (FONT.equals(propertyName)) {
      d.setPropertyValue(reparseFont(decl.getPropertyValue()));
    } else if (FONT_FAMILY.equals(propertyName)) {
      d.setPropertyValue(reparseFontFamily(decl.getPropertyValue()));
    }
    visitController.replaceCurrentBlockChildWith(replacement, false);
    return false;
  }

  private CssPropertyValueNode reparseFont(CssPropertyValueNode n) {
    // Preliminary easy cases
    if (n.numChildren() == 0) {
      return n.deepCopy();
    } else if (n.numChildren() == 1
               && SYSTEM_FONTS.contains(n.getChildAt(0).getValue())
               || INHERIT.equals(n.getChildAt(0).getValue())) {
      return n.deepCopy();
    } else if (n.numChildren() < 2) {
      if (mode == InputMode.CSS) {
        errorManager.report(
            new GssError(SIZE_AND_FAMILY_REQUIRED, getSourceCodeLocation(n)));
      }
      return n.deepCopy();
    }

    // Some clients want to be able to whitelist typefaces or otherwise
    // understand the font shorthand in detail. Unbound GSS variables make
    // this pretty hopeless:
    //   font: CLUELESS;
    // but we can try:
    //   font: italic bold SIZE/LEADING FAMILIES;
    // and a best-effort parse tree for that might be useful.
    //
    // The best case for us is CSS, where we can rely on clues such as
    // the keywords that can disambiguate some cases of style vs. variant
    // and the slash token that tells us that the surrounding tokens are
    // size and line-height and not e.g., size and a family name.
    //
    // Our strategy is to segment the property value and deal with each
    // segment independently. Segmentation isn't straightforward because
    // tokens can't always be understood independently:
    //   font: normal italic 100 medium medium roman regular;
    //         ^the next two tokens tell us this normal specifies a font-variant
    //                       ^this is easily understood to be a font-weight
    //                           ^obviously a font-size
    //                                  ^because we're past size, this must
    //                                   be a font-family name component

    // Try to recognize things individually, then check for conflicts
    // among our constraints, and finally rebuild our AST.

    Iterable<CssValueNode> preFamilyCandidates =
        Iterables.limit(n.childIterable(), PRE_FAMILY_LIMIT);

    Iterable<CssCompositeValueNode> sizeLineHeights =
        Iterables.filter(
            extractByType(CssCompositeValueNode.class, preFamilyCandidates),
            withOperator(CssCompositeValueNode.Operator.SLASH));
    Iterable<CssValueNode> plainSizes =
        Iterables.filter(
            preFamilyCandidates,
            IS_PLAIN_SIZE);
    Iterable<CssValueNode> lhSizes =
        Iterables.transform(
            sizeLineHeights,
            EXTRACT_SIZE);
    final HashMap<CssNode, Integer> lexicalOrder =
        EnumeratingVisitor.enumerate(tree);
    Iterable<CssValueNode> sizes = Iterables.concat(plainSizes, lhSizes);
    if (!validateSplitPoint(
            getSourceCodeLocation(n), lexicalOrder, sizeLineHeights, sizes)) {
      return n.deepCopy();
    }

    // Now we can split on the one and only size node.
    final CssValueNode splitPoint =
        Iterables.getOnlyElement(Iterables.concat(sizeLineHeights, plainSizes));
    Iterable<CssValueNode> prefix =
        takeWhile(n.childIterable(), new Predicate<CssValueNode>() {
            @Override public boolean apply(CssValueNode n) {
              return lexicalOrder.get(n).compareTo(
                  lexicalOrder.get(splitPoint)) < 0;
            }
          });
    final CssPriorityNode priority = getPriority(n);
    Iterable<CssValueNode> families =
        dropWhile(
            takeUntil(n.childIterable(), priority),
            new Predicate<CssValueNode>() {
              @Override public boolean apply(CssValueNode n) {
                return lexicalOrder.get(splitPoint).compareTo(
                    lexicalOrder.get(n)) < 0;
              }
            });
    final Map<CssValueNode, FontProperty> properties =
        classifyNodes(prefix, sizes, sizeLineHeights);

    // Validate analysis
    validatePrefix(prefix);
    validateProperties(prefix, properties);

    // Build output
    return rebuildFont(prefix, splitPoint, families, priority, properties, n);
  }

  private CssPriorityNode getPriority(CssPropertyValueNode n) {
    if (n.numChildren() < 1) return null;

    CssNode last = n.getChildAt(n.numChildren() - 1);
    if (last instanceof CssPriorityNode) {
      return (CssPriorityNode) last;
    } else {
      return null;
    }
  }

  private <T> Iterable<T> takeUntil(Iterable<T> xs, final T excludedEndpoint) {
    return takeWhile(
        xs,
        new Predicate<T>() {
          @Override public boolean apply(T i) {
            return excludedEndpoint != i;
          }
        });
  }

  private Map<CssValueNode, FontProperty> classifyNodes(
      Iterable<CssValueNode> prefix,
      Iterable<CssValueNode> sizes,
      Iterable<CssCompositeValueNode> sizeLineHeights) {
    final Map<CssValueNode, FontProperty> properties = Maps.newHashMap();
    for (CssValueNode i : prefix) {
      if (DEFINITELY_STYLE.contains(i.getValue())) {
        properties.put(i, FontProperty.STYLE);
      } else if (DEFINITELY_VARIANT.contains(i.getValue())) {
        properties.put(i, FontProperty.VARIANT);
      } else if (isWeight(i)) {
        properties.put(i, FontProperty.WEIGHT);
      }
    }
    properties.put(Iterables.getOnlyElement(sizes), FontProperty.SIZE);
    if (!Iterables.isEmpty(sizeLineHeights)) {
      properties.put(
          Iterables.getOnlyElement(sizeLineHeights).getValues().get(1),
          FontProperty.LINE_HEIGHT);
    }
    return properties;
  }

  private boolean isWeight(CssValueNode n) {
    if (DEFINITELY_WEIGHT.contains(n.getValue())) {
      return true;
    }
    if (!(n instanceof CssNumericNode)) {
      return false;
    }
    CssNumericNode numeric = (CssNumericNode) n;
    if (!CssNumericNode.NO_UNITS.equals(numeric.getUnit())) {
      return false;
    }
    return (NUMERIC_WEIGHTS.contains(numeric.getNumericPart()));
  }

  private void validateSizeLineHeight(CssCompositeValueNode composite) {
    if (composite.getValues().size() != 2) {
      reportError(TOO_MANY.get(FontProperty.LINE_HEIGHT),
                  getSourceCodeLocation(composite));
    }
  }

  private void reportError(String message, SourceCodeLocation location) {
    errorManager.report(new GssError(message, location));
  }

  private boolean validateSplitPoint(
      SourceCodeLocation loc,
      HashMap<CssNode, Integer> lexicalOrder,
      Iterable<CssCompositeValueNode> sizeLineHeights,
      Iterable<CssValueNode> sizes) {
    CssCompositeValueNode secondSLH = Iterables.get(sizeLineHeights, 1, null);
    if (secondSLH != null) {
      reportError(TOO_MANY.get(FontProperty.LINE_HEIGHT),
                  getSourceCodeLocation(secondSLH));
      return false;
    }
    CssCompositeValueNode slashy = Iterables.find(
        sizeLineHeights,
        new Predicate<CssCompositeValueNode>() {
          @Override public boolean apply(CssCompositeValueNode n) {
            return n.getValues().size() != 2;
          }
        },
        null);
    if (slashy != null) {
      reportError(TOO_MANY.get(FontProperty.LINE_HEIGHT),
                  getSourceCodeLocation(slashy));
    }
    if (Iterables.isEmpty(sizes)) {
      if (mode == InputMode.CSS) {
        reportError(SIZE_AND_FAMILY_REQUIRED, loc);
      }
      return false;
    }
    if (Iterables.get(sizes, 1, null) != null) {
      reportError(TOO_MANY.get(FontProperty.SIZE),
                  getSourceCodeLocation(
                      max(sizes, Functions.forMap(lexicalOrder))));
      return false;
    }
    return true;
  }

  private void validatePrefix(
      Iterable<CssValueNode> prefix) {
    CssValueNode tooMuch = Iterables.get(prefix, 3, null);
    if (tooMuch != null) {
      reportError(TOO_MANY_PRE_SIZE, getSourceCodeLocation(tooMuch));
    }
  }

  private void validateProperties(
      Iterable<CssValueNode> prefix,
      final Map<CssValueNode, FontProperty> classified) {
    final List<CssValueNode> normals = Lists.newLinkedList();
    for (CssValueNode i : prefix) {
      if (!classified.containsKey(i) && NORMAL.equals(i.getValue())) {
        normals.add(i);
      }
    }
    if (normals.size() > 3) {
      errorManager.report(
          new GssError(
              TOO_MANY_NORMALS,
              getSourceCodeLocation(normals.get(normals.size() - 1))));
    }
    HashSet<FontProperty> properties = Sets.newHashSet();
    for (Map.Entry<CssValueNode, FontProperty> p : classified.entrySet()) {
      if (!properties.add(p.getValue())) {
        reportError(TOO_MANY.get(p.getValue()),
                    getSourceCodeLocation(p.getKey()));
      }
    }
    if (mode == InputMode.CSS) {
      CssValueNode interloper =
          Iterables.find(
              prefix,
              new Predicate<CssValueNode>() {
                @Override public boolean apply(CssValueNode n) {
                  return !classified.containsKey(n) && !normals.contains(n);
                }
              },
              null);
      if (interloper != null) {
        reportError(PRE_SIZE_INTERLOPER_SIZE,
                    getSourceCodeLocation(interloper));
      }
    }
  }

  private CssPropertyValueNode rebuildFont(
      Iterable<CssValueNode> prefix,
      CssValueNode splitPoint,
      Iterable<CssValueNode> families,
      CssPriorityNode priority,
      final Map<CssValueNode, FontProperty> properties,
      CssPropertyValueNode n) {
    TreeMap<FontProperty, CssValueNode> parts =
        Maps.newTreeMap();
    for (Map.Entry<CssValueNode, FontProperty> p : properties.entrySet()) {
      parts.put(p.getValue(), p.getKey());
    }
    List<CssValueNode> preFamily = Lists.newArrayList();
    Iterables.addAll(preFamily, prefix);
    if (parts.containsKey(FontProperty.SIZE)) {
      preFamily.add(parts.get(FontProperty.SIZE));
    }
    if (parts.containsKey(FontProperty.LINE_HEIGHT)) {
      CssValueNode lineHeight = parts.get(FontProperty.LINE_HEIGHT);
      preFamily.add(new CssLiteralNode("/", getSourceCodeLocation(lineHeight)));
      preFamily.add(lineHeight);
    }
    List<CssValueNode> tail =
        Iterables.isEmpty(families)
        ? ImmutableList.<CssValueNode>of()
        : ImmutableList.<CssValueNode>of(reparseFamilies(
            families,
            SourceCodeLocation.merge(families)));
    ImmutableList.Builder<CssValueNode> resultNodes = ImmutableList.builder();
    resultNodes.addAll(Iterables.concat(preFamily, tail));
    if (priority != null) {
      resultNodes.add(priority);
    }
    CssPropertyValueNode result = new CssPropertyValueNode(resultNodes.build());
    return result.deepCopy();
  }

  private CssCompositeValueNode reparseFamilies(
      Iterable<CssValueNode> families, SourceCodeLocation loc) {
    // they will be a comma-delimited sequence of (strings | id-sequences)
    List<CssValueNode> alternatives = Lists.newArrayList();
    List<CssCommentNode> commentsOnAlternatives = Lists.newArrayList();
    for (CssValueNode i : families) {
      if (i instanceof CssCompositeValueNode) {
        CssCompositeValueNode segment = (CssCompositeValueNode) i;
        if (segment.getValues().size() == 0) {
          continue;
        }
        CssValueNode first = Iterables.getFirst(segment.getValues(), null);
        collect(alternatives, first);
        Iterable<CssValueNode> rest = Iterables.skip(segment.getValues(), 1);
        Iterables.addAll(alternatives, rest);
        for (CssNode j : rest) {
          for (CssCommentNode c : j.getComments()) {
            commentsOnAlternatives.add(c);
          }
        }
      } else {
        collect(alternatives, i);
      }
    }
    CssCompositeValueNode result = new CssCompositeValueNode(
        alternatives, CssCompositeValueNode.Operator.COMMA, loc);
    for (CssCommentNode c : commentsOnAlternatives) {
      result.appendComment(c);
    }
    return result;
  }

  private CssPropertyValueNode reparseFontFamily(CssPropertyValueNode n) {
    if (n.numChildren() == 0) {
      return n.deepCopy();
    }
    if (n.numChildren() == 1
        && INHERIT.equals(n.getChildAt(0).getValue())) {
      return n.deepCopy();
    }
    CssPriorityNode priority = getPriority(n);
    // deal with alternatives
    CssCompositeValueNode altNode = reparseFamilies(
            takeUntil(n.childIterable(), priority),
            getSourceCodeLocation(n));
    ImmutableList.Builder<CssValueNode> result = ImmutableList.builder();
    result.add(altNode);
    if (priority != null) {
      result.add(priority);
    }
    return new CssPropertyValueNode(result.build());
  }

  /**
   * Agglomerate each id sequence into a space-separated literal value,
   * leaving strings as they were and fixing up comments and
   * SourceCodeLocations as best we can.
   */
  private void collect(List<CssValueNode> alternatives, CssValueNode item) {
    if (isString(item)) {
      alternatives.add(item);
    } else {
      CssValueNode stump;
      if (alternatives.size() > 0
          && !isString(alternatives.get(alternatives.size() - 1))) {
        // concatenate onto previous node
        stump = alternatives.get(alternatives.size() - 1);
        stump.setValue(stump.getValue() + " ");
      } else {
        // start a new node
        stump = new CssLiteralNode("", getSourceCodeLocation(item));
        alternatives.add(stump);
      }
      stump.setValue(stump.getValue() + item.getValue());
      stump.setSourceCodeLocation(
          SourceCodeLocation.merge(stump.getSourceCodeLocation(), item.getSourceCodeLocation()));
      for (CssCommentNode c : item.getComments()) {
        stump.appendComment(c);
      }
    }
  }

  private boolean isString(CssValueNode n) {
    return n instanceof CssStringNode;
  }

  private static SourceCodeLocation getSourceCodeLocation(CssNode n) {
    n = Iterables.find(n.ancestors(), new Predicate<CssNode>() {
        @Override public boolean apply(CssNode n) {
          return n.getSourceCodeLocation() != null;
        }
      }, null);
    return n != null
        ? n.getSourceCodeLocation()
        : new SourceCodeLocation(
            new SourceCode(null, "x"),
            1, 1, 1, 1, 1, 1);
  }

  /**
   * Computes the prefix of {@code xs} for which {@code p} holds.
   */
  private <T> Iterable<T> takeWhile(
      final Iterable<T> xs, final Predicate<? super T> p) {
    return new Iterable<T>() {
      @Override
      public Iterator<T> iterator() {
        return new UnmodifiableIterator<T>() {
          Iterator<T> xsi = xs.iterator();
          boolean validT = false;
          T t;
          {
            next();
          }
          @Override
          public boolean hasNext() {
            return validT;
          }
          @Override
          public T next() {
            T result = t;
            if (xsi.hasNext()) {
              t = xsi.next();
              validT = p.apply(t);
            } else {
              validT = false;
            }
            return result;
          }
        };
      }
    };
  }

  /**
   * Computes the suffix of {@code xs} starting at the first node for which
   * {@code p} fails.
   *
   * {@code Iterables.concat(takeWhile(xs, p), dropWhile(xs, p)) = xs}
   */
  private <T> Iterable<T> dropWhile(
      final Iterable<T> xs, final Predicate<? super T> p) {
    return new Iterable<T>() {
      @Override
      public Iterator<T> iterator() {
        PeekingIterator<T> xsi = Iterators.peekingIterator(xs.iterator());
        while (xsi.hasNext()) {
          if (p.apply(xsi.peek())) {
            break;
          }
          xsi.next();
        }
        return xsi;
      }
    };
  }

  /**
   * Returns an element of {@code xs} whose image under {@code f} is
   * maximal.
   */
  private <T, U extends Comparable<U>> T max(
      Iterable<T> xs, Function<? super T, U> f) {
    T result = Iterables.getFirst(xs, null);
    U extreme = f.apply(result);
    for (T x : xs) {
      U u = f.apply(x);
      if (extreme.compareTo(u) < 0) {
        result = x;
        extreme = u;
      }
    }
    return result;
  }

  /**
   * Filter to elements of type {@code T} designated by {@code ct}
   * and cast the result.
   */
  private <T> Iterable<T> extractByType(
      final Class<T> ct, Iterable<? super T> xs) {
    return Iterables.transform(
        Iterables.filter(xs, Predicates.instanceOf(ct)),
        new Function<Object, T>() {
          @Override public T apply(Object x) {
            return ct.cast(x);
          }
        });
  }

  @Override
  public void runPass() {
    visitController.startVisit(this);
  }
}