/*
 * Licensed to the Apaanche 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.Collections;
import java.util.Iterator;
import java.util.List;

import jakarta.servlet.DispatcherType;

import org.apache.felix.http.base.internal.HttpConfig;
import org.apache.felix.http.base.internal.handler.FilterHandler;
import org.apache.felix.http.base.internal.handler.ServletHandler;
import org.apache.felix.http.base.internal.runtime.ServletContextHelperInfo;
import org.apache.felix.http.base.internal.runtime.dto.FailedDTOHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.service.servlet.runtime.dto.ServletContextDTO;

/**
 * Registry for all services.
 *
 * The registry is organized per servlet context and is dispatching to one
 * of the {@link PerContextHandlerRegistry} registries.
 */
public final class HandlerRegistry
{
    private static FilterHandler[] EMPTY_FILTER_HANDLER = new FilterHandler[0];

    /** Current list of context registrations. */
    private volatile List<PerContextHandlerRegistry> registrations = Collections.emptyList();

    private final HttpConfig config;

    public HandlerRegistry(final HttpConfig config)
    {
        this.config = config;
    }

    public HttpConfig getConfig()
    {
        return this.config;
    }

    /**
     * Register default context registry for Http Service
     */
    public void init()
    {
        this.add(new PerContextHandlerRegistry(config));
    }

    /**
     * Reset to initial state
     */
    public void reset()
    {
        this.registrations.clear();
        this.init();
    }

    /**
     * Shutdown
     */
    public void shutdown()
    {
        final List<PerContextHandlerRegistry> list;

        synchronized ( this )
        {
            list = new ArrayList<>(this.registrations);
            this.registrations = Collections.emptyList();

        }

        for(final PerContextHandlerRegistry r : list)
        {
            r.removeAll();
        }
    }

    /**
     * Remove a context registration.
     * @param info The servlet context helper info
     */
    public void remove(@NotNull ServletContextHelperInfo info)
    {
        synchronized ( this )
        {
            final List<PerContextHandlerRegistry> updatedList = new ArrayList<>(this.registrations);
            final Iterator<PerContextHandlerRegistry> i = updatedList.iterator();
            while ( i.hasNext() )
            {
                final PerContextHandlerRegistry reg = i.next();
                if ( reg.getContextServiceId() == info.getServiceId() )
                {
                    i.remove();
                    this.registrations = updatedList;
                    break;
                }
            }
        }
    }


    /**
     * Add a new context registration.
     * @param registry The registry
     */
    public void add(@NotNull PerContextHandlerRegistry registry)
    {
        synchronized ( this )
        {
            final List<PerContextHandlerRegistry> updatedList = new ArrayList<>(this.registrations);
            updatedList.add(registry);
            Collections.sort(updatedList);

            this.registrations = updatedList;
        }
    }

    public PerContextHandlerRegistry getRegistry(final long key)
    {
        final List<PerContextHandlerRegistry> list = this.registrations;
        for(final PerContextHandlerRegistry r : list)
        {
            if ( key == r.getContextServiceId())
            {
                return r;
            }
        }
        return null;
    }

    public @Nullable ServletResolution getErrorHandler(@NotNull final String requestURI,
            final Long serviceId,
            final int code,
            final Throwable exception)
    {
        final PerContextHandlerRegistry reg;
        if ( serviceId == null )
        {
            // if the context is unknown, we use the first matching one!
            PerContextHandlerRegistry found = null;
            final List<PerContextHandlerRegistry> regs = this.registrations;
            for(final PerContextHandlerRegistry r : regs)
            {
                final String path = r.isMatching(requestURI);
                if ( path != null )
                {
                    found = r;
                    break;
                }
            }
            reg = found;
        }
        else
        {
            reg = this.getRegistry(serviceId);
        }
        if ( reg != null )
        {
            final ServletHandler handler = reg.getErrorHandler(code, exception);
            if ( handler != null )
            {
                final ServletResolution res = new ServletResolution();
                res.handler = handler;
                res.handlerRegistry = reg;

                return res;
            }
        }
        return null;
    }

    public FilterHandler[] getFilters(@NotNull final ServletResolution pr,
            @NotNull final DispatcherType dispatcherType,
            @NotNull String requestURI)
    {
        if ( pr != null && pr.handlerRegistry != null )
        {
            return pr.handlerRegistry.getFilterHandlers(pr.handler, dispatcherType, requestURI);
        }
        return EMPTY_FILTER_HANDLER;
    }

    public PathResolution resolveServlet(@NotNull final String requestURI)
    {
        final List<PerContextHandlerRegistry> regs = this.registrations;
        for(final PerContextHandlerRegistry r : regs)
        {
            final String path = r.isMatching(requestURI);
            if ( path != null )
            {
                final PathResolution ps = r.resolve(path);
                if ( ps != null )
                {
                    // remove context path from request URI and add registry object
                    ps.requestURI = path;
                    ps.handlerRegistry = r;
                    return ps;
                }
            }
        }

        return null;
    }

    /**
     * Get the servlet handler for a servlet by name
     * @param contextId The context id
     * @param name The servlet name
     * @return The servlet handler or {@code null}
     */
    public ServletResolution resolveServletByName(final long contextId, @NotNull final String name)
    {
        final PerContextHandlerRegistry reg = this.getRegistry(contextId);
        if ( reg != null )
        {
            final ServletHandler handler = reg.resolveServletByName(name);
            if ( handler != null )
            {
                final ServletResolution resolution = new ServletResolution();
                resolution.handler = handler;
                resolution.handlerRegistry = reg;

                return resolution;
            }
        }
        return null;
    }

    public boolean getRuntimeInfo(@NotNull final ServletContextDTO dto,
            @NotNull final FailedDTOHolder failedDTOHolder)
    {
        final PerContextHandlerRegistry reg = this.getRegistry(dto.serviceId);
        if ( reg != null )
        {
            reg.getRuntime(dto, failedDTOHolder);
            return true;
        }
        return false;
    }

    public PerContextHandlerRegistry getBestMatchingRegistry(String requestURI)
    {
        // if the context is unknown, we use the first matching one!
        PerContextHandlerRegistry found = null;
        final List<PerContextHandlerRegistry> regs = this.registrations;
        for(final PerContextHandlerRegistry r : regs)
        {
            final String path = r.isMatching(requestURI);
            if ( path != null )
            {
                found = r;
                break;
            }
        }
        return found;
    }
}
