/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.felix.http.base.internal.registry;

import java.util.ArrayList;
import java.util.List;

import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
import jakarta.servlet.ServletContextAttributeEvent;
import jakarta.servlet.ServletContextAttributeListener;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.ServletRequestAttributeEvent;
import jakarta.servlet.ServletRequestAttributeListener;
import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import jakarta.servlet.http.HttpSessionAttributeListener;
import jakarta.servlet.http.HttpSessionBindingEvent;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionIdListener;
import jakarta.servlet.http.HttpSessionListener;

import org.apache.felix.http.base.internal.handler.ListenerHandler;
import org.apache.felix.http.base.internal.logger.SystemLogger;
import org.apache.felix.http.base.internal.runtime.ListenerInfo;
import org.osgi.service.servlet.runtime.dto.FailedListenerDTO;
import org.osgi.service.servlet.runtime.dto.ListenerDTO;
import org.osgi.service.servlet.runtime.dto.ServletContextDTO;

/**
 * Per context event listener registry.
 */
public final class EventListenerRegistry implements
        HttpSessionListener,
        HttpSessionAttributeListener,
        HttpSessionIdListener,
        ServletContextAttributeListener,
        ServletRequestListener,
        ServletRequestAttributeListener
{
    /** Servlet context listeners. */
    private final ListenerMap<ServletContextListener> contextListeners = new ListenerMap<ServletContextListener>();

    /** Servlet context attribute listeners. */
    private final ListenerMap<ServletContextAttributeListener> contextAttributeListeners = new ListenerMap<ServletContextAttributeListener>();

    /** Session attribute listeners. */
    private final ListenerMap<HttpSessionAttributeListener> sessionAttributeListeners = new ListenerMap<HttpSessionAttributeListener>();

    /** Session listeners. */
    private final ListenerMap<HttpSessionListener> sessionListeners = new ListenerMap<HttpSessionListener>();

    /** Session id listeners. */
    private final ListenerMap<HttpSessionIdListener> sessionIdListeners = new ListenerMap<HttpSessionIdListener>();

    /** Request listeners. */
    private final ListenerMap<ServletRequestListener> requestListeners = new ListenerMap<ServletRequestListener>();

    /** Request attribute listeners. */
    private final ListenerMap<ServletRequestAttributeListener> requestAttributeListeners = new ListenerMap<ServletRequestAttributeListener>();

    public void cleanup()
    {
        this.contextListeners.cleanup();
        this.contextAttributeListeners.cleanup();
        this.sessionAttributeListeners.cleanup();
        this.sessionListeners.cleanup();
        this.sessionIdListeners.cleanup();
        this.requestListeners.cleanup();
        this.requestAttributeListeners.cleanup();
    }

    /**
     * Add  listeners
     *
     * @param handler The listener handler
     */
    public void addListeners(@NotNull final ListenerHandler handler)
    {
        final int reason = handler.init();

        if ( handler.getListenerInfo().isListenerType(ServletContextListener.class.getName()))
        {
            this.contextListeners.add(handler, reason);
        }
        if ( handler.getListenerInfo().isListenerType(ServletContextAttributeListener.class.getName()))
        {
            this.contextAttributeListeners.add(handler, reason);
        }
        if ( handler.getListenerInfo().isListenerType(HttpSessionListener.class.getName()))
        {
            this.sessionListeners.add(handler, reason);
        }
        if ( handler.getListenerInfo().isListenerType(HttpSessionIdListener.class.getName()))
        {
            this.sessionIdListeners.add(handler, reason);
        }
        if ( handler.getListenerInfo().isListenerType(HttpSessionAttributeListener.class.getName()))
        {
            this.sessionAttributeListeners.add(handler, reason);
        }
        if ( handler.getListenerInfo().isListenerType(ServletRequestListener.class.getName()))
        {
            this.requestListeners.add(handler, reason);
        }
        if ( handler.getListenerInfo().isListenerType(ServletRequestAttributeListener.class.getName()))
        {
            this.requestAttributeListeners.add(handler, reason);
        }
    }

    /**
     * Remove listeners
     * @param info The listener info
     */
    public void removeListeners(@NotNull final ListenerInfo info)
    {
        // each listener map returns the same handler, we just need it once to destroy
        ListenerHandler handler = null;
        if ( info.isListenerType(ServletContextListener.class.getName()))
        {
            handler = this.contextListeners.remove(info);
        }
        if ( info.isListenerType(ServletContextAttributeListener.class.getName()))
        {
            handler = this.contextAttributeListeners.remove(info);
        }
        if ( info.isListenerType(HttpSessionListener.class.getName()))
        {
            handler = this.sessionListeners.remove(info);
        }
        if ( info.isListenerType(HttpSessionIdListener.class.getName()))
        {
            handler = this.sessionIdListeners.remove(info);
        }
        if ( info.isListenerType(HttpSessionAttributeListener.class.getName()))
        {
            handler = this.sessionAttributeListeners.remove(info);
        }
        if ( info.isListenerType(ServletRequestListener.class.getName()))
        {
            handler = this.requestListeners.remove(info);
        }
        if ( info.isListenerType(ServletRequestAttributeListener.class.getName()))
        {
            handler = this.requestAttributeListeners.remove(info);
        }
        if ( handler != null )
        {
            handler.destroy();
        }
    }

    /**
     * Get the listener handler for the listener info
     * @param info The listener info
     * @return The handler or {@code null}.
     */
    public @Nullable ListenerHandler getServletContextListener(@NotNull final ListenerInfo info)
    {
        return this.contextListeners.getListenerHandler(info);
    }

    public void contextInitialized() {
        for (final ListenerHandler l : contextListeners.getActiveHandlers())
        {
            final ServletContextListener listener = (ServletContextListener)l.getListener();
            if ( listener != null )
            {
                contextInitialized(l.getListenerInfo(), listener, new ServletContextEvent(l.getContext()));
            }
        }
    }

    public void contextDestroyed() {
        for (final ListenerHandler l : contextListeners.getActiveHandlers())
        {
            final ServletContextListener listener = (ServletContextListener) l.getListener();
            if ( listener != null )
            {
                contextDestroyed(l.getListenerInfo(), listener, new ServletContextEvent(l.getContext()));
            }
        }
    }

    @Override
    public void attributeReplaced(final HttpSessionBindingEvent event)
    {
        for (final HttpSessionAttributeListener l : sessionAttributeListeners.getActiveListeners())
        {
            try
            {
                l.attributeReplaced(event);
            }
            catch (final Throwable t)
            {
                SystemLogger.LOGGER.error("Exception while calling listener {}", l, t);
            }
        }
    }

    @Override
    public void attributeRemoved(final HttpSessionBindingEvent event)
    {
        for (final HttpSessionAttributeListener l : sessionAttributeListeners.getActiveListeners())
        {
            try
            {
                l.attributeRemoved(event);
            }
            catch (final Throwable t)
            {
                SystemLogger.LOGGER.error("Exception while calling listener {}", l, t);
            }
        }
    }

    @Override
    public void attributeAdded(final HttpSessionBindingEvent event)
    {
        for (final HttpSessionAttributeListener l : sessionAttributeListeners.getActiveListeners())
        {
            try
            {
                l.attributeAdded(event);
            }
            catch (final Throwable t)
            {
                SystemLogger.LOGGER.error("Exception while calling listener {}", l, t);
            }
        }
    }

    @Override
    public void attributeReplaced(final ServletContextAttributeEvent event)
    {
        for (final ServletContextAttributeListener l : contextAttributeListeners.getActiveListeners())
        {
            try
            {
                l.attributeReplaced(event);
            }
            catch (final Throwable t)
            {
                SystemLogger.LOGGER.error("Exception while calling listener {}", l, t);
            }
        }
    }

    @Override
    public void attributeRemoved(final ServletContextAttributeEvent event)
    {
        for (final ServletContextAttributeListener l : contextAttributeListeners.getActiveListeners())
        {
            try
            {
                l.attributeRemoved(event);
            }
            catch (final Throwable t)
            {
                SystemLogger.LOGGER.error("Exception while calling listener {}", l, t);
            }
        }
    }

    @Override
    public void attributeAdded(final ServletContextAttributeEvent event)
    {
        for (final ServletContextAttributeListener l : contextAttributeListeners.getActiveListeners())
        {
            try
            {
                l.attributeAdded(event);
            }
            catch (final Throwable t)
            {
                SystemLogger.LOGGER.error("Exception while calling listener {}", l, t);
            }
        }
    }

    @Override
    public void sessionCreated(final HttpSessionEvent se)
    {
        for (final HttpSessionListener l : sessionListeners.getActiveListeners())
        {
            try
            {
                l.sessionCreated(se);
            }
            catch (final Throwable t)
            {
                SystemLogger.LOGGER.error("Exception while calling listener {}", l, t);
            }
        }
    }

    @Override
    public void sessionDestroyed(final HttpSessionEvent se)
    {
        for (final HttpSessionListener l : sessionListeners.getActiveListeners())
        {
            try
            {
                l.sessionDestroyed(se);
            }
            catch (final Throwable t)
            {
                SystemLogger.LOGGER.error("Exception while calling listener {}", l, t);
            }
        }
    }

    @Override
    public void requestDestroyed(final ServletRequestEvent sre)
    {
        for (final ServletRequestListener l : requestListeners.getActiveListeners())
        {
            try
            {
                l.requestDestroyed(sre);
            }
            catch (final Throwable t)
            {
                SystemLogger.LOGGER.error("Exception while calling listener {}", l, t);
            }
        }
    }

    @Override
    public void requestInitialized(final ServletRequestEvent sre)
    {
        for (final ServletRequestListener l : requestListeners.getActiveListeners())
        {
            try
            {
                l.requestInitialized(sre);
            }
            catch (final Throwable t)
            {
                SystemLogger.LOGGER.error("Exception while calling listener {}", l, t);
            }
        }
    }

    @Override
    public void attributeAdded(final ServletRequestAttributeEvent srae)
    {
        for (final ServletRequestAttributeListener l : requestAttributeListeners.getActiveListeners())
        {
            try
            {
                l.attributeAdded(srae);
            }
            catch (final Throwable t)
            {
                SystemLogger.LOGGER.error("Exception while calling listener {}", l, t);
            }
        }
    }

    @Override
    public void attributeRemoved(final ServletRequestAttributeEvent srae)
    {
        for (final ServletRequestAttributeListener l : requestAttributeListeners.getActiveListeners())
        {
            try
            {
                l.attributeRemoved(srae);
            }
            catch (final Throwable t)
            {
                SystemLogger.LOGGER.error("Exception while calling listener {}", l, t);
            }
        }
    }

    @Override
    public void attributeReplaced(final ServletRequestAttributeEvent srae)
    {
        for (final ServletRequestAttributeListener l : requestAttributeListeners.getActiveListeners())
        {
            try
            {
                l.attributeReplaced(srae);
            }
            catch (final Throwable t)
            {
                SystemLogger.LOGGER.error("Exception while calling listener {}", l, t);
            }
        }
    }

    /**
     * @see jakarta.servlet.http.HttpSessionIdListener#sessionIdChanged(jakarta.servlet.http.HttpSessionEvent, java.lang.String)
     */
    @Override
    public void sessionIdChanged(@NotNull final HttpSessionEvent event, @NotNull final String oldSessionId) {
        for (final HttpSessionIdListener l : sessionIdListeners.getActiveListeners())
        {
            try
            {
                l.sessionIdChanged(event, oldSessionId);
            }
            catch (final Throwable t)
            {
                SystemLogger.LOGGER.error("Exception while calling listener {}", l, t);
            }
        }
    }

    public void getRuntimeInfo(final ServletContextDTO dto, final List<FailedListenerDTO> failedListenerDTOs)
    {
        final List<ListenerDTO> listenerDTOs = new ArrayList<ListenerDTO>();
        this.contextListeners.getRuntimeInfo(listenerDTOs, failedListenerDTOs);
        this.contextAttributeListeners.getRuntimeInfo(listenerDTOs, failedListenerDTOs);
        this.requestListeners.getRuntimeInfo(listenerDTOs, failedListenerDTOs);
        this.requestAttributeListeners.getRuntimeInfo(listenerDTOs, failedListenerDTOs);
        this.sessionListeners.getRuntimeInfo(listenerDTOs, failedListenerDTOs);
        this.sessionAttributeListeners.getRuntimeInfo(listenerDTOs, failedListenerDTOs);
        this.sessionIdListeners.getRuntimeInfo(listenerDTOs, failedListenerDTOs);

        if ( listenerDTOs.size() > 0 )
        {
            dto.listenerDTOs = listenerDTOs.toArray(new ListenerDTO[listenerDTOs.size()]);
        }
    }

    public static void contextInitialized(
            @NotNull final ListenerInfo info,
            @NotNull final ServletContextListener listener,
            @NotNull final ServletContextEvent event)
    {
        try
        {
            listener.contextInitialized(event);
        }
        catch (final Throwable t)
        {
            SystemLogger.LOGGER.error(SystemLogger.formatMessage(info.getServiceReference(), "Exception while calling servlet context listener."), t);
        }
    }

    public static void contextDestroyed(
            @NotNull final ListenerInfo info,
            @NotNull final ServletContextListener listener,
            @NotNull final ServletContextEvent event)
    {
        try
        {
            listener.contextDestroyed(event);
        }
        catch (final Throwable t)
        {
            SystemLogger.LOGGER.error(SystemLogger.formatMessage(info.getServiceReference(), "Exception while calling servlet context listener."), t);
        }
    }
}
