/**
 * 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.xml;

import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.jibx.runtime.BindingDirectory;
import org.jibx.runtime.IBindingFactory;
import org.jibx.runtime.IMarshallingContext;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Jul 19, 2004
 * 
 */
public class Configuration implements Cloneable
{

   public static final String KERNEL_CONFIGURATION_1_0_URI = "http://www.exoplaform.org/xml/ns/kernel_1_0.xsd";

   private static final Log LOG = ExoLogger.getLogger("exo.kernel.container.Configuration");

   /**
    * We need this thread local to be able to detect the normal objects from the components while serializing the configuration
    */
   private static final ThreadLocal<Configuration> CURRENT_CONFIG_TO_SERIALIZE = new ThreadLocal<Configuration>();

   private Map<String, ContainerLifecyclePlugin> containerLifecyclePlugin_ =
      new HashMap<String, ContainerLifecyclePlugin>();

   private Map<String, ComponentLifecyclePlugin> componentLifecyclePlugin_ =
      new HashMap<String, ComponentLifecyclePlugin>();

   private Map<String, Component> component_ = new HashMap<String, Component>();

   private Map<String, ExternalComponentPlugins> externalComponentPlugins_ =
      new HashMap<String, ExternalComponentPlugins>();

   private List<String> imports_;

   private List<String> removeConfiguration_;

   private int currentSize;

   private int currentHash;

   public Collection<ContainerLifecyclePlugin> getContainerLifecyclePlugins()
   {
      List<ContainerLifecyclePlugin> plugins =
         new ArrayList<ContainerLifecyclePlugin>(containerLifecyclePlugin_.values());
      Collections.sort(plugins);
      return plugins;
   }

   public void addContainerLifecyclePlugin(ContainerLifecyclePlugin plugin)
   {
      String key = plugin.getType();
      containerLifecyclePlugin_.put(key, plugin);
   }

   public Iterator<ContainerLifecyclePlugin> getContainerLifecyclePluginIterator()
   {
      return getContainerLifecyclePlugins().iterator();
   }

   public boolean hasContainerLifecyclePlugin()
   {
      return containerLifecyclePlugin_.size() > 0;
   }

   public Collection<ComponentLifecyclePlugin> getComponentLifecyclePlugins()
   {
      return componentLifecyclePlugin_.values();
   }

   public void addComponentLifecyclePlugin(Object object)
   {
      ComponentLifecyclePlugin plugin = (ComponentLifecyclePlugin)object;
      String key = plugin.getType();
      componentLifecyclePlugin_.put(key, plugin);
   }

   public Iterator<ComponentLifecyclePlugin> getComponentLifecyclePluginIterator()
   {
      return componentLifecyclePlugin_.values().iterator();
   }

   public boolean hasComponentLifecyclePlugin()
   {
      return componentLifecyclePlugin_.size() > 0;
   }

   public Component getComponent(String s)
   {
      return component_.get(s);
   }

   public void addComponent(Component comp)
   {
      String key = comp.getKey();
      if (key == null)
      {
         key = comp.getType();
         comp.setKey(key);
      }
      component_.put(key, comp);
   }

   public Collection<Component> getComponents()
   {
      return component_.values();
   }

   public Iterator<Component> getComponentIterator()
   {
      return component_.values().iterator();
   }

   public boolean hasComponent()
   {
      return component_.size() > 0;
   }

   public ExternalComponentPlugins getExternalComponentPlugins(String s)
   {
      return externalComponentPlugins_.get(s);
   }

   public void addExternalComponentPlugins(ExternalComponentPlugins eps)
   {
      // Retrieve potential existing external component
      // plugins with same target component.
      String targetComponent = eps.getTargetComponent();
      ExternalComponentPlugins foundExternalComponentPlugins =
         (ExternalComponentPlugins)externalComponentPlugins_.get(targetComponent);

      if (foundExternalComponentPlugins == null)
      {
         // No external component plugins found. Create a new entry.
         externalComponentPlugins_.put(targetComponent, eps);
      }
      else
      {
         // Found external component plugins. Add the specified one.
         foundExternalComponentPlugins.merge(eps);
      }
   }

   public Iterator<ExternalComponentPlugins> getExternalComponentPluginsIterator()
   {
      return externalComponentPlugins_.values().iterator();
   }

   public boolean hasExternalComponentPlugins()
   {
      return externalComponentPlugins_.size() > 0;
   }

   public void addImport(String url)
   {
      if (imports_ == null)
         imports_ = new ArrayList<String>();
      imports_.add(url);
   }

   public List<String> getImports()
   {
      return imports_;
   }

   public void addRemoveConfiguration(String type)
   {
      if (removeConfiguration_ == null)
         removeConfiguration_ = new ArrayList<String>();
      removeConfiguration_.add(type);
   }

   public List<String> getRemoveConfiguration()
   {
      return removeConfiguration_;
   }

   public void removeConfiguration(String type)
   {
      component_.remove(type);
   }

   // -------------------------end new mapping configuration--------------------

   public void mergeConfiguration(Configuration other)
   {
      component_.putAll(other.component_);

      componentLifecyclePlugin_.putAll(other.componentLifecyclePlugin_);
      containerLifecyclePlugin_.putAll(other.containerLifecyclePlugin_);

      // merge the external plugins
      Iterator<ExternalComponentPlugins> i = other.externalComponentPlugins_.values().iterator();
      while (i.hasNext())
      {
         addExternalComponentPlugins(i.next());
      }

      if (other.getRemoveConfiguration() == null)
         return;
      if (removeConfiguration_ == null)
         removeConfiguration_ = new ArrayList<String>();
      removeConfiguration_.addAll(other.getRemoveConfiguration());
   }

   /**
    * Merge all the given configurations and return a safe copy of the result
    * @param configs the list of configurations to merge ordered by priority, the second
    * configuration will override the configuration of the first one and so on.
    * @return the merged configuration
    */
   public static Configuration merge(Configuration... configs)
   {
      if (configs == null || configs.length == 0)
      {
         return null;
      }
      Configuration result = null;
      for (Configuration conf : configs)
      {
         if (conf == null)
         {
            // Ignore the null configuration
            continue;
         }
         else if (result == null)
         {
            // Initialize with the clone of the first non null configuration 
            result = (Configuration)conf.clone();
         }
         else
         {
            // The merge the current configuration with this new configuration
            result.mergeConfiguration(conf);
         }
      }
      return result;
   }
   
   /**
    * {@inheritDoc}
    */
   @SuppressWarnings("unchecked")
   @Override
   protected Object clone()
   {
      try
      {
         Configuration conf = (Configuration)super.clone();
         conf.component_ = (Map<String, Component>)((HashMap<String, Component>)component_).clone();
         conf.componentLifecyclePlugin_ =
            (Map<String, ComponentLifecyclePlugin>)((HashMap<String, ComponentLifecyclePlugin>)componentLifecyclePlugin_)
               .clone();
         conf.containerLifecyclePlugin_ =
            (Map<String, ContainerLifecyclePlugin>)((HashMap<String, ContainerLifecyclePlugin>)containerLifecyclePlugin_)
               .clone();
         conf.externalComponentPlugins_ =
            (Map<String, ExternalComponentPlugins>)((HashMap<String, ExternalComponentPlugins>)externalComponentPlugins_)
               .clone();
         if (imports_ != null)
         {
            conf.imports_ = (List<String>)((ArrayList<String>)imports_).clone();
         }
         if (removeConfiguration_ != null)
         {
            conf.removeConfiguration_ =  (List<String>)((ArrayList<String>)removeConfiguration_).clone();
         }
         return conf;
      }
      catch (CloneNotSupportedException e)
      {
         throw new AssertionError("Could not clone the configuration");
      }
   }

   /**
    * Indicates whether or not the given key has been registered has a component within the current
    * configuration if and only if we have a configuration currently defined. If no configuration
    * has been defined it will always return <code>false</code>
    */
   public static boolean hasComponent(String key)
   {
      Configuration config = CURRENT_CONFIG_TO_SERIALIZE.get();
      if (config == null)
         return false;
      return config.getComponent(key) != null;
   }

   /**
    * Dumps the configuration in XML format into the given {@link Writer}
    */
   public void toXML(Writer w)
   {
      try
      {
         CURRENT_CONFIG_TO_SERIALIZE.set(this);
         IBindingFactory bfact = BindingDirectory.getFactory(Configuration.class);
         IMarshallingContext mctx = bfact.createMarshallingContext();
         mctx.setIndent(2);
         mctx.marshalDocument(this, "UTF-8", null, w);
      }
      catch (Exception e)
      {
         LOG.warn("Couldn't dump the runtime configuration in XML Format", e);
      }
      finally
      {
         CURRENT_CONFIG_TO_SERIALIZE.remove();
      }
   }

   /**
    * Dumps the configuration in XML format into a {@link StringWriter} and 
    * returns the content
    */
   public String toXML()
   {
      StringWriter sw = new StringWriter();
      try
      {
         toXML(sw);
      }
      catch (Exception e)
      {
         LOG.warn("Cannot convert the configuration to XML format", e);
         return null;
      }
      finally
      {
         try
         {
            sw.close();
         }
         catch (IOException ignore)
         {
            if (LOG.isTraceEnabled())
            {
               LOG.trace("An exception occurred: " + ignore.getMessage());
            }
         }
      }
      return sw.toString();
   }
   
   /**
    * This will keep the current state of the configuration like its size and hash
    */
   public void keepCurrentState()
   {
      String xml = toXML();
      if (xml != null)
      {
         this.currentSize = xml.length();
         this.currentHash = xml.hashCode();
      }
   }

   /**
    * @return the currentSize
    */
   public int getCurrentSize()
   {
      return currentSize;
   }

   /**
    * @return the currentHash
    */
   public int getCurrentHash()
   {
      return currentHash;
   }
}
