/*
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * This 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 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.exoplatform.container;

import junit.framework.AssertionFailedError;

import org.exoplatform.commons.utils.PropertyManager;
import org.exoplatform.commons.utils.SecurityHelper;
import org.exoplatform.commons.utils.Tools;

import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;

/**
 * <p>An helper for building a root container and a portal container. I have done several attempt to make easily
 * and safe root/portal container boot for unit test. This one is my best attempt so far.</p>
 *
 * <p>Note that the portal container are booted in the order they are declared first.</p>
 *
 * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
 * @version $Revision$
 */
public class ContainerBuilder
{

   /** A hack used during the boot of a portal container. */
   private final ThreadLocal<String> bootedPortalName = new ThreadLocal<String>();

   /** . */
   private ClassLoader loader;

   /** . */
   private List<URL> configURLs;

   /** . */
   private LinkedHashMap<String, List<URL>> portalConfigURLs;

   /** . */
   private Set<String> profiles;

   public ContainerBuilder()
   {
      this.loader = Thread.currentThread().getContextClassLoader();
      this.configURLs = new ArrayList<URL>();
      this.portalConfigURLs = new LinkedHashMap<String, List<URL>>();
   }

   public ContainerBuilder withRoot(String configPath)
   {
      configURLs.addAll(urls(configPath));
      return this;
   }

   public ContainerBuilder withRoot(URL configURL)
   {
      configURLs.add(configURL);
      return this;
   }

   public ContainerBuilder withPortal(String configPath)
   {
      return withPortal("portal", configPath);
   }

   public ContainerBuilder withPortal(String portalName, String configPath)
   {
      for (URL configURL : urls(configPath))
      {
         withPortal(portalName, configURL);
      }
      return this;
   }

   public ContainerBuilder withPortal(URL configURL)
   {
      return withPortal("portal", configURL);
   }

   public ContainerBuilder withPortal(String portalName, URL configURL)
   {
      List<URL> urls = portalConfigURLs.get(portalName);
      if (urls == null)
      {
         urls = new ArrayList<URL>();
         portalConfigURLs.put(portalName, urls);
      }
      urls.add(configURL);
      return this;
   }

   public ContainerBuilder withLoader(ClassLoader loader)
   {
      this.loader = loader;
      return this;
   }

   public ContainerBuilder profiledBy(String... profiles)
   {
      this.profiles = Tools.set(profiles);
      return this;
   }

   private List<URL> urls(String path)
   {
      try
      {
         return Collections.list(loader.getResources(path));
      }
      catch (IOException e)
      {
         AssertionFailedError err = new AssertionFailedError();
         err.initCause(e);
         throw err;
      }
   }

   public RootContainer build()
   {
      PrivilegedExceptionAction<RootContainer> action = new PrivilegedExceptionAction<RootContainer>()
      {
         public RootContainer run() throws Exception
         {
            return _build();
         }
      };
      try
      {
         return SecurityHelper.doPrivilegedExceptionAction(action);
      }
      catch (PrivilegedActionException pae)
      {
         Throwable cause = pae.getCause();
         AssertionFailedError err = new AssertionFailedError();
         err.initCause(cause);
         throw err;
      }
   }

   private RootContainer _build() throws Exception
   {
      //
      if (configURLs.size() == 0)
      {
         throw new IllegalStateException("Must provide at least one URL for building the root container");
      }

      // Must clear the top container first otherwise it's not going to work well
      // it's a big ugly but I don't want to change anything in the ExoContainerContext class for now
      // and this is for unit testing
      Field topContainerField = ExoContainerContext.class.getDeclaredField("topContainer");
      topContainerField.setAccessible(true);
      topContainerField.set(null, null);

      ExoContainerContext.setCurrentContainer(null);
      // Same remark than above
      Field singletonField = RootContainer.class.getDeclaredField("singleton_");
      singletonField.setAccessible(true);
      singletonField.set(null, null);

      // Setup profiles
      if (profiles == null)
      {
         PropertyManager.setProperty(PropertyManager.RUNTIME_PROFILES, "");
      }
      else
      {
         StringBuilder builder = new StringBuilder();
         for (Iterator<String> i = profiles.iterator();i.hasNext();)
         {
            builder.append(i.next());
            if (i.hasNext())
            {
               builder.append(',');
            }
         }
         PropertyManager.setProperty(PropertyManager.RUNTIME_PROFILES, builder.toString());
      }

      //
      ClassLoader rootCL = new ClassLoader(loader)
      {
         @Override
         public Enumeration<URL> getResources(String name) throws IOException
         {
            if ("conf/configuration.xml".equals(name))
            {
               return Collections.enumeration(configURLs);
            }
            else if ("conf/portal/configuration.xml".equals(name))
            {
               String portalName = bootedPortalName.get();
               return Collections.enumeration(portalConfigURLs.get(portalName));
            }
            else if ("conf/portal/test-configuration.xml".equals(name))
            {
               return Collections.enumeration(Collections.<URL>emptyList());
            }
            else
            {
               return super.getResources(name);
            }
         }
      };

      //
      ClassLoader oldCL = Thread.currentThread().getContextClassLoader();

      // Boot root container
      RootContainer root;
      try
      {
         Thread.currentThread().setContextClassLoader(rootCL);

         //
         root = RootContainer.getInstance();

         PortalContainer.reloadConfig();
         
         //
         for (String portalName : portalConfigURLs.keySet())
         {
            try
            {
               bootedPortalName.set(portalName);
               root.getPortalContainer(portalName);
            }
            finally
            {
               bootedPortalName.set(null);
            }
         }
      }
      finally
      {
         Thread.currentThread().setContextClassLoader(oldCL);
      }

      //
      return root;
   }

   public static RootContainer bootstrap(URL configurationURL, String... profiles)
   {
      ContainerBuilder builder = new ContainerBuilder();
      builder.withRoot(configurationURL);
      builder.profiledBy(profiles);
      return builder.build();
   }
}
