/* $Id */

/*
 *  mirror.c - HTTP/FTP mirror helper routines for pakke
 *  Copyright (C) 2002 by Richard Dawe
 *      
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "common.h"

#include <stdlib.h>
#include <string.h>
#include <limits.h>

#include <libpakke/util.h>

#include "mirror.h"

extern const char *simtel_mirrors[];

const char ftp_simtel_net[] = "ftp://ftp.simtel.net/pub/simtelnet/";

static int   mirror_inited = 0;
static char *mirror        = NULL; /* This is the mirror we will use. */

/* -----------
 * - countem -
 * ----------- */

static inline int
countem (char **list)
{
  int i;

  if (list == NULL)
    return(0);

  for (i = 0; list[i] != NULL; i++) {;}

  return(i);
}

/* ----------------
 * - valid_mirror -
 * ---------------- */

static int
valid_mirror (const char *url, char **locations)
{
  /* TODO: probably should be MAXGETHOSTNAME, but where's that defined
   * portably? */
  static char hostname[PATH_MAX];
  char *p = NULL;
  int i;

  /* Don't waste time. */
  if ((locations == NULL) || (locations[0] == NULL))
    return(0);

  /* Get the hostname component of the URL. */
  if (!get_url_component(URL_COMP_HOST, url, hostname, sizeof(hostname)))
    return(0);

  /* Find the country-code suffix. Failing that, just compare
   * the entire string. */
  p = strrchr(hostname, '.');
  if (p)
    p++;
  else
    p = hostname;

  /* Does the suffix match any of our locations? Do a case insensitive
   * comparison, because hostnames are case insensitive. */
  for (i = 0; locations[i] != NULL; i++) {
    if (strcasecmp(p, locations[i]) == 0)
      return(1);
  }

  return(0);
}

/* ---------------
 * - mirror_init -
 * --------------- */

static void
mirror_uninit_wrapper (void)
{
  mirror_uninit();
}

int
mirror_init (char **http_mirrors, char **ftp_mirrors, char **locations)
{
  const char DJGPP_SUBDIR[] = "gnu/djgpp/";
  int n_http_mirrors, n_ftp_mirrors, n_locations, n_simtel_mirrors;
  int idx, i;

  /* Already initialised? */
  if (mirror_inited)
    return(mirror_inited);

  n_http_mirrors   = countem(http_mirrors);
  n_ftp_mirrors    = countem(ftp_mirrors);
  n_locations      = countem(locations);
  n_simtel_mirrors = countem((char **) simtel_mirrors);

  /*
   * If the user has configured any mirrors manually, prefer HTTP over FTP.
   * We prefer HTTP because it's has a quicker start-up time (only 1 TCP
   * connection) and works better through firewalls.
   *
   * Note that we don't suffix the path with DJGPP_SUBDIR, because
   * the user should have already included that in the URL.
   */
  if ((mirror == NULL) && n_http_mirrors) {
    idx = rand() % n_http_mirrors;

    mirror = strdup(http_mirrors[idx]);
    if (mirror == NULL)
      return(0);
  }

  if ((mirror == NULL) && n_ftp_mirrors) {
    idx = rand() % n_ftp_mirrors;

    mirror = strdup(ftp_mirrors[idx]);
    if (mirror == NULL)
      return(0);
  }

  /* Choose a mirror based on location. Suffix it with DJGPP_SUBDIR. */
  if ((mirror == NULL) && n_locations) {
    /* For each index, store a flag to say whether it matches or location
     * or not. */
    int *mirror_ok = NULL;
    int  n_ok      = 0;

    mirror_ok = calloc(n_simtel_mirrors, sizeof(int));
    if (mirror_ok == NULL)
      return(0);

    for (i = 0; i < n_simtel_mirrors; i++) {
      if (!valid_mirror(simtel_mirrors[i], locations))
	continue;

      mirror_ok[i] = 1;
      n_ok++;
    }

    /* Now loop until we've chosen a valid mirror. */
    if (n_ok) {
      for (idx = rand() % n_simtel_mirrors;
	   !mirror_ok[idx];
	   idx = rand() % n_simtel_mirrors) {;}

      mirror = strdupnx(simtel_mirrors[idx],
			strlen(DJGPP_SUBDIR) + 1 /* slash */);
      if (mirror == NULL) {
	/* Tidy up */
	free(mirror_ok);

	return(0);
      }
    }

    /* Tidy up */
    free(mirror_ok);

    /* Now add the DJGPP subdirectory. */
    if (mirror != NULL) {
      addforwardslash(mirror);
      strcat(mirror, DJGPP_SUBDIR);
    }
  }

  /* As a fall-back use the Simtel.NET main site. Suffix it with
   * DJGPP_SUBDIR. */
  if (mirror == NULL) {
    mirror = strdupnx(ftp_simtel_net, strlen(DJGPP_SUBDIR) + 1 /* slash */);
    if (mirror == NULL)
      return(0);

    /* Now add the DJGPP subdirectory. */
    addforwardslash(mirror);
    strcat(mirror, DJGPP_SUBDIR);
  }

  /* Set up the exit handler. */
  atexit(mirror_uninit_wrapper);

  /* Done */
  mirror_inited++;

  return(mirror_inited);
}

/* -----------------
 * - mirror_uninit -
 * ----------------- */

int
mirror_uninit (void)
{
  if (!mirror_inited)
    return(0);

  if (mirror) {
    free(mirror);
    mirror = NULL;
  }

  return(1);
}

/* -----------------
 * - mirror_mirror -
 * ----------------- */

/* Return the name of the mirror we've chosen. */

/*
 * TODO: Rename to mirror_mirror_on_the_wall_who_is_the_fairest_of_them_all.
 * OK, only joking. ;)
 */

const char *
mirror_mirror (void)
{
  return(mirror);
}
