/**
   * Search against all repositories using provided parameters. Note there are a few different types
   * of searches you can perform. If you provide the 'q' query parameter, a keyword search will be
   * performed. If you provide the 'g, a, v, p or c' query parameters, a maven coordinate search
   * will be performed. If you provide the 'cn' query parameter, a classname search will be
   * performed. If you provide the 'sha1' query parameter, a checksum search will be performed.
   *
   * @param q provide this param for a keyword search (g, a, v, p, c, cn, sha1 params will be
   *     ignored).
   * @param sha1 provide this param for a checksum search (g, a, v, p, c, cn params will be
   *     ignored).
   * @param cn provide this param for a classname search (g, a, v, p, c params will be ignored).
   * @param g group id to perform a maven search against (can be combined with a, v, p & c params as
   *     well).
   * @param a artifact id to perform a maven search against (can be combined with g, v, p & c params
   *     as well).
   * @param v version to perform a maven search against (can be combined with g, a, p & c params as
   *     well).
   * @param p packaging type to perform a maven search against (can be combined with g, a, v & c
   *     params as well).
   * @param c classifier to perform a maven search against (can be combined with g, a, v & p params
   *     as well).
   * @param from result index to start retrieving results from.
   * @param count number of results to have returned to you.
   * @param repositoryId The repositoryId to which repository search should be narrowed. Omit if
   *     search should be global.
   */
  @Override
  @GET
  @ResourceMethodSignature(
      queryParams = {
        @QueryParam("q"),
        @QueryParam("g"),
        @QueryParam("a"),
        @QueryParam("v"),
        @QueryParam("p"),
        @QueryParam("c"),
        @QueryParam("cn"),
        @QueryParam("sha1"),
        @QueryParam("from"),
        @QueryParam("count"),
        @QueryParam("repositoryId")
      },
      output = SearchResponse.class)
  public Object get(Context context, Request request, Response response, Variant variant)
      throws ResourceException {
    Form form = request.getResourceRef().getQueryAsForm();

    final Map<String, String> terms = new HashMap<String, String>();

    for (Parameter parameter : form) {
      terms.put(parameter.getName(), parameter.getValue());
    }

    Integer from = null;
    Boolean exact = null;
    String repositoryId = null;
    Boolean expandVersion = Boolean.FALSE;
    Boolean collapseResults = Boolean.FALSE;

    if (form.getFirstValue("from") != null) {
      try {
        from = Integer.valueOf(form.getFirstValue("from"));
      } catch (NumberFormatException e) {
        from = null;
      }
    }

    int count = LUCENE_HIT_LIMIT;
    if (form.getFirstValue("count") != null) {
      try {
        // capping the possible count
        count = Math.min(LUCENE_HIT_LIMIT, Integer.valueOf(form.getFirstValue("count")));
      } catch (NumberFormatException e) {
        count = LUCENE_HIT_LIMIT;
      }
    }

    if (form.getFirstValue("repositoryId") != null) {
      repositoryId = form.getFirstValue("repositoryId");
    }

    if (form.getFirstValue("exact") != null) {
      exact = Boolean.valueOf(form.getFirstValue("exact"));
    }

    if (form.getFirstValue("versionexpand") != null) {
      expandVersion = Boolean.valueOf(form.getFirstValue("versionexpand"));
    }
    if (form.getFirstValue("collapseresults") != null) {
      collapseResults = Boolean.valueOf(form.getFirstValue("collapseresults"));
    }

    // A little explanation about collapseResults, that might seems little bit awkward, since
    // currently we have only
    // one column "collapsable" (the version), but before and maybe in the future that's not the
    // case. So, here is
    // it:
    // the "collapseResults" is just a flag "do we allow collapse at all". It is just a shorthand to
    // turn on or off
    // collapse generally
    // Let's assume we have columns colA, colB and colC collapsable. So, instead saying
    // expandColA=true,expandColB=true,expandColC=true,
    // it is just easy to say collapseresults=false
    // BUT, if collapseresults=true is sent by client, even then an "override" will happen if there
    // is actually NO
    // ROW to collapse!
    // So: collapseresults=false EQUALS-TO expandColA=true & expandColB=true & expandColC=true
    if (collapseResults) {
      // here we would like to have ANDed all the collapsable column flags and negated the result
      // currently we
      collapseResults = !(true && expandVersion); // && expandColA && expandColB;
    }

    IteratorSearchResponse searchResult = null;

    SearchNGResponse result = new SearchNGResponse();

    int runCount = 0;

    while (runCount < RETRIES) {
      try {
        List<ArtifactInfoFilter> filters = new ArrayList<ArtifactInfoFilter>();

        // we need to save this reference to later
        SystemWideLatestVersionCollector systemWideCollector =
            new SystemWideLatestVersionCollector();
        filters.add(systemWideCollector);

        RepositoryWideLatestVersionCollector repositoryWideCollector = null;

        if (collapseResults) {
          repositoryWideCollector = new RepositoryWideLatestVersionCollector();
          filters.add(repositoryWideCollector);
        }

        try {
          searchResult =
              searchByTerms(
                  terms,
                  repositoryId,
                  from,
                  count,
                  exact,
                  expandVersion,
                  collapseResults,
                  filters,
                  searchers);

          if (searchResult == null) {
            collapseResults = false;

            continue;
          } else {
            repackIteratorSearchResponse(
                request,
                terms,
                result,
                collapseResults,
                from,
                count,
                searchResult,
                systemWideCollector,
                repositoryWideCollector);

            if (!result.isTooManyResults()) {
              // if we had collapseResults ON, and the totalHits are larger than actual (filtered)
              // results, and the actual result count is below COLLAPSE_OVERRIDE_TRESHOLD,
              // and full result set is smaller than HIT_LIMIT
              // then repeat without collapse
              if (collapseResults
                  && result.getData().size() < searchResult.getTotalHitsCount()
                  && result.getData().size() < COLLAPSE_OVERRIDE_TRESHOLD
                  && searchResult.getTotalHitsCount() < GA_HIT_LIMIT) {
                collapseResults = false;

                continue;
              }
            }
          }
        } catch (IOException e) {
          throw new ResourceException(Status.SERVER_ERROR_INTERNAL, e.getMessage(), e);
        }

        // we came here, so we break the while-loop, we got what we need
        break;
      } catch (NoSuchRepositoryException e) {
        throw new ResourceException(
            Status.CLIENT_ERROR_BAD_REQUEST, "Repository to be searched does not exists!", e);
      } catch (AlreadyClosedException e) {
        runCount++;

        getLogger()
            .info(
                "NexusIndexer issue (NEXUS-3702), we got AlreadyClosedException that happens when Reindexing or other \"indexer intensive\" task is running on instance while searching! Redoing search again.");

        if (getLogger().isDebugEnabled()) {
          // just keep it silent (DEBUG)
          getLogger().debug("Got AlreadyClosedException exception!", e);
        }

        result.setData(null);
      }
    }

    if (result.getData() == null) {
      try {
        repackIteratorSearchResponse(
            request,
            terms,
            result,
            collapseResults,
            from,
            count,
            IteratorSearchResponse.empty(null),
            null,
            null);
      } catch (NoSuchRepositoryException e) {
        // will not happen
      } catch (IOException e) {
        // will not happen
      }

      getLogger()
          .info(
              "Nexus issue (NEXUS-3702): Was unable to perform search "
                  + RETRIES
                  + " times, giving up, and lying about TooManyResults. Please retry to reproduce this with DEBUG logs and report this issue!");
    }

    return result;
  }