/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package org.jboss.as.host.controller;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import org.jboss.as.domain.controller.HostConnectionInfo;
import org.jboss.as.host.controller.mgmt.SlaveHostPinger;

/**
 * @author Emanuel Muckenhuber
 */
public class DomainSlaveHostRegistrations {

    // Keep entries for 1 week by default
    private static final long TTL = TimeUnit.DAYS.toMillis(7);

    private final Map<String, DomainHostConnection> registrations = new ConcurrentHashMap<>();

    protected void registerHost(final String hostName, SlaveHostPinger pinger, String address) {
        synchronized (this) {
            DomainHostConnection registration = registrations.get(hostName);
            final List<HostConnectionInfo.Event> events;
            if (registration == null) {
                events = new ArrayList<>();
            } else {
                events = registration.events;
            }
            events.add(HostConnectionInfo.Events.create(HostConnectionInfo.EventType.REGISTERED, address));
            registration = new DomainHostConnection(hostName, pinger, address, events);
            registrations.put(hostName, registration);
        }
    }

    protected boolean unregisterHost(final String hostName, HostConnectionInfo.Event event) {
        synchronized (this) {
            DomainHostConnection registration = registrations.get(hostName);
            final List<HostConnectionInfo.Event> events;
            if (registration == null) {
                return false;
            } else {
                events = registration.events;
            }
            events.add(event);
            registration = new DomainHostConnection(hostName, events);
            registrations.put(hostName, registration);
        }
        return true;
    }

    protected boolean contains(final String host) {
        return registrations.containsKey(host);
    }

    protected Set<String> getHosts() {
        return registrations.keySet();
    }

    protected DomainHostConnection getRegistration(final String hostName) {
        return registrations.get(hostName);
    }

    protected void addEvent(String hostName, HostConnectionInfo.Event event) {
        synchronized (this) {
            DomainHostConnection registration = registrations.get(hostName);
            if (registration == null) {
                registration = new DomainHostConnection(hostName);
                registrations.put(hostName, registration);
            }
            final List<HostConnectionInfo.Event> events = new ArrayList<>(registration.events);
            events.add(event);
            registration.events = events;
        }
    }

    public void pruneExpired() {
        evictEntries(EXPIRED);
    }

    public void pruneDisconnected() {
        evictEntries(DISCONNECTED);
    }

    protected void evictEntries(EvictionPolicy policy) {
        synchronized (this) {
            final Iterator<DomainHostConnection> i = registrations.values().iterator();
            while (i.hasNext()) {
                final DomainHostConnection registration = i.next();
                if (policy.evictEntry(registration)) {
                    i.remove();
                }
            }
        }
    }

    abstract static class EvictionPolicy {

        abstract boolean evictEntry(DomainHostConnection entry);

    }

    private static final EvictionPolicy EXPIRED = new EvictionPolicy() {

        @Override
        boolean evictEntry(DomainHostConnection entry) {
            final long expired = System.currentTimeMillis() - TTL;
            final List<HostConnectionInfo.Event> events = entry.events;
            final List<HostConnectionInfo.Event> newEvents = new ArrayList<>();
            for (final HostConnectionInfo.Event event : events) {
                if (event.getTimestamp() >= expired) {
                    newEvents.add(event);
                }
            }
            entry.events = newEvents;
            if (! newEvents.isEmpty()) {
                return false;
            } else {
                return ! entry.isConnected();
            }
        }
    };

    private static final EvictionPolicy DISCONNECTED = new EvictionPolicy() {

        @Override
        boolean evictEntry(DomainHostConnection entry) {
            return ! entry.isConnected();
        }
    };

    static class DomainHostConnection implements HostConnectionInfo, Comparable<HostConnectionInfo> {

        private final String hostName;

        private final String address;
        private final SlaveHostPinger pinger;
        private volatile boolean connected;
        private volatile List<Event> events;

        DomainHostConnection(String hostName) {
            this(hostName, new ArrayList<Event>());
        }

        DomainHostConnection(String hostName, List<Event> events) {
            this.hostName = hostName;
            this.connected = false;
            this.address = null;
            this.pinger = null;
            this.events = events;
        }

        DomainHostConnection(String hostName, SlaveHostPinger pinger, String address, List<Event> events) {
            this.hostName = hostName;
            this.pinger = pinger;
            this.events = events;
            this.address = address;
            this.connected = true;
        }

        @Override
        public String getHostName() {
            return hostName;
        }

        @Override
        public boolean isConnected() {
            return connected;
        }

        @Override
        public List<Event> getEvents() {
            return events;
        }

        protected String getAddress() {
            return address;
        }

        protected Long getRemoteConnectionId() {
            if (pinger != null) {
                return pinger.getRemoteConnectionID();
            } else {
                return null;
            }
        }

        protected SlaveHostPinger getPinger() {
            return pinger;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            final DomainHostConnection that = (DomainHostConnection) o;
            if (!hostName.equals(that.hostName)) return false;
            return true;
        }

        @Override
        public int hashCode() {
            return hostName.hashCode();
        }

        @Override
        public int compareTo(HostConnectionInfo o) {
            return hostName.compareTo(o.getHostName());
        }
    }
}
