/**
   * Returns the most similar {@link Term} for the term represented by the specified sdr
   *
   * @param sdr sparse int array
   * @return
   */
  Term getClosestTerm(int[] sdr) {
    Fingerprint fp = new Fingerprint(sdr);
    try {
      List<Term> terms = getSimilarTerms(fp);

      // Retrieve terms from cache if present
      for (int i = 0; i < terms.size(); i++) {
        if (cache.containsKey(terms.get(i).getTerm())) {
          terms.set(i, cache.get(terms.get(i).getTerm()));
        }
      }

      Term retVal = null;
      if (terms != null && terms.size() > 0) {
        retVal = terms.get(0);
        if (checkTerm(retVal.getTerm(), retVal, true)) {
          return retVal;
        }

        // Cache fall through incomplete term for next time
        cache.put(retVal.getTerm(), retVal = getTerms(retVal.getTerm(), true).get(0));
        return retVal;
      }
    } catch (Exception e) {
      LOGGER.debug("Problem using Expressions API");
      e.printStackTrace();
    }

    return null;
  }
  /**
   * Feeds the {@link Network with the final phrase consisting of the first
   * two words of the final question "fox, eats, ...".
   *
   * @param network   the current {@link Network} object
   * @param it        an {@link Iterator} over the input source file lines
   * @return
   */
  Term feedQuestion(Network network, String[] phrase) {
    for (int i = 0; i < 2; i++) {
      int[] sdr = getFingerprintSDR(phrase[i]);
      network.compute(sdr);
    }

    Layer<?> layer = network.lookup("Region 1").lookup("Layer 2/3");
    Set<Cell> predictiveCells = layer.getPredictiveCells();
    int[] prediction =
        SDR.cellsAsColumnIndices(predictiveCells, layer.getConnections().getCellsPerColumn());
    Term term = getClosestTerm(prediction);
    cache.put(term.getTerm(), term);

    return term;
  }
  /**
   * Called by {@link #checkCache(boolean, boolean)} for every line in the cache file to validate
   * the contents of the specified {@link Term}
   *
   * @param key
   * @param t
   * @param print
   * @return
   */
  boolean checkTerm(String key, Term t, boolean print) {
    Fingerprint fp = t.getFingerprint();
    if (fp == null) {
      if (print) {
        LOGGER.debug("\tkey: " + key + ", missing fingerprint");
      }
      return false;
    }

    int[] pos = fp.getPositions();
    if (pos == null) {
      if (print) {
        LOGGER.debug("\tkey: " + key + ", has null positions");
      }
      return false;
    }

    if (pos.length < 1) {
      if (print) {
        LOGGER.debug("\tkey: " + key + ", had empty positions");
      }
      return false;
    }

    int sdrLen = pos.length;
    if (print) {
      LOGGER.debug("\tkey: " + key + ", term len: " + sdrLen);
    }

    return true;
  }
  /**
   * Returns a {@link Fingerprint} for the specified term.
   *
   * @param term
   * @return
   */
  Fingerprint getFingerprint(String term) {
    try {
      Term t = cache.get(term) == null ? getTerms(term, true).get(0) : cache.get(term);

      if (!checkTerm(t.getTerm(), t, true)) {
        throw new IllegalStateException("Checkterm failed: " + t.getTerm());
      }

      cache.put(t.getTerm(), t);

      return t.getFingerprint();
    } catch (Exception e) {
      LOGGER.debug("Problem retrieving fingerprint for term: " + term);
    }

    return null;
  }
  /**
   * Loads the fingerprint cache file into memory if it exists. If it does not exist, this method
   * creates the file; however it won't be written to until the demo is finished processing, at
   * which point {@link #writeCache()} is called to store the cache file.
   */
  void loadCache() {
    if (cache == null) {
      cache = new HashMap<>();
    }

    String json = null;
    try {
      StringBuilder sb = new StringBuilder();
      getCacheStream()
          .forEach(
              l -> {
                sb.append(l);
              });
      json = sb.toString();
    } catch (IOException e) {
      e.printStackTrace();
    }

    if (json.isEmpty()) {
      LOGGER.debug("Term cache is empty.");
      return;
    }

    ObjectMapper mapper = new ObjectMapper();
    List<Term> terms = null;
    try {
      terms = Arrays.asList(mapper.readValue(json, Term[].class));
      if (terms == null) {
        LOGGER.debug("Term cache is empty or malformed.");
      }
    } catch (IOException e) {
      e.printStackTrace();
    }

    for (Term t : terms) {
      cache.put(t.getTerm(), t);
    }

    checkCache(true, true);
  }
  /**
   * Writes the fingerprint cache file to disk. This takes place at the end of the demo's
   * processing.
   */
  void writeCache() {
    File f = getCacheFile();

    try (PrintWriter pw = new PrintWriter(new FileWriter(f))) {
      StringBuilder builderStr = new StringBuilder();
      int i = 0;
      for (Iterator<Term> it = cache.values().iterator(); it.hasNext(); i++) {
        Term t = it.next();
        String termStr = Term.toJson(t);
        if (i > 0) {
          termStr = termStr.substring(1).trim();
        }
        termStr = termStr.substring(0, termStr.length() - 1).trim();
        builderStr.append(termStr).append(",");
      }
      builderStr.setLength(builderStr.length() - 1);
      builderStr.append(" ]");

      pw.println(builderStr.toString());
      pw.flush();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }