#include "lib_acl.h"
#include "lib_protocol.h"
#include "fiber/lib_fiber.h"
#include <signal.h>

static int __nfibers = 0;
static int __npkt = 10;
static int __delay = 10;
static int __timeout = 1000; 
static int __benchmark = 0;

static int __show_reply = 0;
static int __show_timeout = 0;
static int __show_unreach = 0;

static void display_res(ICMP_CHAT *chat)
{
	if (chat) {
		/* ʾ PING Ľܽ */
		icmp_stat(chat);
		printf(">>>max pkts: %d\r\n", icmp_chat_seqno(chat));
	}
}

static void reply_callback(ICMP_PKT_STATUS *status, void *arg)
{
	if (__show_reply) {
		ICMP_HOST *host = (ICMP_HOST *) arg;
		const ICMP_PKT *pkt_reply = icmp_pkt_peer(status->pkt);
		const ICMP_PKT_STATUS *status_reply;
		char       buf[ICMP_MAX_PACKET];
		size_t     dlen;

		assert(pkt_reply);
		status_reply = icmp_pkt_status(pkt_reply);
		dlen = icmp_pkt_payload(pkt_reply, buf, sizeof(buf));

		printf("%d bytes reply from %s: seq=%d ttl=%d time=%.2f ms"
			" code=%d type=%d dlen=%d data=%s\r\n",
			(int) status_reply->reply_len, host->dest_ip,
			status->seq, status->ttl, status->rtt,
			icmp_pkt_code(pkt_reply), icmp_pkt_type(pkt_reply),
			(int) dlen, buf);
	}
}

static void timeout_callback(ICMP_PKT_STATUS *status, void *arg)
{
	if (__show_timeout) {
		ICMP_HOST *host = (ICMP_HOST *) arg;
		printf("timeout: from %s, %.2f ms\r\n", host->dest_ip, status->rtt);
	}
}

static void unreach_callback(ICMP_PKT_STATUS *status acl_unused, void *arg)
{
	if (__show_unreach) {
		ICMP_HOST *host = (ICMP_HOST *) arg;
		printf("unreach: from %st\r\n", host->dest_ip);
	}
}

static void finish_callback(ICMP_HOST *host, void *arg acl_unused)
{
	icmp_stat_host(host, 0);
}

static void ping_one(const char *ip)
{
	ICMP_CHAT *chat = icmp_chat_create(NULL, 1); /*  ICMP  */
	ICMP_HOST *host = icmp_host_alloc(chat, ip, ip);

	icmp_host_init(host, ICMP_TYPE_ECHO, ICMP_CODE_EXTRA, __npkt, 64,
		__delay, __timeout);
	icmp_host_set(host, host, reply_callback, timeout_callback,
		unreach_callback, finish_callback);

	icmp_chat(host);	/* ʼ PING */
	display_res(chat);	/* ʾ PING  */

	icmp_host_free(host);
	icmp_chat_free(chat);	/* ͷ ICMP  */
}

/* PING Э */
static void fiber_ping(ACL_FIBER *fiber acl_unused, void *arg)
{
	const char *dest = (const char *) arg;
	ACL_DNS_DB* dns_db;
	const char *ip;

	/* ͨIPַ */
	dns_db = acl_gethostbyname(dest, NULL);
	if (dns_db == NULL) {
		acl_msg_warn("Can't find domain %s", dest);
		return;
	}

	/* ֻȡһ IP ַ PING */
	ip = acl_netdb_index_ip(dns_db, 0);
	if (ip == NULL || *ip == 0) {
		acl_msg_error("ip invalid");
		acl_netdb_free(dns_db);
		return;
	}

	ping_one(ip);

	acl_netdb_free(dns_db);  /* ͷ */

	if (--__nfibers == 0)
		acl_fiber_schedule_stop();
}

static void fiber_pine_one(ACL_FIBER *fiber acl_unused, void *arg)
{
	char *ip = (char*) arg;

	ping_one(ip);

	acl_myfree(ip);

	if (--__nfibers == 0)
		acl_fiber_schedule_stop();
}

static void usage(const char* progname)
{
	printf("usage: %s -h help\r\n"
		" -d delay[milliseconds]\r\n"
		" -t timout[milliseconds]\r\n"
		" -z stack_size\r\n"
		" -f ip_list_file\r\n"
		" -s show_result_list[timeout,reply,unreach,all]\r\n"
		" -b benchmark [if > 0 dest will be ignored]\r\n"
		" -n npkt dest1 dest2...\r\n", progname);

	printf("example: %s -n 10 www.sina.com.cn www.qq.com\r\n", progname);
}

static void show_list(const char *s)
{
	ACL_ITER iter;
	ACL_ARGV *tokens = acl_argv_split(s, ",;: \t\r\n");

#define EQ	!strcasecmp

	acl_foreach(iter, tokens) {
		const char *ptr = (const char *) iter.data;

		if (EQ(ptr, "all")) {
			__show_timeout = 1;
			__show_reply = 1;
			__show_unreach = 1;
		} else if (EQ(ptr, "timeout"))
			__show_timeout = 1;
		else if (EQ(ptr, "reply"))
			__show_reply = 1;
		else if (EQ(ptr, "unreach"))
			__show_unreach = 1;
	}

	acl_argv_free(tokens);
}

/* յ SIGINT ź( PING û ctrl + c)ʱźŴ */
static void on_sigint(int signo acl_unused)
{
	exit(0);
}

int main(int argc, char* argv[])
{
	char  ch;
	int   i, stack_size = 16000;
	char  iplist[256];

	signal(SIGINT, on_sigint);  /* û ctr + c ʱж PING  */
	acl_msg_stdout_enable(1);  /*  acl_msg_xxx ¼ϢĻ */

	iplist[0] = 0;

	while ((ch = getopt(argc, argv, "hn:d:z:b:t:f:s:")) > 0) {
		switch (ch) {
		case 'n':
			__npkt = atoi(optarg);
			break;
		case 'h':
			usage(argv[0]);
			return 0;
		case 'z':
			stack_size = atoi(optarg);
			break;
		case 'd':
			__delay = atoi(optarg);
			break;
		case 't':
			__timeout = atoi(optarg);
			break;
		case 'b':
			__benchmark = atoi(optarg);
			break;
		case 'f':
			ACL_SAFE_STRNCPY(iplist, optarg, sizeof(iplist));
			break;
		case 's':
			show_list(optarg);
			break;
		default:
			break;
		}
	}

	if (__npkt <= 0)
		__npkt = 10;

	if (__benchmark > 0) {
		static char *localhost = "127.0.0.1";

		__nfibers = __benchmark;

		for (i = 0; i < __benchmark; i++)
			acl_fiber_create(fiber_ping, localhost, stack_size);
	} else if (iplist[0]) {
		ACL_ARGV *ips = acl_argv_alloc(10240);
		ACL_FILE *fp = acl_fopen(iplist, "r");
		ACL_ITER  iter;

		if (fp == NULL) {
			printf("open file %s error %s\r\n",
				iplist, acl_last_serror());
			acl_argv_free(ips);
			return 1;
		}
		while (!acl_feof(fp)) {
			char line[256];
			if (acl_fgets_nonl(line, sizeof(line), fp) == NULL)
				break;
			acl_argv_add(ips, line, NULL);
		}
		acl_fclose(fp);

		acl_foreach(iter, ips) {
			char *ip = acl_mystrdup((char*) iter.data);
			acl_fiber_create(fiber_pine_one, ip, stack_size);
		}

		__nfibers = acl_argv_size(ips);
		acl_argv_free(ips);
	} else {
		if (optind == argc) {
			usage(argv[0]);
			return 0;
		}

		/* ¼ҪЭ̵ */
		__nfibers = argc - optind;

		for (i = optind; i < argc; i++)
			acl_fiber_create(fiber_ping, argv[i], stack_size);
	}

	acl_fiber_schedule();

	return 0;
}
