// 
// Decompiled by Procyon v0.6.0
// 

package org.jline.builtins.telnet;

import java.io.IOException;
import java.text.MessageFormat;
import java.net.Socket;
import java.util.logging.Level;
import java.util.Iterator;
import java.net.InetAddress;
import java.util.Collections;
import java.util.ArrayList;
import java.util.Stack;
import java.util.List;
import java.util.logging.Logger;

public abstract class ConnectionManager implements Runnable
{
    private static Logger LOG;
    private final List<Connection> openConnections;
    private Thread thread;
    private ThreadGroup threadGroup;
    private Stack<Connection> closedConnections;
    private ConnectionFilter connectionFilter;
    private int maxConnections;
    private int warningTimeout;
    private int disconnectTimeout;
    private int housekeepingInterval;
    private String loginShell;
    private boolean lineMode;
    private boolean stopping;
    
    public ConnectionManager() {
        this.lineMode = false;
        this.stopping = false;
        this.threadGroup = new ThreadGroup(this.toString() + "Connections");
        this.closedConnections = new Stack<Connection>();
        this.openConnections = Collections.synchronizedList(new ArrayList<Connection>(100));
    }
    
    public ConnectionManager(final int con, final int timew, final int timedis, final int hoke, final ConnectionFilter filter, final String lsh, final boolean lm) {
        this();
        this.connectionFilter = filter;
        this.loginShell = lsh;
        this.lineMode = lm;
        this.maxConnections = con;
        this.warningTimeout = timew;
        this.disconnectTimeout = timedis;
        this.housekeepingInterval = hoke;
    }
    
    public ConnectionFilter getConnectionFilter() {
        return this.connectionFilter;
    }
    
    public void setConnectionFilter(final ConnectionFilter filter) {
        this.connectionFilter = filter;
    }
    
    public int openConnectionCount() {
        return this.openConnections.size();
    }
    
    public Connection getConnection(final int idx) {
        synchronized (this.openConnections) {
            return this.openConnections.get(idx);
        }
    }
    
    public Connection[] getConnectionsByAdddress(final InetAddress addr) {
        final ArrayList<Connection> l = new ArrayList<Connection>();
        synchronized (this.openConnections) {
            for (final Connection connection : this.openConnections) {
                if (connection.getConnectionData().getInetAddress().equals(addr)) {
                    l.add(connection);
                }
            }
        }
        final Connection[] conns = new Connection[l.size()];
        return l.toArray(conns);
    }
    
    public void start() {
        (this.thread = new Thread(this)).start();
    }
    
    public void stop() {
        ConnectionManager.LOG.log(Level.FINE, "stop()::" + this.toString());
        this.stopping = true;
        try {
            if (this.thread != null) {
                this.thread.join();
            }
        }
        catch (final InterruptedException iex) {
            ConnectionManager.LOG.log(Level.SEVERE, "stop()", iex);
        }
        synchronized (this.openConnections) {
            for (final Connection tc : this.openConnections) {
                try {
                    tc.close();
                }
                catch (final Exception exc) {
                    ConnectionManager.LOG.log(Level.SEVERE, "stop()", exc);
                }
            }
            this.openConnections.clear();
        }
        ConnectionManager.LOG.log(Level.FINE, "stop():: Stopped " + this.toString());
    }
    
    public void makeConnection(final Socket insock) {
        ConnectionManager.LOG.log(Level.FINE, "makeConnection()::" + insock.toString());
        if (this.connectionFilter == null || this.connectionFilter.isAllowed(insock.getInetAddress())) {
            final ConnectionData newCD = new ConnectionData(insock, this);
            newCD.setLoginShell(this.loginShell);
            newCD.setLineMode(this.lineMode);
            if (this.openConnections.size() < this.maxConnections) {
                final Connection con = this.createConnection(this.threadGroup, newCD);
                final Object[] args = { this.openConnections.size() + 1 };
                ConnectionManager.LOG.info(MessageFormat.format("connection #{0,number,integer} made.", args));
                synchronized (this.openConnections) {
                    this.openConnections.add(con);
                }
                con.start();
            }
        }
        else {
            ConnectionManager.LOG.info("makeConnection():: Active Filter blocked incoming connection.");
            try {
                insock.close();
            }
            catch (final IOException ex) {}
        }
    }
    
    protected abstract Connection createConnection(final ThreadGroup p0, final ConnectionData p1);
    
    @Override
    public void run() {
        try {
            do {
                this.cleanupClosed();
                this.checkOpenConnections();
                Thread.sleep(this.housekeepingInterval);
            } while (!this.stopping);
        }
        catch (final Exception e) {
            ConnectionManager.LOG.log(Level.SEVERE, "run()", e);
        }
        ConnectionManager.LOG.log(Level.FINE, "run():: Ran out " + this.toString());
    }
    
    private void cleanupClosed() {
        if (this.stopping) {
            return;
        }
        while (!this.closedConnections.isEmpty()) {
            final Connection nextOne = this.closedConnections.pop();
            ConnectionManager.LOG.info("cleanupClosed():: Removing closed connection " + nextOne.toString());
            synchronized (this.openConnections) {
                this.openConnections.remove(nextOne);
            }
        }
    }
    
    private void checkOpenConnections() {
        if (this.stopping) {
            return;
        }
        synchronized (this.openConnections) {
            for (final Connection conn : this.openConnections) {
                final ConnectionData cd = conn.getConnectionData();
                if (!conn.isActive()) {
                    this.registerClosedConnection(conn);
                }
                else {
                    final long inactivity = System.currentTimeMillis() - cd.getLastActivity();
                    if (inactivity <= this.warningTimeout) {
                        continue;
                    }
                    if (inactivity > this.disconnectTimeout + this.warningTimeout) {
                        ConnectionManager.LOG.log(Level.FINE, "checkOpenConnections():" + conn.toString() + " exceeded total timeout.");
                        conn.processConnectionEvent(new ConnectionEvent(conn, ConnectionEvent.Type.CONNECTION_TIMEDOUT));
                    }
                    else {
                        if (cd.isWarned()) {
                            continue;
                        }
                        ConnectionManager.LOG.log(Level.FINE, "checkOpenConnections():" + conn.toString() + " exceeded warning timeout.");
                        cd.setWarned(true);
                        conn.processConnectionEvent(new ConnectionEvent(conn, ConnectionEvent.Type.CONNECTION_IDLE));
                    }
                }
            }
        }
    }
    
    public void registerClosedConnection(final Connection con) {
        if (this.stopping) {
            return;
        }
        if (!this.closedConnections.contains(con)) {
            ConnectionManager.LOG.log(Level.FINE, "registerClosedConnection()::" + con.toString());
            this.closedConnections.push(con);
        }
    }
    
    public int getDisconnectTimeout() {
        return this.disconnectTimeout;
    }
    
    public void setDisconnectTimeout(final int disconnectTimeout) {
        this.disconnectTimeout = disconnectTimeout;
    }
    
    public int getHousekeepingInterval() {
        return this.housekeepingInterval;
    }
    
    public void setHousekeepingInterval(final int housekeepingInterval) {
        this.housekeepingInterval = housekeepingInterval;
    }
    
    public boolean isLineMode() {
        return this.lineMode;
    }
    
    public void setLineMode(final boolean lineMode) {
        this.lineMode = lineMode;
    }
    
    public String getLoginShell() {
        return this.loginShell;
    }
    
    public void setLoginShell(final String loginShell) {
        this.loginShell = loginShell;
    }
    
    public int getMaxConnections() {
        return this.maxConnections;
    }
    
    public void setMaxConnections(final int maxConnections) {
        this.maxConnections = maxConnections;
    }
    
    public int getWarningTimeout() {
        return this.warningTimeout;
    }
    
    public void setWarningTimeout(final int warningTimeout) {
        this.warningTimeout = warningTimeout;
    }
    
    static {
        ConnectionManager.LOG = Logger.getLogger(ConnectionManager.class.getName());
    }
}
