/**
   * Sort taskSet based on their impact factors and then merge similar taskSet together
   *
   * @param taskList
   */
  @Override
  public void process(ArrayList<TaskSet> taskList) {

    if (taskList.size() > getClusterNum()) {
      ArrayList<TaskSet> jobList = new ArrayList<TaskSet>();
      for (int i = 0; i < getClusterNum(); i++) {
        jobList.add(new TaskSet());
      }
      int clusters_size = taskList.size() / getClusterNum();
      if (clusters_size * getClusterNum() < taskList.size()) {
        clusters_size++;
      }
      // sortListDecreasing(taskList);
      preprocessing(taskList, jobList);

      for (TaskSet set : taskList) {
        // sortListIncreasing(jobList);
        TaskSet job = getCandidateTastSet(jobList, set, clusters_size);
        addTaskSet2TaskSet(set, job);
        job.addTask(set.getTaskList());
        job.setImpactFafctor(set.getImpactFactor());
        // update dependency
        for (Task task : set.getTaskList()) {
          getTaskMap().put(task, job); // this is enough
          // impact factor is not updated
        }
      }

      taskList.clear(); // you sure?
    } else {
      // do nothing since
    }
  }
  private ArrayList preprocessing(ArrayList<TaskSet> taskList, ArrayList<TaskSet> jobList) {
    int size = taskList.size();
    int[] record = new int[size];
    for (int i = 0; i < size; i++) {
      record[i] = -1;
    }
    int index_record = 0;

    int[][] distances = new int[size][size];

    for (int i = 0; i < size; i++) {
      for (int j = 0; j < i; j++) {
        TaskSet setA = taskList.get(i);
        TaskSet setB = taskList.get(j);
        int distance = calDistance(setA, setB);

        distances[i][j] = distance;
      }
    }
    int job_index = 0;
    // boolean [] popped = new boolean[size];
    ArrayList idList = sortDistanceIncreasing(distances, size, jobList.size());
    for (int i = 0; i < idList.size(); i++) {
      int max_i = (Integer) idList.get(i);

      record[index_record] = max_i;
      index_record++;
      TaskSet set = taskList.get(max_i);
      TaskSet job = jobList.get(job_index);
      addTaskSet2TaskSet(set, job);
      job.addTask(set.getTaskList());
      job.setImpactFafctor(set.getImpactFactor());
      // update dependency
      for (Task task : set.getTaskList()) {
        getTaskMap().put(task, job); // this is enough
        // impact factor is not updated
      }
      job_index++;
      if (job_index == jobList.size()) {
        break;
      }
    }

    /** Actually not really necessary because record[i] is already empty */
    Arrays.sort(record);
    for (int i = size - 1; i >= 0 && record[i] >= 0; i--) {
      taskList.remove(record[i]);
    }

    return taskList;
  }
  @Override
  public void run() {

    if (clusterNum > 0) {
      for (Iterator it = getTaskList().iterator(); it.hasNext(); ) {
        Task task = (Task) it.next();
        TaskSet set = new TaskSet();
        set.addTask(task);
        mTask2TaskSet.put(task, set);
      }
    }

    remove();
    updateTaskSetDependencies();

    printMetrics();
    String code = Parameters.getClusteringParameters().getCode();
    Map<Integer, ArrayList<TaskSet>> map = getCurrentTaskSetAtLevels();
    if (code != null) {
      for (char c : code.toCharArray()) {

        switch (c) {
          case 'v':

            // verticalClustering();
            VerticalBalancing v = new VerticalBalancing(map, this.mTask2TaskSet, this.clusterNum);
            v.run();
            break;
          case 'c':
            // childAwareHorizontalClustering();
            ChildAwareHorizontalClustering ch =
                new ChildAwareHorizontalClustering(map, this.mTask2TaskSet, this.clusterNum);
            ch.run();
            updateTaskSetDependencies();
            break;
          case 'r':
            // horizontalRuntimeBalancing();
            HorizontalRuntimeBalancing r =
                new HorizontalRuntimeBalancing(map, this.mTask2TaskSet, this.clusterNum);
            r.run();
            updateTaskSetDependencies();
            break;
          case 'i':
            HorizontalImpactBalancing i =
                new HorizontalImpactBalancing(map, this.mTask2TaskSet, this.clusterNum);
            i.run();
            break;
          case 'd':
            HorizontalDistanceBalancing d =
                new HorizontalDistanceBalancing(map, this.mTask2TaskSet, this.clusterNum);
            d.run();
            break;
          case 'h':
            HorizontalRandomClustering h =
                new HorizontalRandomClustering(map, this.mTask2TaskSet, this.clusterNum);
            h.run();
            break;
          default:
            break;
        }
      }
      printMetrics();
    }

    printOut();

    Collection sets = mTask2TaskSet.values();
    for (Iterator it = sets.iterator(); it.hasNext(); ) {
      TaskSet set = (TaskSet) it.next();
      if (!set.hasChecked) {
        set.hasChecked = true;
        addTasks2Job(set.getTaskList());
      }
    }
    // a good habit
    cleanTaskSetChecked();

    updateDependencies();
    addClustDelay();

    recover();
  }