/**
   * Checks if, for a given node, it exist an <b>outgoing</b> with that label and returns all the
   * graphEdges found
   *
   * @param label the label we are looking for
   * @param graphEdges the knoweldgebase graphEdges
   * @return labeled graphEdges, can be empty
   */
  public static List<Edge> findEdges(Long label, Collection<Edge> graphEdges) {

    // Compare to the graphEdges in the KB exiting from the mapped node passed
    List<Edge> edges = new ArrayList<>();

    for (Edge Edge : graphEdges) {
      if (label == Edge.GENERIC_EDGE_LABEL || Edge.getLabel().longValue() == label || label == 0L) {
        edges.add(Edge);
      }
    }
    return edges;
  }
  /**
   * @param queryEdge
   * @param graphEdge
   * @param r
   * @return
   */
  protected boolean edgeMatch(Edge queryEdge, Edge graphEdge, IsomorphicQuery r) {

    if (queryEdge.getLabel() != 0L && queryEdge.getLabel() != graphEdge.getLabel().longValue()) {
      return false;
    }

    Long querySource = null;
    Long queryDestination = null;
    Long graphSource = null;
    Long graphDestination = null;

    if (r != null) {
      if (r.isUsing(graphEdge)) {
        return false;
      }

      querySource = queryEdge.getSource();
      graphSource = graphEdge.getSource();

      boolean mappedSource = r.hasMapped(querySource);
      boolean usingSource = r.isUsing(graphSource);

      if (usingSource && !mappedSource) {
        return false;
      }

      queryDestination = queryEdge.getDestination();
      graphDestination = graphEdge.getDestination();

      boolean mappedDestination = r.hasMapped(queryDestination);
      boolean usingDestination = r.isUsing(graphDestination);
      if (usingDestination && !mappedDestination) {
        return false;
      }

      if (mappedSource && !graphSource.equals(r.isomorphicMapOf(querySource).getNodeID())) {
        return false;
      }

      if (mappedDestination
          && !graphDestination.equals(r.isomorphicMapOf(queryDestination).getNodeID())) {
        return false;
      }

      if (usingSource && !r.mappedAs(graphSource).equals(querySource)) {
        return false;
      }

      if (usingDestination && !r.mappedAs(graphDestination).equals(queryDestination)) {
        return false;
      }
    }
    return true;
  }
  /**
   * Given a query, a starting node from the query, and a node from the knowledgeBase , tries to
   * build up a related query
   *
   * @param query
   * @param queryNode
   * @param graphNode
   * @return
   */
  public List<IsomorphicQuery> createQueries(
      Multigraph query, Long queryNode, MappedNode graphNode, IsomorphicQuery relatedQuery) {
    // Initialize the queries set
    // Given the current situation we expect to build more than one possible related query
    List<IsomorphicQuery> relatedQueries = new ArrayList<>();
    relatedQueries.add(relatedQuery);

    // The graphEdges exiting from the query node passed
    Collection<Edge> queryEdgesOut = query.outgoingEdgesOf(queryNode);
    // The graphEdges entering the query node passed
    Collection<Edge> queryEdgesIn = query.incomingEdgesOf(queryNode);

    // The graphEdges in the KB exiting from the mapped node passed
    Collection<Edge> graphEdgesOut = graph.outgoingEdgesOf(graphNode.getNodeID());
    // The graphEdges in the KB entering the mapped node passed
    Collection<Edge> graphEdgesIn = graph.incomingEdgesOf(graphNode.getNodeID());
    //        System.out.println(graphNode.getNodeID() + " " + (graphEdgesOut.size() +
    // graphEdgesIn.size()));

    // Null handling
    queryEdgesIn = queryEdgesIn == null ? new HashSet<Edge>() : queryEdgesIn;
    queryEdgesOut = queryEdgesOut == null ? new HashSet<Edge>() : queryEdgesOut;
    graphEdgesIn = graphEdgesIn == null ? new HashSet<Edge>() : graphEdgesIn;
    graphEdgesOut = graphEdgesOut == null ? new HashSet<Edge>() : graphEdgesOut;

    // debug("TEst %d map to  %d", queryNode, graphNode);

    // Optimization: if the queryEdges are more than the kbEdges, we are done, not isomorphic!
    if (queryEdgesIn.size() > graphEdgesIn.size() || queryEdgesOut.size() > graphEdgesOut.size()) {
      return null;
    }

    // All non mapped graphEdges from the query are put in one set
    Set<Edge> queryEdges = new HashSet<>();

    for (Edge edgeOut : queryEdgesOut) {
      if (!relatedQuery.hasMapped(edgeOut)) {
        queryEdges.add(edgeOut);
      }
    }

    for (Edge edgeIn : queryEdgesIn) {
      if (!relatedQuery.hasMapped(edgeIn)) {
        queryEdges.add(edgeIn);
      }
    }

    queryEdgesIn = null;
    queryEdgesOut = null;
    List<Edge> sortedEdges = sortEdge(queryEdges, this.labelFreq);
    // Look if we can map all the outgoing/ingoing graphEdges of the query node
    for (Edge queryEdge : sortedEdges) {
      //        	System.out.println("Processs answer number: " + relatedQueries.size());
      if (relatedQueries.size() > MAX_RELATED) return relatedQueries;
      //        	System.out.println(queryEdge);
      //            info("Trying to map the edge " + queryEdge);
      List<IsomorphicQuery> newRelatedQueries = new ArrayList<>();
      LinkedList<IsomorphicQuery> toTestRelatedQueries = new LinkedList<>();

      for (IsomorphicQuery current : relatedQueries) {
        if (current.hasMapped(queryEdge)) {
          newRelatedQueries.add(current);
        } else {
          toTestRelatedQueries.add(current);
        }
      }
      relatedQueries.clear();
      IsomorphicQuerySearch.interNum =
          Math.max(toTestRelatedQueries.size(), IsomorphicQuerySearch.interNum);
      // reset, we do not want too many duplicates
      relatedQueries = new LinkedList<>();

      // If all candidated have this QueryEdge mapped, go to next
      if (toTestRelatedQueries.isEmpty()) {
        relatedQueries = newRelatedQueries;
        continue;
      }

      // The label we are looking for
      Long label = queryEdge.getLabel();

      // is it isIncoming or outgoing ?
      boolean isIncoming = queryEdge.getDestination().equals(queryNode);

      List<Edge> graphEdges;
      // Look for graphEdges with the same label and same direction as the one from the query
      if (isIncoming) {
        graphEdges = findEdges(label, graphEdgesIn);
      } else {
        graphEdges = findEdges(label, graphEdgesOut);
      }

      // loggable.debug("Matching with %d graphEdges", graphEdges.size() );
      // Do we found any?
      if (graphEdges.isEmpty()) {
        // If we cannot map graphEdges, this path is wrong
        return null;
      } else {
        // Cycle through all the possible graphEdges options,
        // they would be possibly different related queries
        for (Edge graphEdge : graphEdges) {

          // Cycle through all the possible related queries retrieved up to now
          // A new related query is good if it finds a match
          for (IsomorphicQuery tempRelatedQuery : toTestRelatedQueries) {
            if (newRelatedQueries.size() > MAX_RELATED
                || watch.getElapsedTimeMillis() > QUIT_TIME) {
              //                    		System.out.println("Time limit exceeded or more than 10000
              // partial results");
              this.isQuit = true;
              return relatedQueries.size() > 0 ? relatedQueries : null;
            }
            if (tempRelatedQuery.isUsing(graphEdge)) {
              // Ok this option is already using this edge,
              // not a good choice go away
              // it means that this query didn't found his match in this edge
              continue;
            }
            Utilities.searchCount++;
            // Otherwise this edge can be mapped to the query edge if all goes well
            IsomorphicQuery newRelatedQuery = tempRelatedQuery.getClone();

            // check nodes similarity
            // double nodeSimilarity = 0;
            // if (isIncoming) {
            //    nodeSimilarity = RelatedQuerySearch.conceptSimilarity(queryEdge.getSource(),
            // graphEdge.getSource());
            // } else {
            //    nodeSimilarity = RelatedQuerySearch.conceptSimilarity(queryEdge.getDestination(),
            // graphEdge.getDestination());
            // }
            // If the found edge peudo-destination is similar to the query edge pseudo-destination
            // if (nodeSimilarity > RelatedQuerySearch.MIN_SIMILARITY) {
            // The destination if outgoing the source if isIncoming
            Long queryNextNode;
            MappedNode graphNextNode;
            if (isIncoming) {
              queryNextNode = queryEdge.getSource();
              //                            graphNextNode = graphEdge.getSource();
              graphNextNode =
                  new MappedNode(graphEdge.getSource(), graphEdge, 0, isIncoming, false);
            } else {
              queryNextNode = queryEdge.getDestination();
              //                            graphNextNode = graphEdge.getDestination();
              graphNextNode =
                  new MappedNode(graphEdge.getDestination(), graphEdge, 0, isIncoming, false);
            }

            // Is this node coeherent with the structure?
            if (edgeMatch(queryEdge, graphEdge, newRelatedQuery)) {
              // That's a good edge!! Add it to this related query
              newRelatedQuery.map(queryEdge, graphEdge);

              // Map also the node
              newRelatedQuery.map(queryNextNode, graphNextNode);

              // The query node that we are going to map
              // Does it have graphEdges that we don't have mapped?
              boolean needExpansion = false;
              Collection<Edge> pseudoOutgoingEdges = query.incomingEdgesOf(queryNextNode);
              Long queryPrevNode = null;
              if (pseudoOutgoingEdges.size() > 0) {
                for (Edge pseudoEdge : pseudoOutgoingEdges) {
                  needExpansion =
                      !newRelatedQuery.hasMapped(pseudoEdge) && !pseudoEdge.equals(queryEdge);
                  queryPrevNode =
                      pseudoEdge.getDestination().equals(queryNextNode)
                          ? pseudoEdge.getSource()
                          : pseudoEdge.getDestination();
                  needExpansion = needExpansion && !queryPrevNode.equals(queryNode);
                  if (needExpansion) {
                    break;
                  }
                }
              }

              pseudoOutgoingEdges = query.outgoingEdgesOf(queryNextNode);
              if (!needExpansion && pseudoOutgoingEdges.size() > 0) {
                for (Edge pseudoEdge : pseudoOutgoingEdges) {
                  needExpansion =
                      !newRelatedQuery.hasMapped(pseudoEdge) && !pseudoEdge.equals(queryEdge);
                  queryPrevNode =
                      pseudoEdge.getDestination().equals(queryNextNode)
                          ? pseudoEdge.getSource()
                          : pseudoEdge.getDestination();
                  needExpansion = needExpansion && !queryPrevNode.equals(queryNode);
                  if (needExpansion) {
                    break;
                  }
                }
              }

              // Lookout! We need to check the outgoing part, if we did not already
              if (needExpansion) {
                // Possible outgoing branches
                List<IsomorphicQuery> tmpRelatedQueries;
                // Go find them!
                // log("Go find mapping for: " + queryNextNode + " // " + graphNextNode);
                tmpRelatedQueries =
                    createQueries(query, queryNextNode, graphNextNode, newRelatedQuery);
                // Did we find any?
                if (tmpRelatedQueries != null) {
                  // Ok so we found some, they are all good to me
                  // More possible related queries
                  // They already contain the root
                  for (IsomorphicQuery branch : tmpRelatedQueries) {
                    // All these related queries have found in this edge their match
                    newRelatedQueries.add(branch);
                  }
                }
                // else {
                // This query didn't find in this edge its match
                // continue;
                // }
              } else {
                // log("Complete query " + relatedQuery);
                // this related query has found in this edge is map
                // newRelatedQuery.map(queryNextNode, graphNextNode);
                newRelatedQueries.add(newRelatedQuery);
              }
            }
            // else {
            // info("Edge does not match  %s   -  for %s  : %d", graphEdge.getId(),
            // FreebaseConstants.convertLongToMid(graphNode), graphNode);
            // }
          }
        }
      }
      // after this cycle we should have found some, how do we check?
      if (newRelatedQueries.isEmpty()) {
        return null;
      } else {
        // basically in the *new* list are the related queries still valid and growing
        relatedQueries = newRelatedQueries;
      }
    }
    return relatedQueries.size() > 0 ? relatedQueries : null;
  }