class ClassDefScanner {

  public List<ClassDef> scan(final Scanner files) throws Exception {
    checkNotNull(files);

    final boolean debug = log.isDebugEnabled();

    // reset and scan for files
    classes.clear();

    log.debug("Finding files to scan...");
    files.scan();

    // scan each file
    String[] included = files.getIncludedFiles();
    log.debug("Scanning " + included.length + " files...");
    for (String path : included) {
      File file = new File(files.getBasedir(), path);
      scan(file);
    }

    // TODO: Sort out how/if we want to resolve aliases, ATM this data is collected by not used

    // Resolve all references and build map with all class defs (including aliases/alts)
    Map<String, ClassDef> classNames = new HashMap<>();
    List<ClassDef> allClasses = new ArrayList<>();
    for (ClassDef def : classes.values()) {
      log.debug("Processing: " + def);

      classNames.put(def.getName(), def);
      for (String alt : def.getAlternateClassName()) {
        classNames.put(alt, def);
      }

      resolve(def);

      allClasses.add(def);
    }

    // Sort all classes by priority, so that higher-priority value is closer to the start and processed sooner
    // ie. priority=10 is closer to the start than priority=1
    Collections.sort(allClasses, new Comparator<ClassDef>()
    {
      @Override
      public int compare(final ClassDef a, final ClassDef b) {
        return Double.compare(b.getPriority(), a.getPriority());
      }
    });

    if (debug) {
      log.debug("All classes:");
      for (ClassDef def : allClasses) {
        log.debug("  " + def.getName());
      }
    }

    // build the graph
    DAG graph = new DAG();
    for (ClassDef def : allClasses) {
      graph.addVertex(def.getName());
      for (String name : def.getDependencies()) {
        // resolve dependencies which have class defs to primary class name
        ClassDef dep = classNames.get(name);
        if (dep != null) {
          name = dep.getName();
        }
        graph.addEdge(def.getName(), name);
      }
    }

    // display some debug information about the graph
    if (debug) {
      log.debug("Vertices:");
      for (Vertex v : graph.getVerticies()) {
        log.debug("  " + v.getLabel());
        if (!v.getParents().isEmpty()) {
          log.debug("    parents:");
          for (Vertex parent : v.getParents()) {
            log.debug("      " + parent.getLabel());
          }
        }
        if (!v.getChildren().isEmpty()) {
          log.debug("    children:");
          for (Vertex child : v.getChildren()) {
            log.debug("      " + child.getLabel());
          }
        }
      }
    }

    // result sorted list of class definitions
    Set<ClassDef> results = new LinkedHashSet<>();
    log.debug("Ordered classes:");
    // the graph contains many references, only include those which are class defs
    for (String className : TopologicalSorter.sort(graph)) {
      ClassDef def = classNames.get(className);
      // skip duplicates (due to alt/aliases)
      if (def != null && !results.contains(def)) {
        log.debug("  " + def.getName());
        results.add(def);
      }
    }

    log.debug("Found " + results.size() + " classes");
    return new ArrayList<>(results);
  }

  public  ClassDefScanner(final Log log);

}