package be.iminds.ilabt.jfed.highlevel.model;

import be.iminds.ilabt.jfed.log.ApiCallDetails;
import be.iminds.ilabt.jfed.lowlevel.GeniAMResponseCode;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import be.iminds.ilabt.jfed.lowlevel.api.AggregateManager2;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.ErrorDetails;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.StatusDetails;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.*;
import be.iminds.ilabt.jfed.rspec.model.ModelRspecType;
import be.iminds.ilabt.jfed.rspec.rspec_source.ManifestRspecSource;
import be.iminds.ilabt.jfed.util.GeniUrn;
import be.iminds.ilabt.jfed.util.RFC3339Util;
import javafx.application.Platform;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.text.ParseException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * SfaModelSliceAuthorityListener: this watches for SliceAuthority calls and fills in SfaModel using the info in them
 */
public class SfaModelAggregateManager2Listener extends SfaModelAbstractListener {
    private final static org.slf4j.Logger LOG = LoggerFactory.getLogger(SfaModelAggregateManager2Listener.class);
    //private static boolean debug = false;

    private final TestbedInfoSource testbedInfoSource;

    public SfaModelAggregateManager2Listener(SfaModel sfaModel, TestbedInfoSource testbedInfoSource) {
        super(sfaModel);
        this.testbedInfoSource = testbedInfoSource;
    }

    private List<String> getSliceUrns(List<String> urns) {
        List<String> res = new ArrayList<>();
        for (String u : urns) {
            GeniUrn geniUrn = GeniUrn.parse(u);
            if (geniUrn != null && Objects.equals(geniUrn.getEncodedResourceType(), "slice"))
                res.add(u);
        }
        return res;
    }

    private List<String> getSliverUrns(List<String> urn) {
        List<String> res = new ArrayList<>();
        for (String u : urn) {
            GeniUrn geniUrn = GeniUrn.parse(u);
            if (geniUrn != null && Objects.equals(geniUrn.getEncodedResourceType(), "sliver"))
                res.add(u);
        }
        return res;
    }

    private void onGetVersionResult(ApiCallDetails result) {
        if (result.getReply().getGeniResponseCode().isSuccess()) {
            //TODO
        }
    }

    public void onStatusResult(String sliceUrnStr, AggregateManager2.SliverStatus statusInfo, Server auth) {
        GeniUrn sliceUrn;
        try {
            sliceUrn = new GeniUrn(sliceUrnStr);
        } catch (GeniUrn.GeniUrnParseException e) {
            LOG.error("Cannot process StatusResult with invalid slice-urn {}", sliceUrnStr, e);
            return;
        }
        Slice slice = getModel().logExistSlice(sliceUrn);
        assert slice != null;

        getModel().logExistAtLeastOneSliver(sliceUrn, auth, Sliver.Source.AGGREGATE_MANAGER_STATUS); //since success is returned, the sliver must exist

        assert statusInfo.getStatus() != null;

        Set<GeniUrn> mentionedSliverUrns = new HashSet<>();
        List<Sliver> slivers = new ArrayList<>();

        /* We don't use the si.getGlobalStatus() because it is not always trustworthy (see FEDIBBTDEV-2178).
        We will however log a warning when it doesn't match the expected value in the end!*/
        StatusDetails globalStatus = new StatusDetails(StatusDetails.SliverStatus.READY);
        for (AggregateManager2.SliverStatus.ResourceStatus resourceStatus : statusInfo.getResources()) {
            StatusDetails resourceStatusDetails = new StatusDetails(resourceStatus.getStatus());

            globalStatus.mergeGlobalStatus(resourceStatusDetails.getGlobalStatus());

            //fun stuff: urn could be sliver or component here. We need to handle both cases.
            GeniUrn urn = GeniUrn.parse(resourceStatus.getUrn());
            String sliverUrn;
            String componentUrn;
            if (urn != null && Objects.equals(urn.getEncodedResourceType(), "sliver")) {
                mentionedSliverUrns.add(urn);
                sliverUrn = resourceStatus.getUrn();
                componentUrn = null;
                globalStatus.addSliverStatus(sliverUrn, resourceStatus.getStatus());
                resourceStatusDetails.addSliverStatus(sliverUrn, resourceStatus.getStatus());
            } else {
                sliverUrn = null;
                componentUrn = resourceStatus.getUrn();
                globalStatus.addComponentStatus(componentUrn, resourceStatus.getStatus());
                resourceStatusDetails.addComponentStatus(componentUrn, resourceStatus.getStatus());
            }

            if (resourceStatus.getError() != null && !resourceStatus.getError().isEmpty()) {
                globalStatus.addError(new ErrorDetails(resourceStatus.getError(), sliverUrn, componentUrn));
                resourceStatusDetails.addError(new ErrorDetails(resourceStatus.getError(), sliverUrn, componentUrn));
            }

            if (statusInfo.getAnyExtExpires() != null)
                resourceStatusDetails.addKnownExpires(statusInfo.getAnyExtExpires());

            Sliver sliver;
            if (sliverUrn != null) {
                sliver =
                        getModel().logExistSliver(sliceUrn, urn, auth, Sliver.Source.AGGREGATE_MANAGER_STATUS); //might return null, if sliver URN does not match auth
                if (sliver != null) {
                    sliver.setStatusString(resourceStatus.getStatus());
                    sliver.setStatus(resourceStatusDetails);

                    //sadly, there is no expire info in this reply for AMv2. But emulab and orca and others do add a way, so we check for these here
                    if (statusInfo.getAnyExtExpires() != null)
                        sliver.setExpirationDate(statusInfo.getAnyExtExpires().toInstant());
                    slivers.add(sliver);
                }
            } else {
                //find sliver matching the component
                sliver = null;
                for (Sliver s : slice.findSlivers(auth)) {
                    if (s.containsComponent(componentUrn))
                        sliver = s;
                }
                if (sliver != null) {
                    sliver.getStatus().add(resourceStatusDetails);
                    slivers.add(sliver);
                }
            }
        }

        LOG.debug("Found slivers in SliverStatus: " + slivers);

        if (slivers.isEmpty()) {
            slivers = slice.findSlivers(auth);

            if (slivers.size() == 1) {
                //we know nothing about the slivers, so assume geni_single
                //this should not occur normally, but is a good fallback
                Sliver sliver = slivers.get(0);
                sliver.setStatusString(statusInfo.getStatus());
                sliver.setStatus(globalStatus);
            } else {
                LOG.warn("Got more than one sliver, while we were expecting to find only one! Status wil NOT be updated!");
            }
        }

        //remove slivers that were not mentioned, and are only known via the slice authority
        List<Sliver> notMentionedSlivers = slice.findSlivers(auth).stream()
                .filter(sliver -> !mentionedSliverUrns.contains(sliver.getUrn()))
                .filter(sliver -> sliver.getSource() == Sliver.Source.SLICE_AUTHORITY)
                .collect(Collectors.toList());

        if(!notMentionedSlivers.isEmpty()){
            LOG.trace("The following slivers were not mentioned in the SliverStatus of {}: {}",
                    auth.getName(),
                    notMentionedSlivers.stream().map(Sliver::getUrnString).collect(Collectors.joining(", ")));
        }                                                                else{
            LOG.trace("No unmentioned slivers in SliverStatus for authority {}", auth);
        }

        for (Sliver notMentionedSliver : notMentionedSlivers) {
            getModel().logNotExistSliver(sliceUrn, notMentionedSliver.getUrn(), Sliver.Source.AGGREGATE_MANAGER_STATUS);
        }

        /* Sometimes authorities do not report status of all slivers that they mentioned in other calls.

        To counter this: when all resources in the statusdetails-call are reported as "ready", we remove the slivers
        that are not mentioned anymore.

        ex. ExoGENI reports a sliver for each link in a CreateSliver-request,
        but doesn't mention it anymore in a StatusDetails-call

         */
        if (globalStatus.getGlobalStatus() == StatusDetails.SliverStatus.READY) {
            slice.findSlivers(auth).stream()
                    //don't force it on slivers which are explicitly unallocated (eg. leftovers from previous experiment runs)
                    .filter(sliver -> sliver.getStatus().getGlobalStatus() != StatusDetails.SliverStatus.UNALLOCATED)

                    .filter(sliver -> sliver.getStatus().getGlobalStatus() != StatusDetails.SliverStatus.READY)
                    .forEach(sliver -> {
                        LOG.info("{} reported all resources as READY in its StatusDetails-call, but we found sliver {} " +
                                        "which is registered in the SfaModel, but is not ready. We force it to READY now.",
                                auth, sliver.getUrnString());

                        sliver.setStatus(new StatusDetails(StatusDetails.SliverStatus.READY));
                    });
        }

        if(globalStatus.getGlobalStatus() != StatusDetails.SliverStatus.parse(statusInfo.getStatus())){
            LOG.warn("The computed global status {} does not match the received status {}! Please check if this is a testbed issue or not.",
                    globalStatus.getGlobalStatus(), statusInfo.getStatus());

        }
    }

    //workaround for AL2S which returns a bad result (bad slice urn and bad sliver urn)
    public void onAl2sStatusResult(@Nonnull String requestParamSliceUrn,
                                   @Nonnull AggregateManager2.SliverStatus statusInfo,
                                   @Nonnull Server auth) {

        LOG.debug("Using special processing for AL2S SliverStatus reply.");
        GeniUrn sliceUrn;
        try {
            sliceUrn = new GeniUrn(requestParamSliceUrn);
        } catch (GeniUrn.GeniUrnParseException e) {
            LOG.error("Could not parse slice-urn {} from Al2s result", requestParamSliceUrn);
            return;
        }

        getModel().logExistAtLeastOneSliver(sliceUrn, auth, Sliver.Source.AGGREGATE_MANAGER_STATUS); //since success is returned, the sliver must exist

        assert statusInfo.getStatus() != null;

        List<Sliver> slivers = new ArrayList<>();
        StatusDetails globalStatus = new StatusDetails(statusInfo.getStatus());
        for (AggregateManager2.SliverStatus.ResourceStatus resourceStatus : statusInfo.getResources()) {
            StatusDetails resourceStatusDetails = new StatusDetails(resourceStatus.getStatus());

            //for AL2S, this is a slice URN., which is actually a sliver urn with slice in the place of sliver...
            GeniUrn urn = GeniUrn.parse(resourceStatus.getUrn());
            GeniUrn sliverUrn;
            //flexible in case they fix the bug
            if (urn != null && (urn.getEncodedResourceType().equals("sliver") || urn.getEncodedResourceType().equals("slice"))) {
                if (Objects.equals(urn.getEncodedResourceType(), "slice")) {
                    sliverUrn =
                            GeniUrn.createGeniUrnFromEncodedParts(auth.getDefaultComponentManagerAsGeniUrn().getEncodedTopLevelAuthority(), "sliver", urn.getEncodedResourceName());
                    LOG.debug("Fixed sliver urn URN in AL2S SliverStatus reply: " + sliverUrn);
                } else
                    sliverUrn = urn;

                globalStatus.addSliverStatus(sliverUrn, resourceStatus.getStatus());
                resourceStatusDetails.addSliverStatus(sliverUrn, resourceStatus.getStatus());
            } else {
                LOG.error("Reply from AL2S is unsupported: resource urn = " + urn);
                return;
            }

            if (resourceStatus.getError() != null && !resourceStatus.getError().isEmpty()) {
                globalStatus.addError(new ErrorDetails(resourceStatus.getError(), sliverUrn, null/*componentUrn*/));
                resourceStatusDetails.addError(new ErrorDetails(resourceStatus.getError(), sliverUrn, null/*componentUrn*/));
            }

            if (statusInfo.getAnyExtExpires() != null)
                resourceStatusDetails.addKnownExpires(statusInfo.getAnyExtExpires());

            Sliver sliver =
                    getModel().logExistSliver(sliceUrn, sliverUrn, auth, Sliver.Source.AGGREGATE_MANAGER_STATUS); //might return null, if sliver URN does not match auth
            if (sliver != null) {
                sliver.setStatusString(resourceStatus.getStatus());
                sliver.setStatus(resourceStatusDetails);

                //sadly, there is no expire info in this reply for AMv2. But emulab and orca and others do add a way, so we check for these here
                if (statusInfo.getAnyExtExpires() != null)
                    sliver.setExpirationDate(statusInfo.getAnyExtExpires().toInstant());
                slivers.add(sliver);
            }
        }

        assert !slivers.isEmpty();
    }

    public void seeAdvertisementRspec(Server server, boolean available, String rspec) {
        LOG.trace("SfaModelAggregateManager2Listener.seeAdvertisementRspec(" + server.getName() + ", " + available + ", rspec.length=" + rspec.length() + ")");

        getModel().setAdvertisementRspec(server, available, rspec);
    }

//    public void seeRequestRspec(Server auth, String sliceUrn, List<String> sliverUrns, String rspec) {
//        model.getRSpecList().seeRequestRspec(auth, sliceUrn, sliverUrns, rspec);
//    }
//
//    public void seeEchoRequestRspec(Server auth, String sliceUrn, List<String> sliverUrns, String rspec) {
//        model.getRSpecList().seeEchoRequestRspec(auth, sliceUrn, sliverUrns, rspec);
//    }

    /**
     * seeing a manifest rspec also means that the sliver exists.
     */
    public List<Sliver> seeManifestRspec(@Nonnull Server auth, @Nonnull String sliceUrnStr,
                                         @Nonnull String manifestRspecString) {

        GeniUrn sliceUrn;
        try {
            sliceUrn = new GeniUrn(sliceUrnStr);
        } catch (GeniUrn.GeniUrnParseException e) {
            LOG.error("Could not process manifest, as the given slice-urn '{}' is invalid", sliceUrnStr, e);
            return Collections.emptyList();
        }

        Slice slice = getModel().getSlice(sliceUrn);
        if (slice == null) return Collections.emptyList();

        List<Sliver> sliverList;
        if (manifestRspecString != null)
            sliverList =
                    findSliversInManifest(auth, sliceUrn, new ManifestRspecSource(manifestRspecString, ModelRspecType.FX));
        else
            sliverList = null;
        if (sliverList == null) {
            //since success is returned, at least one sliver must exist, but we don't know its URN
            //we will assume geni_single here
            getModel().logExistAtLeastOneSliver(sliceUrn, auth, Sliver.Source.AGGREGATE_MANAGER_STATUS);
            sliverList = slice.findSlivers(auth);
        }

        //////// HACK for PLE ////////
        //hack for ple: handle the fact that ple requires a component_manager_id of
        //   urn:publicid:IDN+ple:ibbtple+authority+cm in the request RSpec but it returns
        //   urn:publicid:IDN+ple+authority+cm
        if (manifestRspecString.contains("component_manager_id=\"urn:publicid:IDN+ple+authority+cm\"")) {
            manifestRspecString = manifestRspecString.replaceAll(
                    Pattern.quote("urn:publicid:IDN+ple+authority+cm"),
                    Matcher.quoteReplacement("urn:publicid:IDN+ple:ibbtple+authority+cm"));
            LOG.warn("Using PLE hack in SfaModelAggregateManager2Listener#seeManifestRspec. succesfully applied: " +
                    (!manifestRspecString.contains("component_manager_id=\"urn:publicid:IDN+ple+authority+cm\"")));
            assert !manifestRspecString.contains("component_manager_id=\"urn:publicid:IDN+ple+authority+cm\"") :
                    "HACK applied unsuccessfully to:\n" + manifestRspecString;
        }
        //////// HACK for PLE ////////

        ManifestRspecSource manifestRspec = new ManifestRspecSource(manifestRspecString, ModelRspecType.FX);
        for (Sliver sliver : sliverList) {
            LOG.debug("SfaModelAggregateManager2Listener seeManifestRspec is setting manifest for " + slice + " -> " + sliceUrn + "  " + sliver + " -> " + sliver.getUrn());
            sliver.setManifestRspec(manifestRspec);
        }

        LOG.debug("SfaModelAggregateManager2Listener processed manifest");

        return sliverList;
    }

    /**
     * process the result, in the JavaFX thread. And wait for it in this thread.
     */
    @Override
    public void onResult(final ApiCallDetails details) {
        assert Platform.isFxApplicationThread();
        onResultInJavaFXThread(details);
    }

    public void onResultInJavaFXThread(ApiCallDetails details) {
        //ignore errors here
        if (details.getReply() == null || details.getJavaMethodName() == null)
            return;

        if (!Objects.equals(details.getApiName(), AggregateManager2.getApiName()))
            return;

        LOG.trace("SfaModelAggregateManager2Listener onResultInJavaFXThread " +
                "callAuth=" + (details.getServerComponentManagerUrnString()) +
                " API=" + details.getApiName() +
                " SfaCommand=\"" + details.getGeniMethodName() + "\"" +
                " javaCommand=\"" + details.getJavaMethodName() + "\"");

        try {
            if (Objects.equals(details.getJavaMethodName(), "getVersion")) //returns VersionInfo
                onGetVersionResult(details);

            if (Objects.equals(details.getJavaMethodName(), "listResources") && !details.getMethodParameters().containsKey("sliceUrn")) { //returns String (rspec)
                if (details.getReply().getGeniResponseCode().isSuccess()) {
                    String rspec = (String) details.getReply().getValue();
                    boolean available = false;
                    if (details.getMethodParameters().get("available") != null)
                        available = (Boolean) details.getMethodParameters().get("available");
                    seeAdvertisementRspec(details.getServer(), available, rspec);
                }
            }

            if (Objects.equals(details.getJavaMethodName(), "listResources") && details.getMethodParameters().containsKey("sliceUrn")) { //returns String (rspec)
                String sliceUrn = (String) details.getMethodParameters().get("sliceUrn");
                if (details.getReply().getGeniResponseCode().isSuccess()) {
                    LOG.trace("SfaModelAggregateManager2Listener sees successful ListResources call with sliceUrn " + sliceUrn);
                    String rspec = (String) details.getReply().getValue();
                    assert rspec != null;
                    assert !rspec.isEmpty();
                    getModel().logExistSlice(sliceUrn); //since success is returned, the slice must exist
                    getModel().logExistAtLeastOneSliver(sliceUrn, details.getServer(), Sliver.Source.AGGREGATE_MANAGER_STATUS); //since success is returned, the sliver must exist
                    seeManifestRspec(details.getServer(), sliceUrn, rspec);
                } else {
                    LOG.debug("SfaModelAggregateManager2Listener sees failed ListResources call with sliceUrn");
                    if (Objects.equals(details.getReply().getGeniResponseCode(), GeniAMResponseCode.GENIRESPONSE_SEARCHFAILED)) {
                        //since searchfailed is returned, the sliver must not exist
                        getModel().logNotExistSliverInSliceOnAuth(sliceUrn, details.getServer());
                    }
                }
            }

            if (Objects.equals(details.getJavaMethodName(), "createSliver")) { //returns AllocateAndProvisionInfo
                if (LOG.isTraceEnabled())
                    LOG.trace("onResultInJavaFXThread CreateSliver success=" + details.getReply().getGeniResponseCode().isSuccess());
                String sliceUrn = (String) details.getMethodParameters().get("sliceUrn");
                if (details.getReply().getGeniResponseCode().isSuccess()) {
                    Slice slice = getModel().logExistSlice(sliceUrn);

                    String manifestRspec = (String) details.getReply().getValue();
                    LOG.trace("SfaModelAggregateManager2Listener sets manifest Rspec on CreateSliver");
                    List<Sliver> slivers = seeManifestRspec(details.getServer(), sliceUrn, manifestRspec);

                    getModel().logExistAtLeastOneSliver(sliceUrn, details.getServer(), Sliver.Source.AGGREGATE_MANAGER_STATUS); //since success is returned, the sliver must exist

                    for (Sliver sliver : slivers) {
                        sliver.setStatus(new StatusDetails(StatusDetails.SliverStatus.UNKNOWN, sliver.getUrn())); //no specific status known. May be changing, failed or ready
                        sliver.setStatusString("CreateSliver successful");
                    }
                } else {
                    //allocate failed!
                    //we don't do anything here. A sliver might or might not exist, we don't know anything for sure...
                }
            }

            if (Objects.equals(details.getJavaMethodName(), "sliverStatus")) // returns SliverStatus
            {
                String sliceUrn = (String) details.getMethodParameters().get("sliceUrn");
                if (LOG.isTraceEnabled())
                    LOG.trace("onResultInJavaFXThread sliverStatus slice=" + sliceUrn +
                            " at " + details.getServer().getName() +
                            " success=" + details.getReply().getGeniResponseCode().isSuccess());

                if (details.getReply().getGeniResponseCode().isSuccess()) {
                    assert sliceUrn != null;

                    if (details.getServer() != null && Objects.equals(details.getServer().getDefaultComponentManagerUrn(), "urn:publicid:IDN+al2s.internet2.edu+authority+am"))
                        onAl2sStatusResult(sliceUrn, (AggregateManager2.SliverStatus) details.getReply().getValue(), details.getServer());
                    else
                        onStatusResult(sliceUrn, (AggregateManager2.SliverStatus) details.getReply().getValue(), details.getServer());
                } else {
                    if (Objects.equals(details.getReply().getGeniResponseCode(), GeniAMResponseCode.GENIRESPONSE_SEARCHFAILED)) {
                        //since searchfailed is returned, the sliver must not exist
                        getModel().logNotExistSliverInSliceOnAuth(sliceUrn, details.getServer());
                    }
                }
            }

            if (details.getGeniMethodName().equals("DeleteSliver") || details.getGeniMethodName().equals("Shutdown")) { //both return Boolean
                String sliceUrn = (String) details.getMethodParameters().get("sliceUrn");
                if (details.getReply().getGeniResponseCode().isSuccess()) {
                    Boolean isDeleted = null;
                    if (details.getReply().getValue() instanceof Boolean)
                        isDeleted = (Boolean) details.getReply().getValue();
                    if (details.getReply().getValue() instanceof Integer)
                        isDeleted = ((Integer) details.getReply().getValue()) != 0;
                    if (details.getReply().getValue() instanceof String) {
                        String isDeletedString = (String) details.getReply().getValue();
                        if (isDeletedString.trim().equalsIgnoreCase("true") || isDeletedString.trim().equalsIgnoreCase("yes") || isDeletedString.trim().equalsIgnoreCase("1"))
                            isDeleted = true;
                        if (isDeletedString.trim().equalsIgnoreCase("false") || isDeletedString.trim().equalsIgnoreCase("no") || isDeletedString.trim().equalsIgnoreCase("0"))
                            isDeleted = false;
                    }
                    if (isDeleted == null)
                        LOG.error("call \"" + details.getGeniMethodName() + "\" returned unrecognized value. class=" +
                                details.getReply().getValue().getClass().getName() + " value=\"" + details.getReply().getValue().toString() + "\"");
                    if (isDeleted != null && isDeleted)
                        getModel().logNotExistSliverInSliceOnAuth(sliceUrn, details.getServer());
                }
                if (Objects.equals(details.getReply().getGeniResponseCode(), GeniAMResponseCode.GENIRESPONSE_SEARCHFAILED))
                    getModel().logNotExistSliverInSliceOnAuth(sliceUrn, details.getServer());
            }

            if (Objects.equals(details.getJavaMethodName(), "renewSliver")) { //returns List<SliverInfo>
                String sliceUrn = (String) details.getMethodParameters().get("sliceUrn");
                String newExpiration = (String) details.getMethodParameters().get("expirationTimeRfc3339");

                if (details.getReply().getGeniResponseCode().isSuccess() && sliceUrn != null && newExpiration != null) {
                    Boolean reply = (Boolean) details.getReply().getValue();
                    if (reply != null && reply == true) {
                        List<Sliver> slivers = getModel().logExistSlice(sliceUrn).findSlivers(details.getServer());
                        try {
                            Date newExpirationDate = RFC3339Util.rfc3339StringToDate(newExpiration);

                            for (Sliver sliver : slivers)
                                sliver.setExpirationDate(newExpirationDate.toInstant());
                        } catch (ParseException e) {
                            LOG.warn("RenewSliver call \"expirationTime\" parameter failed to parse as RFC3339");
                        }
                    }
                }
                if (Objects.equals(details.getReply().getGeniResponseCode(), GeniAMResponseCode.GENIRESPONSE_SEARCHFAILED))
                    getModel().logNotExistSliverInSliceOnAuth(sliceUrn, details.getServer());
            }
        } catch (Exception e) {
            LOG.error("WARNING: Exception when processing AggregateManager2 reply for SfaModel. " +
                    "This will be ignored, but it is most likely a bug. ", e);
        }
    }
}
