/**
   * 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<TaskSet> getNextPotentialTaskSets(
      ArrayList<TaskSet> taskList, TaskSet checkSet, int clusters_size) {
    int dis = Integer.MAX_VALUE;

    HashMap map = new HashMap<Integer, ArrayList>();
    for (TaskSet set : taskList) {
      int distance = calDistance(checkSet, set);
      if (distance < dis) {
        dis = distance;
      }
      if (!map.containsKey(distance)) {
        map.put(distance, new ArrayList<TaskSet>());
      }
      ArrayList<TaskSet> list = (ArrayList) map.get(distance);
      if (!list.contains(set)) {
        list.add(set);
      }
    }
    ArrayList returnList = new ArrayList<TaskSet>();
    for (TaskSet set : (ArrayList<TaskSet>) map.get(dis)) {
      if (set.getTaskList().size() < clusters_size) {
        returnList.add(set);
      }
    }

    if (returnList.isEmpty()) {
      returnList.clear();
      for (TaskSet set : taskList) {
        if (set.getTaskList().isEmpty()) {
          returnList.add(set);
          return returnList;
        }
      }

      // no empty available
      while (returnList.isEmpty()) {
        map.remove(dis);
        ArrayList<Integer> keys = new ArrayList(map.keySet());
        int min = Integer.MAX_VALUE;
        for (int i : keys) {
          if (min > i) {
            min = i;
          }
        }
        dis = min;

        for (TaskSet set : (ArrayList<TaskSet>) map.get(dis)) {
          if (set.getTaskList().size() < clusters_size) {
            returnList.add(set);
          }
        }
      }

      return returnList;

    } else {

      return returnList;
    }
  }
  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;
  }
  /** Update task set dependencies */
  private void updateTaskSetDependencies() {

    Collection sets = mTask2TaskSet.values();
    for (Iterator it = sets.iterator(); it.hasNext(); ) {
      TaskSet set = (TaskSet) it.next();
      if (!set.hasChecked) {
        set.hasChecked = true;
        set.getChildList().clear();
        set.getParentList().clear();
        for (Task task : set.getTaskList()) {
          for (Iterator tIt = task.getParentList().iterator(); tIt.hasNext(); ) {
            Task parent = (Task) tIt.next();
            TaskSet parentSet = (TaskSet) mTask2TaskSet.get(parent);
            if (!set.getParentList().contains(parentSet) && set != parentSet) {
              set.getParentList().add(parentSet);
            }
          }
          for (Iterator tIt = task.getChildList().iterator(); tIt.hasNext(); ) {
            Task child = (Task) tIt.next();
            TaskSet childSet = (TaskSet) mTask2TaskSet.get(child);
            if (!set.getChildList().contains(childSet) && set != childSet) {
              set.getChildList().add(childSet);
            }
          }
        }
      }
    }
    // within each method
    cleanTaskSetChecked();
  }
  /**
   * Calculate the distance between two taskSet one assumption here taskA and taskB are at the same
   * level because it is horizontal clustering does not work with arbitary workflows
   *
   * @param taskA
   * @param taskB
   * @return
   */
  private int calDistance(TaskSet taskA, TaskSet taskB) {
    if (taskA == null || taskB == null || taskA == taskB) {
      return 0;
    }
    LinkedList<TaskSet> listA = new LinkedList<TaskSet>();
    LinkedList<TaskSet> listB = new LinkedList<TaskSet>();
    int distance = 0;
    listA.add(taskA);
    listB.add(taskB);

    if (taskA.getTaskList().isEmpty() || taskB.getTaskList().isEmpty()) {
      return Integer.MAX_VALUE;
    }
    do {

      LinkedList<TaskSet> copyA = (LinkedList) listA.clone();
      listA.clear();
      for (TaskSet set : copyA) {
        for (TaskSet child : set.getChildList()) {
          if (!listA.contains(child)) {
            listA.add(child);
          }
        }
      }
      LinkedList<TaskSet> copyB = (LinkedList) listB.clone();
      listB.clear();
      for (TaskSet set : copyB) {
        for (TaskSet child : set.getChildList()) {
          if (!listB.contains(child)) {
            listB.add(child);
          }
        }
      }

      for (TaskSet set : listA) {
        if (listB.contains(set)) {
          return distance * 2;
        }
      }

      distance++;

    } while (!listA.isEmpty() && !listB.isEmpty());

    return distance * 2;
  }
  /** Print out the clustering information. */
  private void printOut() {
    Collection sets = mTask2TaskSet.values();
    for (Iterator it = sets.iterator(); it.hasNext(); ) {
      TaskSet set = (TaskSet) it.next();
      if (!set.hasChecked) {
        set.hasChecked = true;

        Log.printLine("Job");
        for (Task task : set.getTaskList()) {
          Log.printLine(
              "Task "
                  + task.getCloudletId()
                  + " "
                  + task.getImpact()
                  + " "
                  + task.getCloudletLength());
        }
      }
    }
    // within each method
    cleanTaskSetChecked();
  }
  @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();
  }