package edu.sc.seis.sod;

import java.time.Duration;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Timer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import edu.sc.seis.seisFile.fdsnws.stationxml.Channel;
import edu.sc.seis.seisFile.fdsnws.stationxml.Network;
import edu.sc.seis.seisFile.fdsnws.stationxml.Station;
import edu.sc.seis.sod.hibernate.NetworkDB;
import edu.sc.seis.sod.hibernate.NotFound;
import edu.sc.seis.sod.hibernate.SodDB;
import edu.sc.seis.sod.model.common.TimeRange;
import edu.sc.seis.sod.model.station.ChannelGroup;
import edu.sc.seis.sod.model.station.ChannelIdUtil;
import edu.sc.seis.sod.model.station.StationIdUtil;
import edu.sc.seis.sod.model.status.Stage;
import edu.sc.seis.sod.model.status.Standing;
import edu.sc.seis.sod.model.status.Status;
import edu.sc.seis.sod.source.SodSourceException;
import edu.sc.seis.sod.source.event.EventSource;
import edu.sc.seis.sod.source.network.FdsnStation;
import edu.sc.seis.sod.source.network.InstrumentationFromDB;
import edu.sc.seis.sod.source.network.LoadedNetworkSource;
import edu.sc.seis.sod.source.network.NetworkQueryConstraints;
import edu.sc.seis.sod.source.network.NetworkSource;
import edu.sc.seis.sod.source.network.RetryNetworkSource;
import edu.sc.seis.sod.status.Fail;
import edu.sc.seis.sod.status.StringTree;
import edu.sc.seis.sod.status.networkArm.NetworkMonitor;
import edu.sc.seis.sod.subsetter.channel.ChannelEffectiveTimeOverlap;
import edu.sc.seis.sod.subsetter.channel.ChannelSubsetter;
import edu.sc.seis.sod.subsetter.channel.PassChannel;
import edu.sc.seis.sod.subsetter.network.NetworkEffectiveTimeOverlap;
import edu.sc.seis.sod.subsetter.network.NetworkSubsetter;
import edu.sc.seis.sod.subsetter.network.PassNetwork;
import edu.sc.seis.sod.subsetter.station.PassStation;
import edu.sc.seis.sod.subsetter.station.StationEffectiveTimeOverlap;
import edu.sc.seis.sod.subsetter.station.StationSubsetter;
import edu.sc.seis.sod.util.exceptionHandler.GlobalExceptionHandler;
import edu.sc.seis.sod.util.time.ClockUtil;

public class NetworkArm implements Arm {

    public NetworkArm(Element config) throws ConfigurationException {
        channelGrouper = new ChannelGrouper(Start.getRunProps().getChannelGroupingRules());
        processConfig(config);
        refresh = new RefreshNetworkArm(this);
    }

    public void run() {
        try {
            SodDB sodDb = SodDB.getSingleton();
            // lastQueryTime should be null if first time
            lastQueryTime = sodDb.getQueryTime(getInternalNetworkSource().getName());
            
            // only do timer if positive interval and waveform arm exists, otherwise run in thread
            if (getRefreshInterval().toNanos() > 0 && Start.getWaveformRecipe() != null) {
                Timer timer = new Timer("Refresh NetworkArm", true);
                long period = (long)getInternalNetworkSource().getRefreshInterval().toMillis();
                long firstDelay = lastQueryTime==null ? 0 : lastQueryTime.delayMillisUntilNextRefresh(getRefreshInterval());
                logger.debug("Refresh timer startup: period: "+period+"  firstDelay: "+firstDelay+"  last query: "+(lastQueryTime==null ? "null" : lastQueryTime.getTime()));
                timer.schedule(refresh, firstDelay, period);
                if (period == 0) {
                    try { Thread.sleep(10);  } catch(InterruptedException e) { } // give refresh time to start up
                }
                initialStartupFinished = true;
            } else {
                // no refresh, so do net arm in this thread once
                initialStartupFinished = true;
                refresh.run();
            }
            // make sure any open read only db connetion is closed (ie from lastQueryTime above)
            SodDB.rollback();
        } catch(Throwable e) {
            armFinished = true;
            Start.armFailure(this, e);
        }
    }

    public boolean isActive() {
        return !armFinished;
    }

    public String getName() {
        return "NetworkArm";
    }

    private void processConfig(Element config) throws ConfigurationException {
        NodeList children = config.getChildNodes();
        for(int i = 0; i < children.getLength(); i++) {
            Node node = children.item(i);
            if(node instanceof Element) {
                loadConfigElement(SodUtil.load((Element)node, PACKAGES));
            } // end of if (node instanceof Element)
        } // end of for (int i=0; i<children.getSize(); i++)
        
        TimeRange timeRange = configureEffectiveTimeCheckers();
        NetworkQueryConstraints constraints = new NetworkQueryConstraints(attrSubsetter,
                                                                          stationSubsetter,
                                                                          chanSubsetters,
                                                                          timeRange);
        getNetworkSource().setConstraints(constraints);
    }

    private TimeRange configureEffectiveTimeCheckers() {
        EventArm arm = Start.getEventArm();
        if(arm != null && !Start.getRunProps().allowDeadNets()) {
            EventSource[] sources = arm.getSources();
            TimeRange fullTime = sources[0].getEventTimeRange();
            for(int i = 1; i < sources.length; i++) {
                fullTime = new TimeRange(fullTime,
                                                    sources[i].getEventTimeRange());
            }
            TimeRange eventQueryTimes = fullTime;
            netEffectiveSubsetter = new NetworkEffectiveTimeOverlap(eventQueryTimes);
            staEffectiveSubsetter = new StationEffectiveTimeOverlap(eventQueryTimes);
            chanEffectiveSubsetter = new ChannelEffectiveTimeOverlap(eventQueryTimes);
            return eventQueryTimes;
        } else {
            logger.debug("No implicit effective time constraint");
            return null;
        }
    }

    public static final String[] PACKAGES = {"networkArm",
                                             "channel",
                                             "site",
                                             "station",
                                             "network"};

    private void loadConfigElement(Object sodElement)
            throws ConfigurationException {
        if(sodElement instanceof NetworkSource) {
            internalFinder = new RetryNetworkSource((NetworkSource)sodElement);
            finder = new InstrumentationFromDB(internalFinder);
        } else if(sodElement instanceof NetworkSubsetter) {
            attrSubsetter = (NetworkSubsetter)sodElement;
        } else if(sodElement instanceof StationSubsetter) {
            stationSubsetter = (StationSubsetter)sodElement;
        } else if(sodElement instanceof ChannelSubsetter) {
            chanSubsetters.add((ChannelSubsetter)sodElement);
        } else {
            throw new ConfigurationException("Unknown configuration object: "
                    + sodElement.getClass());
        }
    }

    public void add(NetworkMonitor monitor) {
        synchronized(statusMonitors) {
            statusMonitors.add(monitor);
        }
    }

    public Duration getRefreshInterval() {
        return getInternalNetworkSource().getRefreshInterval();
    }

    public List<ChannelSubsetter> getChannelSubsetters() {
        return chanSubsetters;
    }
    
    /**
     * returns an array of SuccessfulNetworks. if the refreshInterval is valid
     * it gets the networks from the database(may be embedded or external). if
     * not it gets the networks again from the network server specified in the
     * networkFinder. After obtaining the Networks if processes them using the
     * NetworkSubsetter and returns the successful networks as an array of
     * NetworkDbObjects.
     * 
     */
    public List<Network> getSuccessfulNetworks() {
        synchronized(refresh) {
            /** null means that we have not yet gotten nets from the server, so wait. */
            List<Network> cacheNets = loadNetworksFromDB();
            while (cacheNets == null && lastQueryTime == null && ! Start.isArmFailure()) {
                // still null, maybe first time through
                logger.info("Waiting on initial network load");
                refresh.notifyAll();
                try {
                    refresh.wait(1000);
                } catch(InterruptedException e) {
                }
                cacheNets = loadNetworksFromDB();
            }
            return cacheNets;
        }
    }
    
    List<Network> loadNetworksFromDB() {
        synchronized(netGetSync) { // don't get nets while being reloaded
        List<Network> fromDB = getNetworkDB().getAllNetworks();
        for (Network net : fromDB) {
            // this is for the side effect of creating
            // networkInfoTemplate stuff
            // avoids a null ptr later
            change(net,
                   Status.get(Stage.NETWORK_SUBSETTER,
                              Standing.SUCCESS));
        }
        return fromDB;
        }
    }

    List<Network> getSuccessfulNetworksFromServer() throws SodSourceException {
        synchronized(netGetSync) {
            statusChanged("Getting networks");
            logger.info("Getting networks from server");
            ArrayList<Network> successes = new ArrayList<Network>();
            List<? extends Network> allNets = getInternalNetworkSource().getNetworks();
            logger.info("Found " + allNets.size() + " networks");
            int i=0;
            for (Network attr : allNets) {
                try {
                    if(netEffectiveSubsetter.accept(attr).isSuccess()) {
                        StringTree result;
                        try {
                            result = attrSubsetter.accept(attr);
                        } catch(Throwable t) {
                            logger.debug("Network subsetter exception: ", t);
                            result = new Fail(attrSubsetter, "Exception", t);
                        }
                        if(result.isSuccess()) {
                            NetworkDB ndb = getNetworkDB();
                            int dbid = ndb.put(attr);
                            NetworkDB.commit();
                            logger.info("store network: " + attr.toString()+" " + attr.getDbid()
                                    + " " + dbid);
                            successes.add(attr);
                            change(attr,
                                   Status.get(Stage.NETWORK_SUBSETTER,
                                              Standing.SUCCESS));
                        } else {
                            change(attr,
                                   Status.get(Stage.NETWORK_SUBSETTER,
                                              Standing.REJECT));
                            failLogger.info(attr.toString()
                                    + " was rejected. "+result);
                        }
                    } else {
                        change(attr, Status.get(Stage.NETWORK_SUBSETTER,
                                                      Standing.REJECT));
                        failLogger.info(attr.toString()
                                + " was rejected because it wasn't active during the time range of requested events");
                    }
                } catch(Throwable th) {
                    GlobalExceptionHandler.handle("Got an exception while trying getSuccessfulNetworks for the "
                                                          + i
                                                          + "th networkAccess ("
                                                          +(attr==null?"null":attr.toString()),
                                                  th);
                }
                i++;
            }
            if(lastQueryTime == null && allNets.size() == 0) {
                // hard to imagine a network arm that can't find a single good network is useful, so just fail
                // only fail if first time (lastQueryTime==null) so a server fail
                // during a run will not cause a crash
                logger.warn(NO_NETWORKS_MSG);
                Start.simpleArmFailure(this, NO_NETWORKS_MSG);
            }
            logger.info(successes.size() + " networks passed");
            statusChanged("Waiting for a request");
            synchronized(refresh) {
                refresh.notifyAll();
            }
            return successes;
        }
    }


    void finish() {
        armFinished = true;
        lastQueryTime = new QueryTime(getInternalNetworkSource().getName(), ClockUtil.now());
        SodDB.getSingleton().putQueryTime(lastQueryTime);
        SodDB.commit();
        logger.info("Network arm finished.");
        for (ArmListener listener : armListeners) {
            listener.finished(this);
        }
        if (Start.getEventArm() != null && Start.getEventArm().getWaveformArmSync() != null) {
            synchronized(Start.getEventArm().getWaveformArmSync()) {
                Start.getEventArm().getWaveformArmSync().notifyAll();
            }
        }
    }

    public void add(ArmListener listener) {
        armListeners.add(listener);
        if(armFinished == true) {
            listener.finished(this);
        }
    }

    public Station[] getSuccessfulStations(Network net) {
        String netCode = net.getCode();
        logger.debug("getSuccessfulStations: "+net.getCode());
        synchronized(refresh) {
            while(refresh.isNetworkBeingReloaded(net.getDbid())) {
                try {
                    refresh.notifyAll();
                    refresh.wait();
                } catch(InterruptedException e) {}
            }
            if(allStationFailureNets.contains(net.toString())) {
                return new Station[0];
            }
            // try db
            List<Station> sta = getNetworkDB().getStationForNet((Network)net);
            if(sta.size() != 0) {
                logger.debug("getSuccessfulStations " + netCode + " - from db "
                        + sta.size());
                return sta.toArray(new Station[0]);
            } else {
                allStationFailureNets.add(net.toString());
                return new Station[0];
            }
        }
    }
    
    Station[] getSuccessfulStationsFromServer(Network net) {
        synchronized(this) {
            Network netAttr;
            try {
                netAttr = NetworkDB.getSingleton().getNetwork(net.getDbid());
            } catch(NotFound e1) {
                // must not be in db yet???
                throw new RuntimeException("Network not in db yet: "+net.toString());
            }
            statusChanged("Getting stations for "
                    + net.getNetworkId());
            ArrayList<Station> arrayList = new ArrayList<Station>();
            try {
                List<? extends Station> stations = getInternalNetworkSource().getStations(netAttr);
                /*
                 // network consistency for TA take a really long time due to pairwise comparison.
                if( ! NetworkConsistencyCheck.isConsistent(stations)) {
                    failLogger.warn("Inconsistent stations for network: "+NetworkIdUtil.toString(net));
                }
                */
                for (Station stationImpl : stations) {
                    logger.debug("Station in NetworkArm: "
                            + StationIdUtil.toString(stationImpl));
                }
                for (Station currStation : stations) {
                    /*
                     //disable for speed reasons
                    if (! NetworkConsistencyCheck.isConsistent(net, currStation)) {
                        failLogger.warn("Not consistent: "+StationIdUtil.toString(currStation.getId()));
                    }*/
                    // hibernate gets angry if the network isn't the same object
                    // as the one already in the thread's session
                    currStation.setNetwork(netAttr);
                    StringTree effResult = staEffectiveSubsetter.accept(currStation,
                                                                        getNetworkSource());
                    if(effResult.isSuccess()) {
                        StringTree staResult;
                        try {
                            staResult = stationSubsetter.accept(currStation,
                                                                getNetworkSource());
                        } catch(Throwable t) {
                            logger.debug("Station subsetter exception: ", t);
                            staResult = new Fail(stationSubsetter, "Exception", t);
                        }
                        if(staResult.isSuccess()) {
                            int dbid = getNetworkDB().put(currStation);
                            logger.info("Store " + currStation.getCode()
                                        + " as " + dbid + " in " + getNetworkDB());
                                arrayList.add(currStation);
                            change(currStation,
                                   Status.get(Stage.NETWORK_SUBSETTER,
                                              Standing.SUCCESS));
                        } else {
                            change(currStation,
                                   Status.get(Stage.NETWORK_SUBSETTER,
                                              Standing.REJECT));
                            failLogger.info(StationIdUtil.toString(currStation)
                                    + " was rejected: " + staResult);
                        }
                    } else {
                        change(currStation, Status.get(Stage.NETWORK_SUBSETTER,
                                                       Standing.REJECT));
                        failLogger.info(StationIdUtil.toString(currStation)
                                + " was rejected because the station was not active during the time range of requested events: "
                                + effResult);
                    }
                }
                NetworkDB.commit();
            } catch(Exception e) {
                GlobalExceptionHandler.handle("Problem in method getSuccessfulStations for net "
                                                      + net.toString(),
                                              e);
                NetworkDB.rollback();
            }
            Station[] rtnValues = new Station[arrayList.size()];
            rtnValues = (Station[])arrayList.toArray(rtnValues);
            statusChanged("Waiting for a request");
            logger.debug("getSuccessfulStations " + net.toString() + " - from server "
                    + rtnValues.length);
            if(rtnValues.length == 0) {
                allStationFailureNets.add(net.toString());
            } else {
                allStationFailureNets.remove(net.toString());
            }
            return rtnValues;
        }
    }

    /**
     * Obtains the Channels corresponding to the station, processes them using
     * the ChannelSubsetter and returns an array of those that pass
     * 
     */
    public List<Channel> getSuccessfulChannels(Station station) {
        synchronized(refresh) {
            while(refresh.isNetworkBeingReloaded(((Network)station.getNetwork()).getDbid())
                    || refresh.isStationBeingReloaded(station.getDbid())) {
                try {
                    refresh.notifyAll();
                    refresh.wait();
                } catch(InterruptedException e) {}
            }
            if(allChannelFailureStations.contains(StationIdUtil.toStringNoDates(station))) {
                return new ArrayList<Channel>(0);
            }
            // no dice, try db
            List<Channel> sta = getNetworkDB().getChannelsForStation(station);
            if(sta.size() != 0) {
                logger.debug("successfulChannels " + station.getCode()
                        + " - from db " + sta.size());
                return sta;
            } else {
                allChannelFailureStations.add(StationIdUtil.toStringNoDates(station));
                return new ArrayList<Channel>(0);
            }
        }
    }
    
    List<Channel> getSuccessfulChannelsFromServer(Station station, LoadedNetworkSource loadedNetworkSource) {
        synchronized(this) {
            statusChanged("Getting channels for " + station);
            List<Channel> successes = new ArrayList<Channel>();
            try {               
                List<? extends Channel> chansAtStation = loadedNetworkSource.getChannels(station);
                Status inProg = Status.get(Stage.NETWORK_SUBSETTER,
                                           Standing.IN_PROG);
                boolean needCommit = false;
                Station dbSta = NetworkDB.getSingleton().getStation(station.getDbid());
                for (Channel chan : chansAtStation) {
                    /*
                     // consistency check takes time and only does a warning, skip
                    if (! NetworkConsistencyCheck.isConsistent(station, chan)) {
                        failLogger.warn("Not consistent: "+ChannelIdUtil.toString(chan.getId()));
                    }
                    */
                    // make the assumption that the station in the channel is the same as the station retrieved earlier
                    chan.setStation(dbSta);
                    change(chan, inProg);
                    StringTree effectiveTimeResult = chanEffectiveSubsetter.accept(chan,
                                                                                   loadedNetworkSource);
                    NetworkDB.flush();
                    if(effectiveTimeResult.isSuccess()) {
                        NetworkDB.flush();
                        boolean accepted = true;
                        synchronized(chanSubsetters) {
                            for (ChannelSubsetter cur : chanSubsetters) {
                                StringTree result;
                                try {
                                    result = cur.accept(chan,
                                                        loadedNetworkSource);
                                } catch(Throwable t) {
                                    logger.debug("Channel subsetter exception: ", t);
                                    result = new Fail(cur, "Exception", t);
                                }
                                if(!result.isSuccess()) {
                                    change(chan,
                                           Status.get(Stage.NETWORK_SUBSETTER,
                                                      Standing.REJECT));
                                    String resultToString = result.toString();
                                    if (result.toString().trim().length() == 0) {
                                        resultToString = cur.getClass().getName();
                                    }
                                    failLogger.info("Rejected "
                                            + ChannelIdUtil.toString(chan)
                                            + ": " + resultToString);
                                    accepted = false;
                                    break;
                                }
                            }
                        }
                        if(accepted) {
                            getNetworkDB().put(chan);
                            logger.debug("Accept "+ChannelIdUtil.toString(chan));
                            needCommit = true;
                            successes.add(chan);
                            change(chan, Status.get(Stage.NETWORK_SUBSETTER,
                                                    Standing.SUCCESS));
                        }
                    } else {
                        change(chan, Status.get(Stage.NETWORK_SUBSETTER,
                                                Standing.REJECT));
                        failLogger.info(ChannelIdUtil.toString(chan)
                                + " was rejected because the channel was not active during the time range of requested events: "
                                + effectiveTimeResult);
                    }
                }
                if(needCommit) {
                    NetworkDB.commit();
                }
            } catch(Throwable e) {
                GlobalExceptionHandler.handle("Problem in method getSuccessfulChannels for "
                                                      + StationIdUtil.toString(station),
                                              e);
                NetworkDB.rollback();
            }
            statusChanged("Waiting for a request");
            if(successes.size() == 0) {
                allChannelFailureStations.add(StationIdUtil.toStringNoDates(station));
            } else {
                allChannelFailureStations.remove(StationIdUtil.toStringNoDates(station));
            }
            return successes;
        }
    }

    public List<ChannelGroup> getSuccessfulChannelGroups(Station station) {
        if(! refresh.isNetworkBeingReloaded(((Network)station.getNetwork()).getDbid())
                && allChannelGroupFailureStations.contains(StationIdUtil.toStringNoDates(station))) {
            return new ArrayList<ChannelGroup>(0);
        }
        synchronized(refresh) {
            while(refresh.isNetworkBeingReloaded(((Network)station.getNetwork()).getDbid())
                    && refresh.isStationBeingReloaded(station.getDbid())) {
                try {
                    refresh.notifyAll();
                    refresh.wait();
                } catch(InterruptedException e) {}
            }
            if(allChannelGroupFailureStations.contains(StationIdUtil.toStringNoDates(station))) {
                return new ArrayList<ChannelGroup>(0);
            }
            // no dice, try db
            List<ChannelGroup> sta = getNetworkDB().getChannelGroupsForStation(station);
            if(sta.size() != 0) {
                logger.debug("successfulChannelGroups " + station.getCode()
                        + " - from db " + sta.size());
                return sta;
            } else {
                allChannelGroupFailureStations.add(StationIdUtil.toStringNoDates(station));
                return new ArrayList<ChannelGroup>(0);
            }
        }
    }
    
    List<ChannelGroup> getSuccessfulChannelGroupsFromServer(Station station, LoadedNetworkSource net) {
        synchronized(this) {
            List<Channel> failures = new ArrayList<Channel>();
            List<ChannelGroup> chanGroups = channelGrouper.group(getSuccessfulChannelsFromServer(station, net),
                                                             failures);
            for(ChannelGroup cg : chanGroups) {
                getNetworkDB().put(cg);
            }
            if (chanGroups.size() != 0) {
                NetworkDB.commit();
                allChannelGroupFailureStations.remove(StationIdUtil.toStringNoDates(station));
            } else {
                allChannelGroupFailureStations.add(StationIdUtil.toStringNoDates(station));
            }
            for (Channel failchan : failures) {
                failLogger.info(ChannelIdUtil.toString(failchan)
                        + "  Channel not grouped into 3 components.");
            }
            return chanGroups;
        }
    }

    private void statusChanged(String newStatus) {
        synchronized(statusMonitors) {
            for (NetworkMonitor netMon : statusMonitors) {
                try {
                    netMon.setArmStatus(newStatus);
                } catch(Throwable e) {
                    // caught for one, but should continue with rest after
                    // logging
                    // it
                    GlobalExceptionHandler.handle("Problem changing status in NetworkArm",
                                                  e);
                }
            }
        }
    }

    private void change(Channel chan, Status newStatus) {
        synchronized(statusMonitors) {
            for (NetworkMonitor netMon : statusMonitors) {
                try {
                    netMon.change(chan, newStatus);
                } catch(Throwable e) {
                    // caught for one, but should continue with rest after
                    // logging
                    // it
                    GlobalExceptionHandler.handle("Problem changing channel status in NetworkArm",
                                                  e);
                }
            }
        }
    }

    private void change(Station sta, Status newStatus) {
        synchronized(statusMonitors) {
            for (NetworkMonitor netMon : statusMonitors) {
                try {
                    netMon.change(sta, newStatus);
                } catch(Throwable e) {
                    // caught for one, but should continue with rest after
                    // logging
                    // it
                    GlobalExceptionHandler.handle("Problem changing station status in NetworkArm",
                                                  e);
                }
            }
        }
    }

    private void change(Network na, Status newStatus) {
        synchronized(statusMonitors) {
            for (NetworkMonitor netMon : statusMonitors) {
                try {
                    netMon.change(na, newStatus);
                } catch(Throwable e) {
                    // caught for one, but should continue with rest after
                    // logging
                    // it
                    GlobalExceptionHandler.handle("Problem changing network status in NetworkArm",
                                                  e);
                }
            }
        }
    }

    private NetworkSource internalFinder;

    private NetworkSource finder;
    
    private NetworkSubsetter attrSubsetter = new PassNetwork();

    private NetworkSubsetter netEffectiveSubsetter = new PassNetwork();

    private StationSubsetter stationSubsetter = new PassStation();

    private StationSubsetter staEffectiveSubsetter = new PassStation();

    private List<ChannelSubsetter> chanSubsetters = new ArrayList<ChannelSubsetter>();

    private ChannelSubsetter chanEffectiveSubsetter = new PassChannel();

    private ChannelGrouper channelGrouper;


    public NetworkSource getNetworkSource() {
        if (finder == null) {
            finder = new InstrumentationFromDB(getInternalNetworkSource());
        }
        return finder;
    }

     protected NetworkSource getInternalNetworkSource() {
         if (internalFinder == null) {
             internalFinder = new RetryNetworkSource(new FdsnStation()); 
         }
        return internalFinder;
    }
    
    protected NetworkDB getNetworkDB() {
        return NetworkDB.getSingleton();
    }
    
    public boolean isBeingRefreshed(Network net) {
        return refresh.isNetworkBeingReloaded(net.getDbid());
    }
    
    public boolean isBeingRefreshed(Station sta) {
        return refresh.isStationBeingReloaded(sta.getDbid());
    }
    
    public RefreshNetworkArm getRefresher() {
        return refresh;
    }

    private HashSet<String> allStationFailureNets = new HashSet<String>();

    private HashSet<String> allChannelFailureStations = new HashSet<String>();
    
    private HashSet<String> allChannelGroupFailureStations = new HashSet<String>();

    private List<NetworkMonitor> statusMonitors = new ArrayList<NetworkMonitor>();

    private List<ArmListener> armListeners = new ArrayList<ArmListener>();

    private static Logger logger = LoggerFactory.getLogger(NetworkArm.class);

    private static final org.slf4j.Logger failLogger = org.slf4j.LoggerFactory.getLogger("Fail.NetworkArm");

    private boolean armFinished = false;
    
    private boolean initialStartupFinished = false;
    
    
    public boolean isInitialStartupFinished() {
        return initialStartupFinished;
    }

    RefreshNetworkArm refresh;
    
    private QueryTime lastQueryTime = null;
    
    final Object netGetSync = new Object();

    final Object staGetSync = new Object();

    final Object chanGetSync = new Object();

    public ChannelGrouper getChannelGrouper() {
        return channelGrouper;
    }
    
    public static final String NO_NETWORKS_MSG = "Found no networks.  Make sure the network codes you entered are valid. "
            +"This can also be caused by asking for a restricted networks without <includeRestricted>true</includeRestricted> in a <fdsnStation> network source.";
            
    
}// NetworkArm
