package io.shockah.skylark.ident.nickserv;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.pircbotx.User;
import io.shockah.skylark.Bot;
import io.shockah.skylark.event.Whois2Event;
import io.shockah.skylark.func.Action2;
import io.shockah.skylark.ident.IdentMethod;
import io.shockah.skylark.ident.IdentMethodFactory;
import io.shockah.skylark.ident.IdentService;
import io.shockah.skylark.util.Box;
import io.shockah.skylark.util.Dates;
import io.shockah.skylark.util.Lazy;
import io.shockah.skylark.util.ReadWriteList;
import io.shockah.skylark.util.ReadWriteMap;
public class NickServIdentMethod extends IdentMethod {
public static final String METHOD_NAME = "NickServ";
public static final String METHOD_PREFIX = "ns";
public static final long DEFAULT_SYNC_REQUEST_TIMEOUT = 5000l;
public static final long DEFAULT_EXPIRATION_TIME = 1000l * 60l * 5l;
public static final String OPERATOR_STATUS_NETWORK_SERVICE = "Network Service";
protected final Lazy<Boolean> available = Lazy.of(this::checkAvailability);
protected final ReadWriteMap<String, Entry> cache = new ReadWriteMap<>(new HashMap<>());
protected final ReadWriteList<Request> userRequests = new ReadWriteList<>(new ArrayList<>());
protected boolean hasWhoX = false;
protected boolean hasExtendedJoin = false;
protected boolean hasAccountNotify = false;
public NickServIdentMethod(IdentService service, IdentMethodFactory factory) {
super(service, factory, METHOD_NAME, METHOD_PREFIX);
}
@Override
public boolean isAvailable() {
return available.get();
}
protected boolean checkAvailability() {
Bot bot = service.manager.getAnyBot();
hasWhoX = bot.getServerInfo().isWhoX();
hasExtendedJoin = bot.getEnabledCapabilities().contains("extended-join");
hasAccountNotify = bot.getEnabledCapabilities().contains("account-notify");
Whois2Event whois = bot.whoisManager.syncRequest("NickServ");
return whois != null && OPERATOR_STATUS_NETWORK_SERVICE.equals(whois.getOperatorStatus());
}
public boolean hasWhoX() {
available.get();
return hasWhoX;
}
public boolean hasExtendedJoin() {
available.get();
return hasExtendedJoin;
}
public boolean hasAccountNotify() {
available.get();
return hasAccountNotify;
}
@Override
public String getForUser(User user) {
Entry entry = cache.get(user.getNick());
if (entry == null || entry.account == null || entry.expired()) {
String account = syncRequest(user);
entry = new Entry(account, getNewEntryExpirationDate());
}
cache.put(user.getNick(), entry);
return entry.account;
}
public void asyncRequest(User user, Action2<String, String> f) {
userRequests.add(new Request(user.getNick(), f));
user.getBot().sendIRC().message("NickServ", String.format("acc %s *", user.getNick()));
}
public void asyncRequest(String nick, Action2<String, String> f) {
userRequests.add(new Request(nick, f));
service.manager.getAnyBot().sendIRC().message("NickServ", String.format("acc %s *", nick));
}
public String syncRequest(User user) {
return syncRequest(user.getNick(), DEFAULT_SYNC_REQUEST_TIMEOUT);
}
public String syncRequest(User user, long timeout) {
return syncRequest(user.getNick(), timeout);
}
public String syncRequest(String nick) {
return syncRequest(nick, DEFAULT_SYNC_REQUEST_TIMEOUT);
}
public String syncRequest(String nick, long timeout) {
CountDownLatch latch = new CountDownLatch(1);
Box<String> box = new Box<>();
asyncRequest(nick, (responseNick, account) -> {
box.value = account;
latch.countDown();
});
try {
latch.await(timeout, TimeUnit.MILLISECONDS);
} catch (Exception e) {
}
return box.value;
}
public void onNickServNotice(String nick, String account) {
userRequests.iterateAndWrite((request, it) -> {
if (request.nick.equals(nick)) {
request.func.call(nick, account);
it.remove();
it.stop();
}
});
}
public void onAccountNotify(String nick, String account) {
putEntry(nick, account);
}
public void onExtendedJoin(String nick, String account) {
putEntry(nick, account);
}
public void onServerResponseEntry(String nick, String account) {
putEntry(nick, account);
}
private void putEntry(String nick, String account) {
cache.put(nick, new Entry(account, getNewEntryExpirationDate()));
}
public void onNickChange(String oldNick, String newNick) {
cache.writeOperation(cache -> {
if (cache.containsKey(oldNick)) {
Entry entry = cache.get(oldNick);
cache.remove(oldNick);
cache.put(newNick, entry);
}
});
}
public void onQuit(String nick) {
cache.remove(nick);
}
private Date getNewEntryExpirationDate() {
return hasWhoX && hasAccountNotify && hasExtendedJoin ? null : new Date(new Date().getTime() + DEFAULT_EXPIRATION_TIME);
}
private static class Entry {
public final String account;
public final Date expirationDate;
public Entry(String account, Date expirationDate) {
this.account = account;
this.expirationDate = expirationDate;
}
public boolean expired() {
return expirationDate == null ? false : Dates.isInPast(expirationDate);
}
}
private static class Request {
public final String nick;
public final Action2<String, String> func;
public Request(String nick, Action2<String, String> f) {
this.nick = nick;
func = f;
}
}
public static class Factory extends IdentMethodFactory {
public Factory() {
super(METHOD_NAME, METHOD_PREFIX);
}
@Override
public IdentMethod create(IdentService service) {
return new NickServIdentMethod(service, this);
}
}
}