/* *************************************************************
   RWhois Software

   Copyright (c) 1994 Scott Williamson and Mark Kosters
   Copyright (c) 1996-1998 Network Solutions, Inc.

   See the file LICENSE for conditions of use and distribution.
**************************************************************** */

#include "rwhois.h"
#include "rwhois_internal.h"

dl_list_type *
recursive_query_rwhois(rw_query_args_struct *query_args,
                       rw_server_struct     *server,
                       char                 *spec_version,
                       char                 *imp_version,
                       rw_recurse_mode_enum recurse_mode,
                       dl_list_type         *referral_group_list,
                       dl_list_type         *record_list,
                       rw_response_struct   *response)
{
  rw_referral_group_struct *cur_ref_group;
  dl_list_type             *cur_ref_list;
  rw_referral_struct       *cur_ref;
  dl_list_type             *local_referral_group_list;
  rw_referral_group_struct *global_ext_ref_group;
  rw_referral_group_struct *local_ext_ref_group;
  rw_server_struct         new_server;
  char                     info_buffer[MAXBUFFER + 1];
  int                      alloc_referral_group_list = FALSE;
  int                      not_done;
  int                      not_done_referrals;

  /* these must be true, or you are using the function incorrectly */
  assert(query_args);
  assert(response);
  assert(server);


  if (NOT_STR_EXISTS(server->host) || NOT_STR_EXISTS(query_args->query)) {
    rwhois_set_response(response, RW_ERROR,
                        "Missing critical information for the rwhois query");
    return NULL;
  }

  memset(info_buffer, 0, sizeof(info_buffer));

  /* connect to the server */
  if (rwhois_connect(server, spec_version, imp_version, info_buffer,
                     sizeof(info_buffer), response) < RW_OK) {

    /* return the info block as a record */
    if (STR_EXISTS(info_buffer)) {
      rw_record_struct *record;
      record = (rw_record_struct *) CALLOC(1, sizeof(*record));
      record->text = STRDUP(info_buffer);
      rwhois_set_record_source_by_server(record, server);

      if (!record_list) {
        record_list = dl_list_create(destroy_rw_record_struct);
      }
      dl_list_append(record_list, record);

      return record_list;
    }

    return NULL;
  }

  /* set the limit */
  if (query_args->limit_changed_flag &&
      (rwhois_limit(server, query_args->limit, response) != RW_OK)) {
    return NULL;
  }

  /* issue the query */
  if (rwhois_query(server, query_args->query, response) != RW_OK) {
    return NULL;
  }


  if (!record_list) {
    record_list = dl_list_create(destroy_rw_record_struct);
  }

  if (! referral_group_list) {
    referral_group_list = dl_list_create(destroy_rw_referral_group_struct);
    alloc_referral_group_list = TRUE;
  }

  /* get all of the results */
  rwhois_fetch_all_records(server, record_list, response);
  if (response->status <= 0) {
    if (alloc_referral_group_list) dl_list_destroy(referral_group_list);
    return NULL;
  }

  /* we are now done with this server connection */
  rwhois_close(server);

  /* get all of the referrals */
  referral_group_list = rwhois_get_all_referrals(record_list,
                                                 referral_group_list);


  /* if we aren't recursing, or there are no referrals, we are
     done now */
  if (recurse_mode == RW_NO_RECURSE || !referral_group_list) {
    if (alloc_referral_group_list) dl_list_destroy(referral_group_list);
    return record_list;
  }

  /* otherwise, recurse, trying referrals serially in each group */
  not_done = dl_list_first(referral_group_list);

  while (not_done)
  {
    cur_ref_group
      = (rw_referral_group_struct *) dl_list_value(referral_group_list);

    /* skip if bad or the external group */
    if (!cur_ref_group ||
        NOT_STR_EXISTS(cur_ref_group->group_name) ||
        STR_EQ(cur_ref_group->group_name, EXT_REFERRALS_GROUP_NAME)) {
      not_done = dl_list_next(referral_group_list);
      continue;
    }

    cur_ref_list = cur_ref_group->referrals;

    not_done_referrals = dl_list_first(cur_ref_list);

    while (not_done_referrals)
    {
      cur_ref = (rw_referral_struct *) dl_list_value(cur_ref_list);

      /* skip if bad */
      if (!cur_ref || NOT_STR_EXISTS(cur_ref->host)) {
        not_done_referrals = dl_list_next(cur_ref_list);
        continue;
      }

      local_referral_group_list
        = dl_list_create(destroy_rw_referral_group_struct);

      /* point new query to the referral server */
      new_server.host = cur_ref->host;
      new_server.port = cur_ref->port;
      new_server.debug_level = server->debug_level;

      /* follow the referral */
      record_list = recursive_query_rwhois(query_args,
                                           &new_server,
                                           spec_version,
                                           imp_version,
                                           recurse_mode,
                                           local_referral_group_list,
                                           record_list,
                                           response);
      if (response->status != RW_OK) {
        dl_list_destroy(local_referral_group_list);
        not_done_referrals = dl_list_next(cur_ref_list);
        continue;
      }

      /* we successfully followed the referral, so the new records
         have already been appended.  All of the resultant rwhois
         referrals should have already been chased, so all we need to
         do is collect the external referrals */

      local_ext_ref_group
        = rwhois_find_referral_group(local_referral_group_list,
                                     EXT_REFERRALS_GROUP_NAME);
      global_ext_ref_group
        = rwhois_find_referral_group(referral_group_list,
                                       EXT_REFERRALS_GROUP_NAME);
      if (local_ext_ref_group && global_ext_ref_group) {
        rwhois_merge_referral_groups(global_ext_ref_group,
                                     local_ext_ref_group);
      }

      dl_list_destroy(local_referral_group_list);
      break;

    } /* while not_done_referrals */

    /* whether or not we successfully followed referrals in that
       group, we are done with it, so we will remove it from the group
       list */
    if (dl_list_at_tail(referral_group_list)) {
      not_done = FALSE;
    } else {
      not_done = TRUE;
    }

    dl_list_delete(referral_group_list);
  }

  return record_list;
}
