Calculates the dependencies of the classes to create a load order

@param classes
function() {
      var t = this;
      var Console = qx.tool.compiler.Console.getInstance();
      var analyser = this.getAnalyser();
      var db = analyser.getDatabase();
      var allDeps = new qx.tool.compiler.utils.IndexedArray();
      var exclude = {};
      var fatalCompileErrors = [];

      this.__loadDeps = null;
      
      var partsByName = {};
      var parts = [];
      var bootPart = null;
      var classDataByClassname = {};
      
      if (this.__parts && this.__parts.length) {
        parts = [];
        t.__parts.forEach(part => {
          if (partsByName[part.getName()]) {
            throw new Error(Console.decode("qx.tool.compiler.application.duplicatePartNames", part.getName())); 
          }
          var partData = {
              name: part.getName(),
              include: part.getInclude(),
              exclude: part.getExclude(),
              classes: [],
              dependsOn: {},
              combine: part.getCombine(),
              minify: part.getMinify()
          };
          partData.match = qx.tool.compiler.app.Application.createWildcardMatchFunction(part.getInclude(), part.getExclude());
          partsByName[part.getName()] = partData;
          parts.push(partData);
        });
        bootPart = partsByName.boot;
        if (!bootPart) {
          throw new Error(Console.decode("qx.tool.compiler.application.noBootPart")); 
        } 
      } else {
        bootPart = {
            name: "boot",
            include:[ "*" ],
            exclude: [],
            classes: [],
            dependsOn: {},
            combine: false,
            minify: false,
            match: function() {
              return true; 
            }
        };
        partsByName.boot = bootPart;
        parts.push(bootPart);
      }
      
      function createClassData(classname) {
        if (classDataByClassname[classname]) {
          return classDataByClassname[classname];
        }
        var classData = classDataByClassname[classname] = {
            classname: classname,
            parts: {},
            best: null,
            actual: null
        };
        parts.forEach(part => {
          if (part === bootPart) {
            return;
          }
          var result = part.match(classname);
          if (result !== null) {
            classData.parts[part.name] = result;
          
            var lastMatch = classData.best && classData.parts[classData.best.name];
            if (lastMatch === undefined || lastMatch === null) {
              classData.best = part; 
            
            // Exact
            } else if (lastMatch === "exact") {
              if (result === "exact") {
                Console.print("qx.tool.compiler.application.conflictingExactPart", classname, part.name, classData.best.name); 
              }
            
            // Wildcard
            } else {
              qx.core.Assert.assertTrue(typeof lastMatch == "number");
              if (result === "exact") {
                classData.best = part;
                classData.exact = true;
              } else {
                qx.core.Assert.assertTrue(typeof result == "number");
                if (lastMatch === result) {
                  Console.print("qx.tool.compiler.application.conflictingBestPart", classname, part.name, classData.best.name); 
                } else if (lastMatch < result) {
                  classData.best = part;
                }
              }
            }
          }
        });
        return classData;
      }
      
      var needed = new qx.tool.compiler.utils.IndexedArray();
      var neededIndex = 0;
      var stack = new qx.tool.compiler.utils.IndexedArray();
      
      
      /*
       * We could say that when a class is `.require`d, then we treat any of it's `construct:true` dependencies as `require:true`
       * The problem is given this example:
       *    qx.core.Init.defer()
       *      qx.event.Registration.addListener
       *        qx.event.Registration.getManager
       *          qx.event.Manager.construct
       *            new qx.util.DeferredCall
       *            
       *    new qx.util.DeferredCall is a runtime only dependency so is not available.
       *    
       * So the theory is that deferred calls tend to be about initialisation, so prioritising constructor dependencies
       * may be helpful
       */
      
      /*
       * Recursively finds all the dependencies for a class which have not already been added
       * to the list of classes to load.
       * 
       *  @param classname {String}
       *  @param deps {String[]} array to modify
       */
      function compileAllRemainingDeps(classname, deps) {
        var checked = {};
        var depNames = { };
        depNames[classname] = true;
        
        function search(classname) {
          if (checked[classname]) {
            return;
          }
          checked[classname] = true;

          var info = db.classInfo[classname];
          if (info && info.dependsOn) {
            for (var depName in info.dependsOn) {
              var dd = info.dependsOn[depName];
              if (dd.load || dd.require || dd.defer || dd.construct) {
                if (!allDeps.contains(depName)) {
                  depNames[depName] = true; 
                }
                search(depName);
              }
            }
          }
        }
        
        search(classname);
        for (var depName in depNames) {
          deps.push(depName);
        }
      }

      var addDepDepth = 0;
      function addDep(classname) {
        if (exclude[classname]) {
          return;
        }
        if (allDeps.contains(classname) || stack.contains(classname)) {
          return;
        }

        var info = db.classInfo[classname];
        if (!info) {
          return;
        }
        if (info.fatalCompileError) {
          fatalCompileErrors.push(classname);
        }
        
        addDepDepth++;
        
        var environmentLoadDeps = {};
        if (info.environment && info.environment.required) {
          for (var key in info.environment.required) {
            var envInfo = info.environment.required[key];
            if (envInfo.defer) {
              environmentLoadDeps[envInfo.className] = true; 
            }
          }
        }
        
        var deferDeps = [];
        if (info.dependsOn) {
          stack.push(classname);
          for (var depName in info.dependsOn) {
            var dd = info.dependsOn[depName];
            if (dd.load || dd.require || environmentLoadDeps[depName]) {
              addDep(depName);
            } else if (dd.defer) {
              deferDeps.push(depName);
            } else if (!allDeps.contains(depName)) {
              needed.push(depName); 
            }
          }
          stack.remove(classname);
        }

        if (parts && !allDeps.contains(classname)) {
          var classData = createClassData(classname);
          var part = classData.best||bootPart;
          part.classes.push(classname);
          classData.actual = part;
        }
        allDeps.push(classname);
        deferDeps.forEach(function(depName) {
          var deps = [];
          compileAllRemainingDeps(depName, deps);
          deps.forEach(addDep);
        });
        if (addDepDepth === 1) {
          while (neededIndex < needed.getLength()) {
            classname = needed.getItem(neededIndex++);
            addDep(classname);
          }
        }
        addDepDepth--;
      }

      exclude = {};
      t.__expandClassnames(t.getExclude()).forEach(name => exclude[name] = true);
      
      // Start the ball rolling
      addDep("qx.core.Object");
      t.getRequiredClasses().forEach(function(classname) {
        addDep(classname);
      });
      if (t.getTheme()) {
        addDep(t.getTheme());
      }
      /*
      parts.forEach((part) => {
        if (part !== bootPart) {
          t.__expandClassnames(part.include).forEach((name) => addDep(name));
        }
      });
      */
      
      while (neededIndex < needed.length) {
        var classname = needed[neededIndex++];
        addDep(classname);
      }
      
      if (parts && parts.length > 1) {
        // Calculate inter-part dependencies
        parts.forEach(part => {
          var checked = {};
          function check(classname) {
            if (checked[classname]) {
              return; 
            }
            checked[classname] = true;
            var classData = classDataByClassname[classname];
            if (classData.actual !== part) {
              part.dependsOn[classData.actual.name] = true;
            }
            for (var depName in db.classInfo.dependsOn) {
              check(depName); 
            }
          }
          part.classes.forEach(check);
          part.dependsOn = Object.keys(part.dependsOn);
        });
        
        // Check for recursive dependencies 
        parts.forEach(part => {
          var checked = {};
          function check(partname) {
            if (checked[partname]) {
              return false;
            }
            checked[partname] = true;
            var checkPart = partsByName[partname];
            if (checkPart === part) {
              return true; 
            }
            return part.dependsOn.some(check);
          }
          if (part.dependsOn.some(check)) {
            Console.print("qx.tool.compiler.application.partRecursive", part.name); 
          }
        });
      }
      
      /*
       * Done
       */

      this.__loadDeps = allDeps.toArray();
      
      var requiredLibs = {};
      this.__loadDeps.forEach(classname => {
        let classInfo = db.classInfo[classname];
        if (classInfo.assets) {
          classInfo.assets.forEach(asset => {
            var pos = asset.indexOf('/');
            if (pos > -1) {
              var ns = asset.substring(0, pos);
              if (analyser.findLibrary(ns))
                requiredLibs[ns] = true;
            }
          });
        }
        requiredLibs[classInfo.libraryName] = true;
      });
      this.__requiredLibs = [];
      for (let ns in requiredLibs) {
        if (analyser.findLibrary(ns)) {
          this.__requiredLibs.push(ns);
        } else {
          Console.print("qx.tool.compiler.application.missingRequiredLibrary", ns);
        }
      }
      
      this.__partsDeps = parts;
      this.__fatalCompileErrors = fatalCompileErrors.length ? fatalCompileErrors : null;
    }