# sping.irc: a script to calculate the lag between you and a server
#
# written by nsx
#
# note: this script uses the serial number 105 for its serial hooks
#

package sping


# *** config variables **

@sping_timeout = 1200


# *** commands ***

alias sping (server) {
	@:source_server = servernum()

	if (server == []) {
		@:server = servername()
	}

	@:now = utime()
	quote PING $server $server

	@:ping_id = get_ping_id()
	@:sping_data = [$ping_id $now $source_server $server]

	push sping_list $sping_data
	^eval timer -refnum $ping_id $sping_timeout @timeout_sping\($ping_id $server\)

	xecho -b -w $serv_win() -- Sent PING to $server
}

alias vping (server) {
	@:refnum = servernum()

	if (server == []) {
		@:server = servername()
	}

	@:serv = [!]
	@:vping_list = vpings[$refnum]

	for (@:i = 3, serv != [], @:i += 4) {
		@:serv = word($i $vping_list)

		if (serv == server) {
			xecho -b -w $serv_win() -- Waiting for reply to previous VPING to $server
			return
		}
	}

	@:now = utime()
	quote VERSION $server

	@:ping_id = get_ping_id()
	@:vping_data = [$ping_id $now $server]

	push vpings[$servernum()] $vping_data
	^eval timer -refnum $ping_id $sping_timeout @timeout_vping\($ping_id $server\)

	^on ^351 "$server *" {
		@:now = utime()
		@:retval = handle_version($0 $now)

		if (retval) {
			xecho -b -w $serv_win() -- $1-
		}

		^on ^351 -"$0 *"
		^on ^263 -"$0 VERSION *"
	}

	^on ^263 "$server VERSION *" {
		@:now = utime()
		@:retval = handle_version($0 $now)

		if (retval) {
			xecho -b -w $serv_win() -- $1-
		}

		^on ^351 -"$0 *"
		^on ^263 -"$0 VERSION *"
	}

	xecho -b -w $serv_win() -- Sent VPING to $server
}


# *** functions ***

alias calculate_ping_time (ithen, fthen, inow, fnow) {
	@:idiff = inow - ithen
	@:fdiff = fnow - fthen

	if (fdiff < 0) {
		@:fdiff += 1000000
		@:idiff--
	}

	repeat ${6 - @fdiff} @:fdiff = [0] ## fdiff

	@:minute_diff = my_floor(${idiff / 60})
	@:second_diff = idiff % 60

	if (idiff >= 60) {
		if (minute_diff == 1) {
			return $minute_diff min ${second_diff}.${fdiff} secs
		} else {
			return $minute_diff mins ${second_diff}.${fdiff} secs
		}
	} else {
		return ${second_diff}.${fdiff} secs
	}

}

alias clear_pings {
	@sping_list = []
	@ping_number = 0

	fe ($myservers(*)) refnum {
		@vpings[$refnum] = []
	}
}

alias get_ping_id {
	@ping_number++

	return ping${ping_number}
}

alias handle_pong (server_name, now) {
	@:inow = word(0 $now)
	@:fnow = word(1 $now)
	@:server = [!]

	for (@:i = 4, server != [], @:i += 5) {
		@:server = word($i $sping_list)

		if (server == server_name) {
			@:ping_id = word(${i - 4} $sping_list)
			@:sping_itime = word(${i - 3} $sping_list)
			@:sping_ftime = word(${i - 2} $sping_list)
			@:reply_msg = calculate_ping_time($sping_itime $sping_ftime $inow $fnow)

			@:new_list_start = leftw(${i - 4} $sping_list)
			@:new_list_end = rightw(${(#sping_list - (i - 4)) - 5} $sping_list)

			if (new_list_start == []) {
				@sping_list = new_list_end
			} else {
				@sping_list = new_list_start ## [ ] ## new_list_end
			}

			^eval timer -del $ping_id

			xecho -b -w $serv_win() -- PONG received from $server_name in $reply_msg
			xecho -w $serv_win()

			return
		}
	}

	xecho -b -w $serv_win() -- Received PONG from $server_name
}

alias handle_version (server_name, now) {
	@:inow = word(0 $now)
	@:fnow = word(1 $now)
	@:refnum = servernum()
	@:server = [!]
	@:vping_list = vpings[$refnum]

	for (@:i = 3, server != [], @:i += 4) {
		@:server = word($i $vping_list)

		if (server == server_name) {
			@:ping_id = word(${i - 3} $vping_list)
			@:vping_itime = word(${i - 2} $vping_list)
			@:vping_ftime = word(${i - 1} $vping_list)
			@:reply_msg = calculate_ping_time($vping_itime $vping_ftime $inow $fnow)
			@:new_list_start = leftw(${i - 3} $vping_list)
			@:new_list_end = rightw(${(#vping_list - (i - 3)) - 4} $vping_list)

			if (new_list_start == []) {
				@:vping_list = new_list_end
			} else {
				@:vping_list = new_list_start ## [ ] ## new_list_end
			}

			@vpings[$refnum] = vping_list
			^eval timer -del $ping_id

			xecho -b -w $serv_win() -- VPING reply received from $server_name in $reply_msg
			xecho -w $serv_win()

			return 0
		}
	}

	return 1
}

alias my_floor (number) {
	switch ($count(. $number)) {
		(1) {
			return $before(. $number)
		}

		(0) {
			return $number
		}

		(*) {
			return -1
		}
	}
}

alias remove_matching_pings (field, value, entry_size, list) {
	@:item = [!]
	@:new_list = []

	for (@:i = field, item != [], @:i += entry_size) {
		@:item = word($i $list)

		if (item != value) {
			@:ping_entry = midw(${i - field} $entry_size $list)

			push new_list $ping_entry
		} else {
			@:ping_id = word(${i - field} $list)

			^eval timer -del $ping_id
		}
	}

	return $new_list
}

alias serv_win (refnum) {
	@:win_max = [255]
	@:i = [1]

	if (refnum == []) {
		@:refnum = servernum()
	}

	while (i < win_max) {
		if (winserv($i) == refnum) {
			return $i
		}

		@:i++
	}
}

alias timeout_sping (ping_id, server) {
	if (sping_list == []) {
		return
	}

	@sping_list = remove_matching_pings(0 $ping_id 5 $sping_list)

	xecho -b -w $serv_win() -- PING to $server timed out
}

alias timeout_vping (ping_id, server) {
	@:refnum = servernum($server)
	@:vping_list = vpings[$refnum]

	if (vping_list == []) {
		return
	}

	@:vping_list = remove_matching_pings(0 $ping_id 4 $vping_list)
	@vpings[$refnum] = vping_list

	xecho -b -w $serv_win() -- VPING to $server timed out
}


# *** hooks ***

# upon connection to any server, let's make sure we're not still waiting for
# any responses previously sent from that server

on #^connect 105 "*" {
	@:refnum = servernum($0)
	@:vpings[$refnum] = []

	@sping_list = remove_matching_pings(3 $refnum 5 $sping_list)
}

on ^pong "*" {
	@:now = utime()
	@handle_pong($0 $now)
}

# upon disconnection from any server, let's make sure we're not still waiting
# for a response to any pings we sent while connected to that server.

on #^server_lost 105 "*" {
	@:refnum = [$0]
	@:vpings[$refnum] = []

	@sping_list = remove_matching_pings(3 $refnum 5 $sping_list)
}

# if we're told that a server doesn't exist, let's make sure we're not waiting
# a ping reply from that server.

on #^402 105 "*" {
	@sping_list = remove_matching_pings(4 $1 5 $sping_list)

	fe ($myservers(*)) refnum {
		@vpings[$refnum] = remove_matching_pings(3 $1 4 $vpings[$refnum])
	}
}

on ^set "SPING_TIMEOUT *" {
	if ([$1] == []) {
		xecho -b Current value of SPING_TIMEOUT is: $sping_timeout
	} elsif (!isnumber($1)) {
		xecho -b Value of SPING_TIMEOUT must be numeric!
	} elsif ([$1] < 1) {
		xecho -b Value of SPING_TIMEOUT must be greater than or equal
to 1
	} else {
		@sping_timeout = [$1]
		xecho -b Value of SPING_TIMEOUT set to $sping_timeout
	}
}

@clear_pings()
