/**
 * This file is part of the Meeds project (https://meeds.io/).
 *
 * Copyright (C) 2020 - 2025 Meeds Association contact@meeds.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.exoplatform.container;

import org.exoplatform.container.util.Utils;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterRegistration;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRegistration;
import jakarta.servlet.ServletRegistration.Dynamic;
import jakarta.servlet.SessionCookieConfig;
import jakarta.servlet.SessionTrackingMode;
import jakarta.servlet.descriptor.JspConfigDescriptor;

/**
 * This class is used to merge all the {@link ServletContext} related to a given portal container.
 * It will used the {@link WebAppInitContext} that have been defined in the related portal container.
 * It will always consider that the {@link WebAppInitContext}
 * with the highest priority has always right, in other words for example in the method
 * getInitParameter, it will try to get the init parameter in the {@link WebAppInitContext} 
 * of the highest priority, if it cans not find it, it will try the {@link WebAppInitContext}
 *  with the second highest priority and so on. The priority of the {@link WebAppInitContext} is
 *  the order given by the method PortalContainer.getWebAppInitContexts(), 
 * the last {@link WebAppInitContext} is the one with the highest priority.
 * 
 * Created by The eXo Platform SAS
 * Author : Nicolas Filotto
 *          nicolas.filotto@exoplatform.com
 * 14 sept. 2009  
 */
class PortalContainerContext implements ServletContext {

   /**
    * The weak reference to the related portal container
    */
   private AtomicReference<PortalContainer> containerRef;

   /**
    * The name of the related portal container used in case of developing mode
    */
   private final String portalContainerName;

   PortalContainerContext(PortalContainer container)
   {
      // In case of developing mode we want to avoid to use hard reference in case 
      // we would like to reload the container
      this.containerRef = new AtomicReference<>(container);
      this.portalContainerName = container.getName();
   }

   private WebAppInitContext[] getWebAppInitContexts()
   {
      final Set<WebAppInitContext> contexts = getPortalContainer().getWebAppInitContexts();
      final WebAppInitContext[] aContexts = new WebAppInitContext[contexts.size()];
      return contexts.toArray(aContexts);
   }
   
   private PortalContainer getPortalContainer()
   {
      PortalContainer container = containerRef.get();
      if (container != null)
      {
         return container;
      }
      container = RootContainer.getInstance().getPortalContainer(portalContainerName);
      containerRef = new AtomicReference<>(container);
      return container;
   }
   
   private ServletContext getPortalContext()
   {
      return getPortalContainer().portalContext;
   }

   /**
    * {@inheritDoc}
    */
   public Object getAttribute(String name)
   {
      return getPortalContext().getAttribute(name);
   }

   /**
    * {@inheritDoc}
    */
   public Enumeration<String> getAttributeNames()
   {
      return getPortalContext().getAttributeNames();
   }

   /**
    * {@inheritDoc}
    */
   public ServletContext getContext(String uripath)
   {
      return getPortalContext().getContext(uripath);
   }

   /**
    * {@inheritDoc}
    */
   public String getInitParameter(String name)
   {
      final WebAppInitContext[] contexts = getWebAppInitContexts();
      for (int i = contexts.length - 1; i >= 0; i--)
      {
         final ServletContext context = contexts[i].getServletContext();
         String param = context.getInitParameter(name);
         if (param != null)
         {
            return param;
         }
      }
      return null;
   }

   /**
    * {@inheritDoc}
    */
   public Enumeration<String> getInitParameterNames()
   {
      final Set<WebAppInitContext> contexts = getPortalContainer().getWebAppInitContexts();
      Set<String> names = null;
      for (WebAppInitContext context : contexts)
      {
         Enumeration<String> eNames = context.getServletContext().getAttributeNames();
         if (eNames != null)
         {
            if (names == null)
            {
               names = new HashSet<>();
            }
            names.addAll(Collections.list(eNames));
         }
      }
      if (names == null)
      {
         return null;
      }
      return Collections.enumeration(names);
   }

   /**
    * {@inheritDoc}
    */
   public int getMajorVersion()
   {
      return getPortalContext().getMajorVersion();
   }

   /**
    * {@inheritDoc}
    */
   public String getMimeType(String file)
   {
      final WebAppInitContext[] contexts = getWebAppInitContexts();
      for (int i = contexts.length - 1; i >= 0; i--)
      {
         final ServletContext context = contexts[i].getServletContext();
         String mimeType = context.getMimeType(file);
         if (mimeType != null)
         {
            return mimeType;
         }
      }
      return null;
   }

   /**
    * {@inheritDoc}
    */
   public int getMinorVersion()
   {
      return getPortalContext().getMinorVersion();
   }

   /**
    * {@inheritDoc}
    */
   public RequestDispatcher getNamedDispatcher(String name)
   {
      return getPortalContext().getNamedDispatcher(name);
   }

   /**
    * {@inheritDoc}
    */
   public String getRealPath(String path)
   {
      final WebAppInitContext[] contexts = getWebAppInitContexts();
      for (int i = contexts.length - 1; i >= 0; i--)
      {
         final ServletContext context = contexts[i].getServletContext();
         final InputStream is = context.getResourceAsStream(path);
         if (is != null)
         {
            // The resource exists within this servlet context
            return context.getRealPath(path);
         }
      }
      return null;
   }

   /**
    * {@inheritDoc}
    */
   public RequestDispatcher getRequestDispatcher(String path)
   {
      final WebAppInitContext[] contexts = getWebAppInitContexts();
      for (int i = contexts.length - 1; i >= 0; i--)
      {
         final ServletContext context = contexts[i].getServletContext();
         final InputStream is = context.getResourceAsStream(Utils.getPathOnly(path));
         if (is != null)
         {
            try {
              is.close();
            } catch (IOException e) {
              log("Error closing Input String of file '%s'".formatted(path));
            }
            // The resource exists within this servlet context
            return context.getRequestDispatcher(path);
         }
      }
      return null;
   }

   /**
    * {@inheritDoc}
    */
   public URL getResource(String path) throws MalformedURLException
   {
      final WebAppInitContext[] contexts = getWebAppInitContexts();
      for (int i = contexts.length - 1; i >= 0; i--)
      {
         final ServletContext context = contexts[i].getServletContext();
         final URL url = context.getResource(path);
         if (url != null)
         {
            return url;
         }
      }
      return null;
   }

   /**
    * {@inheritDoc}
    */
   public InputStream getResourceAsStream(String path)
   {
      final WebAppInitContext[] contexts = getWebAppInitContexts();
      for (int i = contexts.length - 1; i >= 0; i--)
      {
         final ServletContext context = contexts[i].getServletContext();
         final InputStream is = context.getResourceAsStream(path);
         if (is != null)
         {
            return is;
         }
      }
      return null;
   }

   /**
    * {@inheritDoc}
    */
   public Set<String> getResourcePaths(String path)
   {
      final Set<WebAppInitContext> contexts = getPortalContainer().getWebAppInitContexts();
      Set<String> paths = null;
      for (WebAppInitContext context : contexts)
      {
         Set<String> sPaths = context.getServletContext().getResourcePaths(path);
         if (sPaths != null)
         {
            if (paths == null)
            {
               paths = new LinkedHashSet<>();
            }
            paths.addAll(sPaths);
         }
      }
      return paths;
   }

   /**
    * {@inheritDoc}
    */
   public String getServerInfo()
   {
      return getPortalContext().getServerInfo();
   }

   /**
    * {@inheritDoc}
    */
   public String getServletContextName()
   {
      return getPortalContext().getServletContextName();
   }

   /**
    * {@inheritDoc}
    */
   public void log(String message)
   {
      getPortalContext().log(message);
   }

   /**
    * {@inheritDoc}
    */
   public void log(String message, Throwable throwable)
   {
      getPortalContext().log(message, throwable);
   }

   /**
    * {@inheritDoc}
    */
   public void removeAttribute(String name)
   {
      getPortalContext().removeAttribute(name);
   }

   /**
    * {@inheritDoc}
    */
   public void setAttribute(String name, Object object)
   {
      getPortalContext().setAttribute(name, object);
   }

   /**
    * {@inheritDoc}
    */
   public String getContextPath()
   {
      return getPortalContext().getContextPath();
   }

   // servlet 3.0. API

   /**
    * {@inheritDoc}
    */
   public int getEffectiveMajorVersion()
   {
      return getPortalContext().getEffectiveMajorVersion();
   }

   /**
    * {@inheritDoc}
    */
   public int getEffectiveMinorVersion()
   {
      return getPortalContext().getEffectiveMinorVersion();
   }

   /**
    * {@inheritDoc}
    */
   public boolean setInitParameter(String name, String value)
   {
      return getPortalContext().setInitParameter(name, value);
   }

   /**
    * {@inheritDoc}
    */
   public Dynamic addServlet(String servletName, String className)
   {
      return getPortalContext().addServlet(servletName, className);
   }

   /**
    * {@inheritDoc}
    */
   public Dynamic addServlet(String servletName, Servlet servlet)
   {
      return getPortalContext().addServlet(servletName, servlet);
   }

   /**
    * {@inheritDoc}
    */
   public Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass)
   {
      return getPortalContext().addServlet(servletName, servletClass);
   }

   /**
    * {@inheritDoc}
    */
   public <T extends Servlet> T createServlet(Class<T> clazz) throws ServletException
   {
      return getPortalContext().createServlet(clazz);
   }

   /**
    * {@inheritDoc}
    */
   public ServletRegistration getServletRegistration(String servletName)
   {
      return getPortalContext().getServletRegistration(servletName);
   }

   /**
    * {@inheritDoc}
    */
   public Map<String, ? extends ServletRegistration> getServletRegistrations()
   {
      return getPortalContext().getServletRegistrations();
   }

   /**
    * {@inheritDoc}
    */
   public jakarta.servlet.FilterRegistration.Dynamic addFilter(String filterName, String className)
   {
      return getPortalContext().addFilter(filterName, className);
   }

   /**
    * {@inheritDoc}
    */
   public jakarta.servlet.FilterRegistration.Dynamic addFilter(String filterName, Filter filter)
   {
      return getPortalContext().addFilter(filterName, filter);
   }

   /**
    * {@inheritDoc}
    */
   public jakarta.servlet.FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> filterClass)
   {
      return getPortalContext().addFilter(filterName, filterClass);
   }

   /**
    * {@inheritDoc}
    */
   public <T extends Filter> T createFilter(Class<T> clazz) throws ServletException
   {
      return getPortalContext().createFilter(clazz);
   }

   /**
    * {@inheritDoc}
    */
   public FilterRegistration getFilterRegistration(String filterName)
   {
      return getPortalContext().getFilterRegistration(filterName);
   }

   /**
    * {@inheritDoc}
    */
   public Map<String, ? extends FilterRegistration> getFilterRegistrations()
   {
      return getPortalContext().getFilterRegistrations();
   }

   /**
    * {@inheritDoc}
    */
   public SessionCookieConfig getSessionCookieConfig()
   {
      return getPortalContext().getSessionCookieConfig();
   }

   /**
    * {@inheritDoc}
    */
   public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
   {
      getPortalContext().setSessionTrackingModes(sessionTrackingModes);
   }

   /**
    * {@inheritDoc}
    */
   public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
   {
      return getPortalContext().getDefaultSessionTrackingModes();
   }

   /**
    * {@inheritDoc}
    */
   public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
   {
      return getPortalContext().getEffectiveSessionTrackingModes();
   }

   /**
    * {@inheritDoc}
    */
   public void addListener(String className)
   {
      getPortalContext().addListener(className);
   }

   /**
    * {@inheritDoc}
    */
   public <T extends EventListener> void addListener(T t)
   {
      getPortalContext().addListener(t);
   }

   /**
    * {@inheritDoc}
    */
   public void addListener(Class<? extends EventListener> listenerClass)
   {
      getPortalContext().addListener(listenerClass);
   }

   /**
    * {@inheritDoc}
    */
   public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
   {
      return getPortalContext().createListener(clazz);
   }

   /**
    * {@inheritDoc}
    */
   public JspConfigDescriptor getJspConfigDescriptor()
   {
      return getPortalContext().getJspConfigDescriptor();
   }

   /**
    * {@inheritDoc}
    */
   public ClassLoader getClassLoader()
   {
      return getPortalContainer().getPortalClassLoader();
   }

   /**
    * {@inheritDoc}
    */
   public void declareRoles(String... roleNames)
   {
      getPortalContext().declareRoles(roleNames);
   }

   /**
    * {@inheritDoc}
    */
   public String getVirtualServerName() {
     return getPortalContext().getVirtualServerName();
   }

  @Override
  public Dynamic addJspFile(String servletName, String jspFile) {
    return getPortalContext().addJspFile(servletName, jspFile);
  }

  @Override
  public int getSessionTimeout() {
    return getPortalContext().getSessionTimeout();
  }

  @Override
  public void setSessionTimeout(int sessionTimeout) {
    getPortalContext().setSessionTimeout(sessionTimeout);
  }

  @Override
  public String getRequestCharacterEncoding() {
    return getPortalContext().getRequestCharacterEncoding();
  }

  @Override
  public void setRequestCharacterEncoding(String encoding) {
    getPortalContext().setRequestCharacterEncoding(encoding);
  }

  @Override
  public String getResponseCharacterEncoding() {
    return getPortalContext().getResponseCharacterEncoding();
  }

  @Override
  public void setResponseCharacterEncoding(String encoding) {
    getPortalContext().setResponseCharacterEncoding(encoding);
  }
}
