/******************************************************************************
 libprozilla - a download accelerator library
 Copyright (C) 2001 Kalum Somaratna

 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
******************************************************************************/



#include "common.h"
#include "prozilla.h"
#include "connect.h"
#include "misc.h"
#include "url.h"
#include "netrc.h"
#include "debug.h"
#include "ping.h"



#define TCP_PING_PACKSIZE 3

uerr_t tcp_ping(ping_t * ping_data)
{
  int status, noblock, flags;
  extern int h_errno;
  struct timeval start_time;
  struct timeval end_time;
  char ping_buf[TCP_PING_PACKSIZE];
  int bytes_read;
    struct addrinfo hints, *res=NULL;
    char szPort[10];
    int error;

  assert(ping_data->host);
  memset(&hints, 0, sizeof(hints));
  memset(szPort, 0, sizeof(szPort));
  snprintf(szPort, sizeof(szPort), "%d", ping_data->port);
  hints.ai_family = AF_INET;
  hints.ai_socktype = SOCK_STREAM;

    error = getaddrinfo(ping_data->host, szPort, &hints, &res);
    if (error) {
      return ping_data->err = HOSTERR;
        }
 
 if ((ping_data->sock = socket(res->ai_family, res->ai_socktype, IPPROTO_TCP)) < 1)
  {
    free(res);
    return ping_data->err = CONSOCKERR;
  }

  /* Experimental. */
  flags = fcntl(ping_data->sock, F_GETFL, 0);
  if (flags != -1)
    noblock = fcntl(ping_data->sock, F_SETFL, flags | O_NONBLOCK);
  else
    noblock = -1;

  /* get start time */
  gettimeofday(&start_time, 0);

  status =
      connect(ping_data->sock, res->ai_addr, res->ai_addrlen);

  if ((status == -1) && (noblock != -1) && (errno == EINPROGRESS))
  {
    fd_set writefd;

    FD_ZERO(&writefd);
    FD_SET(ping_data->sock, &writefd);

    status =
	select((ping_data->sock + 1), NULL, &writefd, NULL,
	       &ping_data->timeout);

    /* Do we need to retry if the err is EINTR? */

    if (status > 0)
    {
      socklen_t arglen = sizeof(int);

      if (getsockopt
	  (ping_data->sock, SOL_SOCKET, SO_ERROR, &status, &arglen) < 0)
	status = errno;

      if (status != 0)
	errno = status, status = -1;

      if (errno == EINPROGRESS)
	errno = ETIMEDOUT;
    } else if (status == 0)
      errno = ETIMEDOUT, status = -1;
  }

  if (status < 0)
  {
    close_sock(&ping_data->sock);

    if (errno == ECONNREFUSED)
    {
      free(res);
      return ping_data->err = CONREFUSED;
    } else if (errno == ETIMEDOUT)
    {
      free(res);
      return ping_data->err = PINGTIMEOUT;
    } else
    {
      free(res);
      return ping_data->err = CONERROR;
    }
  } else
  {
    flags = fcntl(ping_data->sock, F_GETFL, 0);

    if (flags != -1)
      fcntl(ping_data->sock, F_SETFL, flags & ~O_NONBLOCK);
  }

  /* setsockopt(*sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&opt,
     (int) sizeof(opt)); */

  free(res);


  /*So far so good connection established */

  bytes_read =
      krecv(ping_data->sock, ping_buf, TCP_PING_PACKSIZE, 0,
	    &ping_data->timeout);
  close_sock(&ping_data->sock);

  proz_debug("bytes read = %d", bytes_read);
  if (bytes_read == -1)
  {
    if (errno == ETIMEDOUT)
      return ping_data->err = PINGTIMEOUT;
    else
      return ping_data->err = READERR;
  }

  if (bytes_read == 0 || bytes_read < TCP_PING_PACKSIZE)
    return ping_data->err = READERR;

  /* the end time */
  gettimeofday(&end_time, 0);
  proz_timeval_subtract(&ping_data->ping_time, &end_time, &start_time);


  /*  standard_ping_milli_secs =(int)((((float)ping_data->ping_time.tv_usec/1000)+(((float)ping_data->ping_time.tv_sec)*1000))*3/(float)bytes_read);

     ping_data->ping_time.tv_sec=standard_ping_milli_secs/1000;
     ping_data->ping_time.tv_usec=standard_ping_milli_secs%1000;
   */

  return ping_data->err = PINGOK;
}


void proz_mass_ping(ftps_request_t * request)
{

  request->mass_ping_running = TRUE;
  if (pthread_create(&request->mass_ping_thread, NULL,
		     (void *) &mass_ping, (void *) request) != 0)
    proz_die(_("Error: Not enough system resources"));

}

void proz_cancel_mass_ping(ftps_request_t * request)
{
  /*TODO Rewrite so that this will terminate the pingin threads as well */
  request->mass_ping_running = FALSE;
  pthread_cancel(request->mass_ping_thread);
  pthread_join(request->mass_ping_thread,0);
}

void mass_ping(ftps_request_t * request)
{
  int i, j, k = 0, num_iter, num_left, simul_pings;
  pthread_t *ping_threads;
  ping_t *ping_requests;


  simul_pings = request->max_simul_pings;

  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
  ping_threads = (pthread_t *) kmalloc(sizeof(pthread_t) * simul_pings);
  ping_requests = kmalloc(sizeof(ping_t) * request->num_mirrors);

  num_iter = request->num_mirrors / simul_pings;
  num_left = request->num_mirrors % simul_pings;
  proz_debug("Max simul pings=%d", simul_pings);
  proz_debug("request->num_mirrors=%d", request->num_mirrors);

  pthread_mutex_lock(&request->access_mutex);
  request->mass_ping_running = TRUE;
  pthread_mutex_unlock(&request->access_mutex);


  k = 0;

  for (i = 0; i < num_iter; i++)
  {
    for (j = 0; j < simul_pings; j++)
    {
      ping_t ping_request;

      memset(ping_requests + k, 0, sizeof(ping_request));
      /*FIXME */
      ping_requests[k].timeout.tv_sec = request->ping_timeout.tv_sec;
      ping_requests[k].timeout.tv_usec = request->ping_timeout.tv_usec;
      ping_requests[k].host = strdup(request->mirrors[k].server_name);
      ping_requests[k].port = 21;

      if (pthread_create(&ping_threads[j], NULL,
			 (void *) &tcp_ping,
			 (void *) (ping_requests + k)) != 0)
	proz_die("Error: Not enough system resources"
		 "to create thread!\n");
      k++;
    }

    k -= simul_pings;

    for (j = 0; j < simul_pings; j++)
    {
      /*Wait till the end of each thread. */
      pthread_join(ping_threads[j], NULL);
      if (ping_requests[k].err == PINGOK)
      {
	pthread_mutex_lock(&request->access_mutex);
	request->mirrors[k].milli_secs =
	    (ping_requests[k].ping_time.tv_sec * 1000) +
	    (ping_requests[k].ping_time.tv_usec / 1000);
	request->mirrors[k].status = RESPONSEOK;
	pthread_mutex_unlock(&request->access_mutex);
      } else
      {
	pthread_mutex_lock(&request->access_mutex);
	request->mirrors[k].status = NORESPONSE;
	pthread_mutex_unlock(&request->access_mutex);
      }
      k++;
    }
  }


  for (j = 0; j < num_left; j++)
  {
    ping_t ping_request;

    memset(ping_requests + k, 0, sizeof(ping_request));
    /*FIXME */
    ping_requests[k].timeout.tv_sec = request->ping_timeout.tv_sec;
    ping_requests[k].timeout.tv_usec = 0;
    ping_requests[k].host = strdup(request->mirrors[k].server_name);
    ping_requests[k].port = 21;

    if (pthread_create(&ping_threads[j], NULL,
		       (void *) &tcp_ping,
		       (void *) (&ping_requests[k])) != 0)
      proz_die("Error: Not enough system resources" "to create thread!\n");

    k++;
  }

  k -= num_left;

  for (j = 0; j < num_left; j++)
  {
    /*Wait till the end of each thread. */
    pthread_join(ping_threads[j], NULL);

    /*Wait till the end of each thread. */
    pthread_join(ping_threads[j], NULL);
    if (ping_requests[k].err == PINGOK)
    {
      pthread_mutex_lock(&request->access_mutex);
      request->mirrors[k].milli_secs =
	  (ping_requests[k].ping_time.tv_sec * 1000) +
	  (ping_requests[k].ping_time.tv_usec / 1000);
      request->mirrors[k].status = RESPONSEOK;
      pthread_mutex_unlock(&request->access_mutex);
    } else
    {
      pthread_mutex_lock(&request->access_mutex);
      request->mirrors[k].status = NORESPONSE;
      pthread_mutex_unlock(&request->access_mutex);
    }
    k++;
  }

  proz_debug("mass_ping complete.");
  pthread_mutex_lock(&request->access_mutex);
  request->mass_ping_running = FALSE;
  pthread_mutex_unlock(&request->access_mutex);
}