if (word(2 $loadinfo()) != [pf]) {
	load -pf $word(1 $loadinfo());
	return;
};

## We depend on 'reconnect_required' hook and '$serverctl(GET $server OPEN)',
## which are not avilable in previous versions.

if (info(i) < 1967 || J !~ [EPIC5*]) {
	xecho -b EPIC5 (rev. 1967) or newer is required;
	return;
};

load addset;

package reconnect3;

## Reconnect3 script for EPIC5.
## Written by zlonix@efnet with help of hop@efnet, public domain.
## Help with testing by skered@efnet.
##
## Version: 1.0 (September, 2021)
##  - Initial roll-out

## This script do basic reconnection procedure for you, it hooks on newly
## implemented hook 'reconnect_required', it is thrown when network connection
## experience problems. For more details read UPDATES document.
##
## The script doesn't have complicated logic, for example if you fiddle with
## your windows while being in process of reconnection (killing them for
## example, or changing server association) unexpected things may happen,
## channels be joined not in correct windows, or not joined at all, be aware of
## that.
##
## Do not connect to more than one server per group. It's recommended to have
## sane ircII.servers file in your IRCLIB environment variable, with groups and
## all of that (on disconnect the script tries to reconnect to the server
## $auto_reconnect_retries times before trying next server in the group, looping
## over when tried the last one), also, note that Libera.chat supports supplying
## your password for the account upon connect, through password token in server
## description so you don't need to use sasl_auth script anymore. For details
## about server description and its tokes visit
## http://help.epicsol.org/server_description.
##
## Another limitation is that the script won't catch 'Connection refused', or
## any DNS errors, since it's not considered as action for 'reconnect_required'
## hook. It means, that if you start the client, and it can't connect to the
## specified server, reconnect won't happen.
##
## All of this is sacrificed for simplicity of the implementation.

addset auto_reconnect_delay int;
set auto_reconnect_delay 75;

addset auto_reconnect_retries int;
set auto_reconnect_retries 5;

addset auto_reconnect_try_other_servers bool;
set auto_reconnect_try_other_servers on;

addset auto_reconnect bool {
	if (*0 == [on]) {
		on #-server_state 100 '% % ACTIVE' {
			## We encode(), because default "<default>" group
			## isn't accepted as lval for auto_reconnect array
			@ :group = serverctl(GET $0 GROUP);
			@ :group_enc = encode($serverctl(GET $0 GROUP));

			## If we use default group for more than one server
			## we need to encode server name as a tag for our
			## counters and variables, since user may be connected
			## to more than one server in default group, and when
			## we will experience disconnect for more than one server,
			## timers and variables will share encoded "<default>"
			## tag.
			if (group == [<default>]) {
				@ :group_enc = encode($serverctl(GET $0 NAME));
			};

			timer -delete auto_reconnect.$group_enc;
			assign -auto_reconnect.failures[$group_enc];

			foreach auto_reconnect[$group_enc] uuid {
				fe ($auto_reconnect[$group_enc][$uuid]) chan key {
					@ :win = windowctl(REFNUM $uuid);

					## window could be removed during reconnect
					if (@win)
						window $win channel "$chan $key";
				};
				assign -auto_reconnect[$group_enc][$uuid];
			};

			assign -auto_reconnect[$group_enc];

		};
		on #-reconnect_required 100 * {
			@ :group = serverctl(GET $0 GROUP);
			@ :group_enc = encode($serverctl(GET $0 GROUP));

			if (group == [<default>]) {
				@ :group_enc = encode($serverctl(GET $0 NAME));
			};

			timer -repeat -1 -refnum auto_reconnect.$group_enc $auto_reconnect_delay {
				@ :server = serverctl(FROM_SERVER);
				@ :group = serverctl(GET $server GROUP);
				@ :group_enc = encode($group);

				if (group == [<default>]) {
					@ :group_enc = encode($serverctl(GET $server NAME));
				};

				if (serverctl(GET $server CONNECTED)) {
					timer -delete auto_reconnect.$group_enc;
					assign -auto_reconnect.failures[$group_enc];
					return;
				};

				if (!serverctl(GET $server OPEN)) {
					if (auto_reconnect.failures[$group_enc] < auto_reconnect_retries) {
						@ ::auto_reconnect.failures[$group_enc]++;
						server +$server;
					} else {
						@ ::auto_reconnect.failures[$group_enc] = 0;
						if (auto_reconnect_try_other_servers == [on] && group != [<default>]) {
							server +;
						} else {
							xecho -b Maximum auto reconnect retries failed - Use /RECONNECT to reconnect;
						};
					};
				};
			};
		};
		on #-channel_lost 100 * {
			if (serverctl(GET $0 STATE) == [CLOSING]) {
				@ :group = serverctl(GET $0 GROUP);
				@ :group_enc = encode($group);
				@ :uuid = windowctl(GET $2 UUID);

				if (group == [<default>]) {
					@ :group_enc = encode($serverctl(GET $0 NAME));
				};

				push auto_reconnect[$group_enc][$uuid] $1 $key($1);
			};
		};
		alias lsreconnects (void) {
			xecho -v -b Currently reconnecting to following groups:;

			fe ($timerctl(REFNUMS)) timer {
				if (timer =~ [auto_reconnect.*]) {
					@ :group = decode($after(1 . $timer));

					xecho -v -b $group;
				};
			};
		};
		alias rmreconnect (void) {
			@ :server = windowctl(GET $winnum() SERVER);
			@ :group = serverctl(GET $server GROUP);
			@ :group_enc = encode($group);

			if (group == [<default>]) {
				@ :group_enc = encode($serverctl(GET $server NAME));
			};

			xecho -v -b Canceling reconnect to $decode($group_enc);
			timer -delete auto_reconnect.$group_enc;
		};
	} else {
		fe ($timerctl(REFNUMS)) timer {
			if (timer =~ [auto_reconnect.*]) {
				timer -delete $timer;
			};
		};
		on #-server_state 100 -'% % ACTIVE';
		on #-reconnect_required 100 -*;
		on #-channel_lost 100 -*;
		alais -lsreconnects;
		alias -rmreconnect;
	};
};

set auto_reconnect on;
