/* *************************************************************
   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.
**************************************************************** */
/*
*  referral.c:  file containing function to parse and chase referrals.
*/

#include <stdio.h>
#include "rwhois.h"
#include "rwhois_internal.h"
#include <string.h>

rw_referral_group_struct *
rwhois_find_referral_group(dl_list_type *referral_group_list,
                          char         *group_name)
{
  int                      not_done;
  dl_node_type             *saved_position;
  rw_referral_group_struct *group;

  assert(referral_group_list);

  if (NOT_STR_EXISTS(group_name)) {
    return NULL;
  }

  saved_position = dl_list_get_pos(referral_group_list);

  not_done = dl_list_first(referral_group_list);
  while (not_done) {
    group = (rw_referral_group_struct *) dl_list_value(referral_group_list);

    if (group->group_name && STR_EQ(group_name, group->group_name)) {
      dl_list_put_pos(referral_group_list, saved_position);
      return group;
    }

    not_done = dl_list_next(referral_group_list);
  }

  dl_list_put_pos(referral_group_list, saved_position);
  return NULL;
}

int
rwhois_add_referral_to_group(dl_list_type       *referral_group_list,
                             rw_referral_struct *referral)
{
  rw_referral_group_struct *group;
  char                     *group_name;

  assert(referral_group_list);
  assert(referral);

  /* referrals without schemes are broken, so we don't add them. */
  if (NOT_STR_EXISTS(referral->scheme)) {
    return -1;
  }

  if (! STR_EQ(referral->scheme, "rwhois")) {
    group_name = EXT_REFERRALS_GROUP_NAME;
  } else if (STR_EXISTS(referral->authority_area)) {
    group_name = referral->authority_area;
  } else {
    group_name = ROOT_GROUP_NAME;
  }

  group = rwhois_find_referral_group(referral_group_list, group_name);

  /* if we didn't find a group, create it */
  if (! group) {
    group = (rw_referral_group_struct *) CALLOC(1, sizeof(*group));
    group->group_name = STRDUP(group_name);
    group->referrals = dl_list_create(destroy_rw_referral_struct);

    dl_list_append(referral_group_list, group);
  }

  dl_list_append(group->referrals, referral);

  return TRUE;
}

dl_list_type *
rwhois_get_all_referrals(dl_list_type *record_list,
                         dl_list_type *referral_group_list)
{
  int                not_done;
  rw_record_struct   *rec;

  assert(record_list);

  if (dl_list_empty(record_list)) {
    return NULL;
  }

  /* allocate the group list */
  if (!referral_group_list) {
    referral_group_list = dl_list_create(destroy_rw_referral_group_struct);
  }

  not_done = dl_list_first(record_list);
  while (not_done) {

    rec = (rw_record_struct *) dl_list_value(record_list);

    /* skip if the record is bad, it has no referral, or it has a bad
       referral */
    if (!rec || !rec->referral ||
        NOT_STR_EXISTS(rec->referral->scheme)) {
      not_done = dl_list_next(record_list);
      continue;
    }

    /* otherwise, add the referral to group list, and remove the
       record from the record list, transferring ownership of the
       referral structure to the referral group list */
    rwhois_add_referral_to_group(referral_group_list, rec->referral);

    rec->referral = NULL;

    /* deleting the node will normally advance our position in the
       list, unless we are already at the end */
    if (dl_list_at_tail(record_list)) {
      not_done = FALSE;
    } else {
      not_done = TRUE;
    }
    dl_list_delete(record_list);
  }

  return referral_group_list;
}

rw_referral_struct *
rwhois_find_referral_in_group(rw_referral_group_struct *ref_group,
                              char *host,
                              int  port,
                              char *other)
{
  int                not_done;
  dl_list_type       *ref_list;
  rw_referral_struct *ref;

  assert(ref_group);

  if (!ref_group->referrals) {
    return NULL;
  }

  ref_list = ref_group->referrals;

  not_done = dl_list_first(ref_list);
  while (not_done) {
    ref = (rw_referral_struct *) dl_list_value(ref_list);

    if (!ref ||
        (STR_EXISTS(host) && !STR_EQ(ref->host, host)) ||
        (port > 0 && port != ref->port) ||
        (STR_EXISTS(other) && !STR_EQ(ref->authority_area, other))) {
      not_done = dl_list_next(ref_list);
    } else {
      return ref;
    }
  }

  return NULL;
}

int
rwhois_merge_referral_groups(rw_referral_group_struct *dest_group,
                             rw_referral_group_struct *source_group)
{
  int                not_done;
  dl_list_type       *ref_list;
  rw_referral_struct *ref;

  assert(dest_group);
  assert(source_group);

  /* the group names should match */
  if (! STR_EQ(dest_group->group_name, source_group->group_name)) {
    return RW_ERROR;
  }

  if (!source_group->referrals) {
    return RW_OK;
  }

  if (!dest_group->referrals) {
    dest_group->referrals = source_group->referrals;
    return RW_OK;
  }

  ref_list = source_group->referrals;
  not_done = dl_list_first(ref_list);

  while (not_done) {
    ref = (rw_referral_struct *) dl_list_value(ref_list);

    if (rwhois_find_referral_in_group(dest_group, ref->host, ref->port,
                                      ref->authority_area) != NULL) {
      not_done = dl_list_next(ref_list);
      continue;
    }

    /* otherwise, the current referral isn't in the dest list, so move
       it from the source list to the dest list */
    dl_list_append(dest_group->referrals, ref);
    if (dl_list_at_tail(ref_list)) {
      not_done = FALSE;
    } else {
      not_done = TRUE;
    }
    /* this will advance us to the next node */
    dl_list_delete_node(ref_list);
  }

  return RW_OK;
}


int
destroy_rw_referral_struct(rw_referral_struct *ref)
{
  if (!ref) return TRUE;

  if (ref->scheme) FREE(ref->scheme);
  if (ref->host) FREE(ref->host);
  if (ref->authority_area) FREE(ref->authority_area);
  if (ref->url) FREE(ref->url);
  if (ref->old_style_line) FREE(ref->old_style_line);

  FREE(ref);

  return TRUE;
}

int
destroy_rw_referral_group_struct(rw_referral_group_struct *group)
{
  if (!group) return TRUE;

  if (STR_EXISTS(group->group_name)) FREE(group->group_name);

  dl_list_destroy(group->referrals);

  free(group);

  return TRUE;
}

