/**
   * Returns a list of scores that denotes how related a source concept is to a list of individual
   * concepts.
   *
   * @param parameters The parameters to be used in the service call, account_id, graph and concept
   *     or concepts are required.
   *     <ul>
   *       <li>String account_id - The account identifier.<br>
   *       <li>String graph - The graph name.<br>
   *       <li>String concept - The concept name.<br>
   *       <li>List&lt;String&gt; concepts - Array of concept IDs, each identifying a concept.<br>
   *     </ul>
   *
   * @return {@link Scores}
   */
  public Scores getGraphsRelationScores(Map<String, Object> parameters) {
    Validate.notNull(parameters.get(ACCOUNT_ID), "account_id can't be null");
    Validate.notNull(parameters.get(GRAPH), "graph can't be null");
    Validate.notNull(parameters.get(CONCEPT), "concept can't be null");
    Validate.notNull(parameters.get(CONCEPTS), "concepts can't be null");

    String conceptId =
        createConceptIdPath(
            (String) parameters.get(ACCOUNT_ID),
            (String) parameters.get(GRAPH),
            (String) parameters.get(CONCEPT));

    Map<String, Object> queryParameters = new HashMap<String, Object>();
    JsonObject contentJson = new JsonObject();
    JsonArray conceptsJson = new JsonArray();
    @SuppressWarnings("unchecked")
    List<String> concepts = (List<String>) parameters.get(CONCEPTS);
    for (String value : concepts) {
      conceptsJson.add(new JsonPrimitive(value));
    }
    contentJson.add(CONCEPTS, conceptsJson);
    queryParameters.put(CONCEPTS, conceptsJson.toString());

    return executeRequest(conceptId + RELATION_SCORES_PATH, queryParameters, Scores.class);
  }
  /**
   * Searches for documents and concepts by using partial matches on the label(s) fields.
   *
   * @param parameters The parameters to be used in the service call, account_id, corpus and query
   *     are required.
   *     <ul>
   *       <li>String account_id - The account identifier.<br>
   *       <li>String corpus - The corpus name.<br>
   *       <li>RequestedFields concept_fields - Additional fields to be included in the concept
   *           objects.<br>
   *       <li>RequestedFields document_fields - Additional fields to be included in the document
   *           objects.<br>
   *       <li>Boolean concepts - Whether to return concepts that have a label match.<br>
   *       <li>String query - The query string.<br>
   *       <li>Boolean prefix - Whether the query string should be treated as a prefix.<br>
   *       <li>Integer limit - The maximum number of concepts to be returned.<br>
   *     </ul>
   *
   * @return {@link Matches}
   */
  public Matches searchCorpusByLabel(Map<String, Object> parameters) {
    Validate.notNull(parameters.get(ACCOUNT_ID), "account_id can't be null");
    Validate.notNull(parameters.get(CORPUS), "corpus can't be null");
    Validate.notNull(parameters.get(QUERY), "query can't be null");

    String corpusId =
        createCorpusIdPath((String) parameters.get(ACCOUNT_ID), (String) parameters.get(CORPUS));

    Map<String, Object> queryParameters = new HashMap<String, Object>();
    String[] queryParams = new String[] {QUERY, PREFIX, LIMIT, CONCEPTS};
    for (String param : queryParams) {
      if (parameters.containsKey(param)) queryParameters.put(param, parameters.get(param));
    }

    if (parameters.get(CONCEPT_FIELDS) != null) {
      RequestedFields fields = (RequestedFields) parameters.get(CONCEPT_FIELDS);
      if (fields != null && fields.getFields() != null && !fields.getFields().isEmpty())
        queryParameters.put(CONCEPT_FIELDS, fields.toString());
    }

    if (parameters.get(DOCUMENT_FIELDS) != null) {
      RequestedFields fields = (RequestedFields) parameters.get(DOCUMENT_FIELDS);
      if (fields != null && fields.getFields() != null && !fields.getFields().isEmpty())
        queryParameters.put(DOCUMENT_FIELDS, fields.toString());
    }
    return executeRequest(corpusId + LABEL_SEARCH_PATH, queryParameters, Matches.class);
  }
  /**
   * Retrieves conceptual view of document (including annotations).
   *
   * @param accountId String the account identifier,
   * @param corpusName String the corpus name,
   * @param documentName String the document name.
   * @return {@link DocumentAnnotations}
   */
  public DocumentAnnotations getDocumentAnnotations(
      final String accountId, final String corpusName, final String documentName) {
    Validate.notNull(accountId, "accountId can't be null");
    Validate.notNull(corpusName, "corpusName can't be null");
    Validate.notNull(documentName, "documentName can't be null");

    String documentId = createDocumentIdPath(accountId, corpusName, documentName);
    return executeRequest(documentId + ANNOTATIONS_PATH, null, DocumentAnnotations.class);
  }
  /**
   * Retrieves a document from a corpus.
   *
   * @param accountId String the account identifier,
   * @param corpusName String the corpus name.
   * @param documentName String the document name.
   * @return {@link Document}
   */
  public Document getDocument(
      final String accountId, final String corpusName, final String documentName) {
    Validate.notNull(accountId, "accountId can't be null");
    Validate.notNull(corpusName, "corpusName can't be null");
    Validate.notNull(documentName, "documentName can't be null");

    return executeRequest(
        createDocumentIdPath(accountId, corpusName, documentName), null, Document.class);
  }
 /**
  * Gets processing state of a Corpus.
  *
  * @param accountId String the account identifier,
  * @param corpusName String the corpus name.
  * @return {@link CorpusProcessingState} The processing state of a given corpus.
  */
 public CorpusProcessingState getCorpusProcessingState(
     final String accountId, final String corpusName) {
   Validate.notNull(accountId, "accountId can't be null");
   Validate.notNull(corpusName, "corpusName can't be null");
   return executeRequest(
       createCorpusIdPath(accountId, corpusName) + PROCESSING_STATE_PATH,
       null,
       CorpusProcessingState.class);
 }
 /**
  * Deletes a document in a given corpus.
  *
  * @param accountId String the account identifier,
  * @param corpusName String the corpus name.
  * @param documentName String the document name.
  */
 public void deleteDocument(
     final String accountId, final String corpusName, final String documentName) {
   Validate.notNull(accountId, "accountId can't be null");
   Validate.notNull(corpusName, "corpusName can't be null");
   Validate.notNull(documentName, "documentName can't be null");
   HttpRequestBase request =
       Request.Delete(createDocumentIdPath(accountId, corpusName, documentName)).build();
   executeWithoutResponse(request);
 }
  /**
   * Retrieves processing state of document.
   *
   * @param accountId String the account identifier,
   * @param corpusName String the corpus name,
   * @param documentName String the document name.
   * @return {@link DocumentProcessingStatus}
   */
  public DocumentProcessingStatus getDocumentProcessingState(
      final String accountId, final String corpusName, final String documentName) {
    Validate.notNull(accountId, "accountId can't be null");
    Validate.notNull(corpusName, "corpusName can't be null");
    Validate.notNull(documentName, "documentName can't be null");

    String documentId = createDocumentIdPath(accountId, corpusName, documentName);
    return executeRequest(documentId + PROCESSING_STATE_PATH, null, DocumentProcessingStatus.class);
  }
  /**
   * Updates existing corpus meta-data (access and permissions).
   *
   * @param accountId String the Account identifier.
   * @param corpus {@link Corpus} the corpus to update.
   */
  public void updateCorpus(final String accountId, final Corpus corpus) {
    Validate.notNull(accountId, "account_id can't be null");
    Validate.notNull(corpus, "corpus can't be null");
    Validate.notNull(corpus.getId(), "corpus.id can't be null");

    HttpRequestBase request =
        Request.Post(createCorpusIdPath(accountId, corpus.getId()))
            .withContent(GsonSingleton.getGson().toJson(corpus), MediaType.APPLICATION_JSON)
            .build();
    executeWithoutResponse(request);
  }
 /**
  * Returns information for a specific concept node in a graph.
  *
  * @param parameters The parameters to be used in the service call, account_id, graph and concept
  *     are required.
  *     <ul>
  *       <li>String account_id - The account identifier.<br>
  *       <li>String graph - The graph name.<br>
  *       <li>String concept - The concept name.<br>
  *     </ul>
  *
  * @return {@link ConceptMetadata}
  */
 public ConceptMetadata getConcept(Map<String, Object> parameters) {
   Validate.notNull(parameters.get(ACCOUNT_ID), "account_id can't be null");
   Validate.notNull(parameters.get(GRAPH), "graph can't be null");
   Validate.notNull(parameters.get(CONCEPT), "concept can't be null");
   String conceptId =
       createConceptIdPath(
           (String) parameters.get(ACCOUNT_ID),
           (String) parameters.get(GRAPH),
           (String) parameters.get(CONCEPT));
   return executeRequest(conceptId, null, ConceptMetadata.class);
 }
  /**
   * Updates a document in a given corpus.
   *
   * @param accountId String the account identifier,
   * @param corpusName String the corpus name.
   * @param document {@link Document} The document to update.
   */
  public void updateDocument(
      final String accountId, final String corpusName, final Document document) {
    Validate.notNull(accountId, "accountId can't be null");
    Validate.notNull(corpusName, "corpusName can't be null");
    Validate.notNull(document, "document can't be null");
    Validate.notNull(document.getId(), "document.id can't be null");

    HttpRequestBase request =
        Request.Post(createDocumentIdPath(accountId, corpusName, document.getId()))
            .withContent(GsonSingleton.getGson().toJson(document), MediaType.APPLICATION_JSON)
            .build();
    executeWithoutResponse(request);
  }
  /**
   * Retrieves concepts that are related to an entire corpus.
   *
   * @param parameters The parameters to be used in the service call, account_id, corpus and
   *     concepts are required.
   *     <ul>
   *       <li>String account_id - The account identifier.<br>
   *       <li>String corpus - The corpus name.<br>
   *       <li>RequestedFields concept_fields - Additional fields to be included in the concept
   *           objects.<br>
   *       <li>Integer level - A number in the range 0 - 3 that represents the level of popularity
   *           of related concepts.<br>
   *       <li>Integer limit - The maximum number of concepts to be returned.<br>
   *     </ul>
   *
   * @return {@link Concepts}
   */
  public Concepts getCorpusRelatedConcepts(Map<String, Object> parameters) {
    Validate.notNull(parameters.get(ACCOUNT_ID), "account_id can't be null");
    Validate.notNull(parameters.get(CORPUS), "corpus can't be null");

    String corpusId =
        createCorpusIdPath((String) parameters.get(ACCOUNT_ID), (String) parameters.get(CORPUS));

    Map<String, Object> queryParameters = new HashMap<String, Object>();
    String[] params = new String[] {LEVEL, LIMIT};
    for (String param : params) {
      if (parameters.containsKey(param)) queryParameters.put(param, parameters.get(param));
    }
    if (parameters.get(CONCEPT_FIELDS) != null) {
      RequestedFields fields = (RequestedFields) parameters.get(CONCEPT_FIELDS);
      if (fields != null && fields.getFields() != null && !fields.getFields().isEmpty())
        queryParameters.put(CONCEPT_FIELDS, fields.toString());
    }
    return executeRequest(corpusId + RELATED_CONCEPTS_PATH, queryParameters, Concepts.class);
  }
  /**
   * Identifies concepts in a piece of text.
   *
   * @param parameters The parameters to be used in the service call, account_id, graph and text are
   *     required.
   *     <ul>
   *       <li>String account_id - The account identifier.<br>
   *       <li>String graph - The graph name.<br>
   *       <li>String text - The text to annotate.<br>
   *     </ul>
   *
   * @return {@link Annotations}
   */
  public Annotations annotateText(Map<String, Object> parameters) {
    Validate.notNull(parameters.get(ACCOUNT_ID), "account_id can't be null");
    Validate.notNull(parameters.get(GRAPH), "graph can't be null");
    Validate.notNull(parameters.get(TEXT), "text can't be null");
    String graphId =
        createGraphIdPath((String) parameters.get(ACCOUNT_ID), (String) parameters.get(GRAPH));

    HttpRequestBase request =
        Request.Post(graphId + ANNOTATE_TEXT_PATH)
            .withContent((String) parameters.get(TEXT), MediaType.TEXT_PLAIN)
            .withHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
            .build();

    try {
      HttpResponse response = execute(request);
      Annotations annotations =
          GsonSingleton.getGson().fromJson(ResponseUtil.getString(response), Annotations.class);
      return annotations;
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }
  /**
   * Searches for graph concepts by using partial matches.<br>
   *
   * @param parameters The parameters to be used in the service call, account_id, graph and query
   *     are required.
   *     <ul>
   *       <li>String account_id - The account identifier.<br>
   *       <li>String graph - The graph name.<br>
   *       <li>String query - The query string.<br>
   *       <li>Boolean prefix - Whether the query string should be treated as a prefix.<br>
   *       <li>Integer limit - The maximum number of items to be returned.<br>
   *       <li>RequestedFields concept_fields - An additional fields to include in the concept
   *           objects.<br>
   *     </ul>
   *
   * @return {@link Matches}
   */
  public Matches searchGraphsConceptByLabel(Map<String, Object> parameters) {
    Validate.notNull(parameters.get(ACCOUNT_ID), "account_id can't be null");
    Validate.notNull(parameters.get(GRAPH), "graph can't be null");
    Validate.notNull(parameters.get(QUERY), "query can't be null");

    String graph_id =
        createGraphIdPath((String) parameters.get(ACCOUNT_ID), (String) parameters.get(GRAPH));

    Map<String, Object> queryParameters = new HashMap<String, Object>();
    String[] params = new String[] {QUERY, PREFIX, LIMIT};
    for (String param : params) {
      if (parameters.containsKey(param)) {
        queryParameters.put(param, parameters.get(param));
      }
    }
    if (parameters.get(CONCEPT_FIELDS) != null) {
      RequestedFields fields = (RequestedFields) parameters.get(CONCEPT_FIELDS);
      if (fields != null && fields.getFields() != null && !fields.getFields().isEmpty())
        queryParameters.put(CONCEPT_FIELDS, fields.toString());
    }
    return executeRequest(graph_id + LABEL_SEARCH_PATH, queryParameters, Matches.class);
  }
  /**
   * Retrieves the document ids of a corpus.
   *
   * @param parameters The parameters to be used in the service call, account_id, and corpus are
   *     required.
   *     <ul>
   *       <li>String account_id - The account identifier.<br>
   *       <li>String corpus - The corpus name.<br>
   *       <li>String concept - The concept name.<br>
   *       <li>String query - For query syntax see <a
   *           href="http://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/concept_insights.html">API
   *           Explorer</a>.<br>
   *           JSON object that allows to filter the list of documents. Valid values are
   *           {"status":"error"}, {"status":"processing"}, and {"status":"ready"} which allow to
   *           filter documents by status.<br>
   *       <li>Integer cursor - The number of possible items to return. Specify '0' to return the
   *           maximum value of 100,000...<br>
   *       <li>Integer limit - The number of possible concepts to return..<br>
   *     </ul>
   *
   * @return {@link Documents}
   */
  public Documents listDocuments(Map<String, Object> parameters) {
    Validate.notNull(parameters.get(ACCOUNT_ID), "account_id can't be null");
    Validate.notNull(parameters.get(CORPUS), "corpus can't be null");
    Map<String, Object> queryParameters = new HashMap<String, Object>();
    String[] queryParams = new String[] {CURSOR, LIMIT};
    for (String param : queryParams) {
      if (parameters.containsKey(param)) {
        queryParameters.put(param, parameters.get(param));
      }
    }
    if (parameters.get(QUERY) != null) {
      // TODO we may need to work in the query format,for now we do expect
      // the query parameter String formatted as documented in Concept Insights.
      queryParameters.put(QUERY, parameters.get(QUERY));
    }

    return executeRequest(
        createCorpusIdPath((String) parameters.get(ACCOUNT_ID), (String) parameters.get(CORPUS))
            + DOCUMENTS,
        queryParameters,
        Documents.class);
  }
  /**
   * Searches for graph concepts by using partial matches.
   *
   * @param parameters The parameters to be used in the service call, account_id, graph are
   *     required.
   *     <ul>
   *       <li>String account_id - The account identifier.<br>
   *       <li>String graph - The graph name.<br>
   *       <li>List&lt;String&gt; concepts - Array of concept IDs, each identifying a concept.<br>
   *       <li>String concept - the concept name.<br>
   *       <li>Integer level - A number in the range 0 - 3 that represents the level of popularity
   *           of related concepts.<br>
   *       <li>Integer limit - The maximum number of concepts to be returned.<br>
   *     </ul>
   *
   * @return {@link Concepts}
   */
  public Concepts getGraphsRelatedConcepts(Map<String, Object> parameters) {
    // TODO: we may need to divide this into 2 methods
    Validate.notNull(parameters.get(ACCOUNT_ID), "account_id can't be null");
    Validate.notNull(parameters.get(GRAPH), "graph can't be null");
    if (parameters.get(CONCEPTS) == null && parameters.get(CONCEPT) == null)
      throw new MissingFormatArgumentException("concept or concepts should be identified");

    String graphId =
        createGraphIdPath((String) parameters.get(ACCOUNT_ID), (String) parameters.get(GRAPH));
    Map<String, Object> queryParameters = new HashMap<String, Object>();
    String[] queryParms = new String[] {LEVEL, LIMIT};
    for (String param : queryParms) {
      if (parameters.containsKey(param)) queryParameters.put(param, parameters.get(param));
    }
    if (parameters.get(CONCEPT_FIELDS) != null) {
      RequestedFields fields = (RequestedFields) parameters.get(CONCEPT_FIELDS);
      if (fields != null && fields.getFields() != null && !fields.getFields().isEmpty())
        queryParameters.put(CONCEPT_FIELDS, fields.toString());
    }
    if (parameters.get(CONCEPTS) != null) {
      JsonObject contentJson = new JsonObject();
      JsonArray conceptsJson = new JsonArray();
      @SuppressWarnings("unchecked")
      List<String> concepts = (List<String>) parameters.get(CONCEPTS);
      for (String value : concepts) {
        conceptsJson.add(new JsonPrimitive(value));
      }
      contentJson.add(CONCEPTS, conceptsJson);
      queryParameters.put(CONCEPTS, conceptsJson.toString());
      return executeRequest(graphId + RELATED_CONCEPTS_PATH, queryParameters, Concepts.class);
    } else {
      String conceptId =
          createConceptIdPath(
              (String) parameters.get(ACCOUNT_ID),
              (String) parameters.get(GRAPH),
              (String) parameters.get(CONCEPT));
      return executeRequest(conceptId + RELATED_CONCEPTS_PATH, queryParameters, Concepts.class);
    }
  }
  /**
   * Performs a conceptual search within a corpus.
   *
   * @param parameters The parameters to be used in the service call, account_id, corpus and ids are
   *     required.
   *     <ul>
   *       <li>String account_id - The account identifier.<br>
   *       <li>String corpus - The corpus name.<br>
   *       <li>RequestedFields concept_fields - Additional fields to be included in the concept
   *           objects.<br>
   *       <li>RequestedFields document_fields - Additional fields to be included in the document
   *           objects.<br>
   *       <li>String ids - JSON array of concept and/or document ids.<br>
   *       <li>Integer cursor - A number of items to skip.<br>
   *       <li>Integer limit - The maximum number of concepts to be returned.<br>
   *     </ul>
   *
   * @return {@link QueryConcepts}
   */
  public QueryConcepts conceptualSearch(Map<String, Object> parameters) {
    Validate.notNull(parameters.get(ACCOUNT_ID), "account_id can't be null");
    Validate.notNull(parameters.get(CORPUS), "corpus can't be null");
    Validate.notNull(parameters.get(IDS), "ids can't be null");

    String corpusId =
        createCorpusIdPath((String) parameters.get(ACCOUNT_ID), (String) parameters.get(CORPUS));
    Map<String, Object> queryParams = new HashMap<String, Object>();
    String[] queryParameters = new String[] {CURSOR, LIMIT};

    for (String param : queryParameters) {
      if (parameters.containsKey(param)) queryParams.put(param, parameters.get(param));
    }

    JsonArray IdsJsonArray = new JsonArray();
    @SuppressWarnings("unchecked")
    List<String> ids = (List<String>) parameters.get(IDS);
    for (String value : ids) {
      IdsJsonArray.add(new JsonPrimitive(value));
    }
    queryParams.put(IDS, IdsJsonArray.toString());

    if (parameters.get(CONCEPT_FIELDS) != null) {
      RequestedFields fields = (RequestedFields) parameters.get(CONCEPT_FIELDS);
      if (fields != null && fields.getFields() != null && !fields.getFields().isEmpty())
        queryParams.put(CONCEPT_FIELDS, fields.toString());
    }

    if (parameters.get(DOCUMENT_FIELDS) != null) {
      RequestedFields fields = (RequestedFields) parameters.get(DOCUMENT_FIELDS);
      if (fields != null && fields.getFields() != null && !fields.getFields().isEmpty())
        queryParams.put(DOCUMENT_FIELDS, fields.toString());
    }

    return executeRequest(corpusId + CONCEPTUAL_SEARCH_PATH, queryParams, QueryConcepts.class);
  }
 /**
  * Gets processing state of a Corpus.
  *
  * @param accountId String the account identifier,
  * @param corpusName String the corpus name.
  * @return the {@link CorpusStats}
  */
 public CorpusStats getCorpusStats(final String accountId, final String corpusName) {
   Validate.notNull(accountId, "accountId can't be null");
   Validate.notNull(corpusName, "corpusName can't be null");
   return executeRequest(
       createCorpusIdPath(accountId, corpusName) + STATS_PATH, null, CorpusStats.class);
 }
 /**
  * Retrieves the available corpus objects associated with an account identifier.
  *
  * @param accountId The account identifier.
  * @return {@link Corpora}
  */
 public Corpora listCorpora(String accountId) {
   Validate.notNull(accountId, "account_id can't be null");
   return executeRequest(CORPORA_PATH + FORWARD_SLASH + accountId, null, Corpora.class);
 }