/**
   * Executes the normalization procedures defined in "procedures" on the given schema, exits when
   * the targetNf is reached
   *
   * @param schema the relation to normalize
   * @return the results of the Normalization
   */
  private NormalizationResult startNormalizing(
      RelationSchema schema, ArrayList<NormalizationAlgorithm> procedures, NormalForm targetNf) {
    NormalizationResult result = new NormalizationResult();
    NormalizationResult tempResult;

    result.getRelations().add(schema);

    for (NormalizationAlgorithm procedure : procedures) {
      // Quit if NF already reached
      if (checker.getNF(result.getRelations()).ordinal() >= targetNf.ordinal()) {
        break;
      }

      tempResult = (NormalizationResult) result.getClone();
      for (RelationSchema relation : tempResult.getRelations()) {
        procedure.normalize(relation, result, false);
      }
    }

    compactFds(result);

    return result;
  }
  public NormalizationResult normalize(
      RelationSchema relationToNormalize, NormalForm actualNF, NormalForm targetNF) {
    RelationSchema relation = relationToNormalize.getClone();
    RelationSchema backupRelation;

    NormalizationResult result;

    // Pick up lonely Attributes (in no Functional dependency)
    RelationUtils.getInstance().determineAllAttributes(relation);

    // Set primary-key if not present
    if (RelationUtils.getInstance().getPrimaryKey(relation).getAttributes().isEmpty()) {
      Key newPrimaryKey = RelationUtils.getInstance().getKey(relation);
      for (Attribute attribute : relation.getAttributes()) {
        if (newPrimaryKey.getAttributes().contains(attribute)) {
          attribute.setIsPrimaryKey(true);
        }
      }
    }

    backupRelation = relation.getClone();

    normalizationProcedures.clear();
    switch (actualNF) {
      case FIRST:
        switch (targetNF) {
          case SECOND:
            // 1.NF ==> 2.NF
            normalizationProcedures.add(new DecompositionTo2NF());

            break;
          case THIRD:
            // 1.NF ==> 3.NF
            normalizationProcedures.add(new SyntheseTo3NF());
            break;
          case BOYCECODD:
            // 1.NF ==> BCNF
            normalizationProcedures.add(new SyntheseTo3NF());
            normalizationProcedures.add(new DecompositionToBCNF());
            break;
          default:
            throw new IllegalArgumentException();
        }
        break;
      case SECOND:
        switch (targetNF) {
          case THIRD:
            // 2.NF ==> 3.NF
            normalizationProcedures.add(new SyntheseTo3NF());
            break;
          case BOYCECODD:
            // 2.NF ==> BCNF
            normalizationProcedures.add(new SyntheseTo3NF());
            normalizationProcedures.add(new DecompositionToBCNF());
            break;
          default:
            throw new IllegalArgumentException();
        }
        break;
      case THIRD:
        // 3.NF ==> BCNF
        normalizationProcedures.add(new DecompositionToBCNF());
        break;
      default:
        throw new IllegalArgumentException();
    }

    result = startNormalizing(relation, normalizationProcedures, targetNF);

    if (result.getRelations().isEmpty()) {
      result.getRelations().add(backupRelation);
    }

    return result;
  }
 /**
  * Compacts all Fd's of the given Relations
  *
  * @param result the NormalizationResult to work with
  */
 private void compactFds(NormalizationResult result) {
   for (RelationSchema relation : result.getRelations()) {
     uniteFdsWithSameLeftSide(relation);
   }
 }