/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2015 ForgeRock AS
 */
package org.opends.server.tools;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.forgerock.opendj.config.AbstractManagedObjectDefinition;
import org.forgerock.opendj.config.DefinedDefaultBehaviorProvider;
import org.forgerock.opendj.config.ManagedObjectDefinition;
import org.forgerock.opendj.server.config.client.BackendCfgClient;
import org.forgerock.opendj.server.config.meta.PluggableBackendCfgDefn;
import org.forgerock.opendj.server.config.server.BackendCfg;
import org.opends.guitools.controlpanel.util.Utilities;
import org.opends.server.util.RemoveOnceNewConfigFrameworkIsUsed;

/**
 * Helper class for setup applications. It helps applications to provide a
 * backend type choice to the user.
 */
public class BackendTypeHelper
{

  /**
   * Filter the provided backend name by removing the backend suffix.
   *
   * @param dsCfgBackendName
   *          The backend name
   * @return The backend name with the '-backend' suffix filtered out
   */
  public static String filterSchemaBackendName(final String dsCfgBackendName)
  {
    final String cfgNameRegExp = "(.*)-backend.*";
    final Matcher regExpMatcher = Pattern.compile(cfgNameRegExp, Pattern.CASE_INSENSITIVE).matcher(dsCfgBackendName);
    if (regExpMatcher.matches())
    {
      return regExpMatcher.group(1);
    }

    return dsCfgBackendName;
  }

  /** Adaptor to allow backend type selection in UIs. */
  public static class BackendTypeUIAdapter
  {
    private final ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> backend;

    /**
     * Create a new {@code BackendTypeUIAdapter}.
     *
     * @param backend
     *          The backend to adapt
     */
    private BackendTypeUIAdapter(ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> backend)
    {
      this.backend = backend;
    }

    /**
     * Return a user friendly readable name for this backend.
     *
     * @return A user friendly readable name for this backend.
     */
    @Override
    public String toString()
    {
      return backend.getUserFriendlyName().toString();
    }

    @Override
    public boolean equals(Object obj)
    {
      return obj instanceof BackendTypeUIAdapter && ((BackendTypeUIAdapter) obj).toString().equals(toString());
    }

    @Override
    public int hashCode()
    {
      return toString().hashCode();
    }

    /**
     * Return the adapted backend object.
     *
     * @return The adapted backend object
     */
    public ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> getBackend()
    {
      return backend;
    }

    /**
     * Return the old configuration framework backend object.
     *
     * @return The old configuration framework backend object
     */
    @SuppressWarnings("unchecked")
    @RemoveOnceNewConfigFrameworkIsUsed
    public org.opends.server.admin.ManagedObjectDefinition<
        ? extends org.opends.server.admin.std.client.BackendCfgClient,
        ? extends org.opends.server.admin.std.server.BackendCfg> getLegacyConfigurationFrameworkBackend()
    {
      Utilities.initializeLegacyConfigurationFramework();

      for (org.opends.server.admin.AbstractManagedObjectDefinition<?, ?> oldConfigBackend :
        org.opends.server.admin.std.meta.PluggableBackendCfgDefn.getInstance().getAllChildren())
      {
        if (oldConfigBackend.getName().equals(getBackend().getName()))
        {
          return (org.opends.server.admin.ManagedObjectDefinition<
              ? extends org.opends.server.admin.std.client.BackendCfgClient,
              ? extends org.opends.server.admin.std.server.BackendCfg>) oldConfigBackend;
        }
      }
      throw new IllegalArgumentException("Impossible to find the equivalent backend type in old config framework: "
          + getBackend().getName());
    }
  }

  private final List<ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg>> backends;

  /** Creates a new backend type helper. */
  @SuppressWarnings("unchecked")
  public BackendTypeHelper()
  {
    Utilities.initializeConfigurationFramework();

    backends = new LinkedList<>();

    for (AbstractManagedObjectDefinition<?, ?> backendType : PluggableBackendCfgDefn.getInstance().getAllChildren())
    {
      // Filtering out only the non-abstract backends to avoid users attempt to create abstract ones
      if (backendType instanceof ManagedObjectDefinition)
      {
        final DefinedDefaultBehaviorProvider<String> defaultBehaviorProvider =
                (DefinedDefaultBehaviorProvider<String>) backendType.getPropertyDefinition("java-class")
                                                                    .getDefaultBehaviorProvider();
        final Iterator<String> defaultBackendClassNameIterator = defaultBehaviorProvider.getDefaultValues().iterator();
        if (!defaultBackendClassNameIterator.hasNext())
        {
          return;
        }
        addToBackendListIfClassExists(defaultBackendClassNameIterator.next(),
                (ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg>) backendType);
      }
    }
  }

  private void addToBackendListIfClassExists(final String backendClassName,
          final ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> backendToAdd)
  {
    try
    {
      Class.forName(backendClassName);
      backends.add(backendToAdd);
    }
    catch (ClassNotFoundException ignored)
    {
      // The backend is not supported in the running version.
    }
  }


  ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> retrieveBackendTypeFromName(
      final String backendTypeStr)
  {
    for (ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> backendType : getBackendTypes())
    {
      final String name = backendType.getName();
      if (backendTypeStr.equalsIgnoreCase(name)
          || backendTypeStr.equalsIgnoreCase(filterSchemaBackendName(name)))
      {
        return backendType;
      }
    }

    return null;
  }

  List<ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg>> getBackendTypes()
  {
    return backends;
  }

  String getPrintableBackendTypeNames()
  {
    String backendTypeNames = "";
    for (ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> backend : getBackendTypes())
    {
      backendTypeNames += filterSchemaBackendName(backend.getName()) + ", ";
    }

    if (backendTypeNames.isEmpty())
    {
      return "Impossible to retrieve supported backend type list";
    }

    return backendTypeNames.substring(0, backendTypeNames.length() - 2);
  }

  /**
   * Return a list which contains all available backend type adapted for UI.
   *
   * @return a list which contains all available backend type adapted for UI
   */
  public BackendTypeUIAdapter[] getBackendTypeUIAdaptors()
  {
    List<BackendTypeUIAdapter> adaptors = new ArrayList<>();
    for (ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> backend : getBackendTypes())
    {
      adaptors.add(new BackendTypeUIAdapter(backend));
    }

    return adaptors.toArray(new BackendTypeUIAdapter[adaptors.size()]);
  }

  /**
   * Return a BackendTypeUIAdapter which adapts the backend identified by the
   * provided backend name.
   *
   * @param backendName
   *          the backend name which identifies the backend to adapt.
   * @return a BackendTypeUIAdapter which adapts the backend identified by the
   *         provided backend name.
   */
  public static BackendTypeUIAdapter getBackendTypeAdapter(String backendName)
  {
    ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> backend =
        new BackendTypeHelper().retrieveBackendTypeFromName(backendName);
    return backend != null ? getBackendTypeAdapter(backend) : null;
  }

  /**
   * Return a BackendTypeUIAdapter which adapts the provided backend.
   *
   * @param backend
   *          the backend type to adapt.
   * @return a BackendTypeUIAdapter which adapts the provided backend.
   */
  public static BackendTypeUIAdapter getBackendTypeAdapter(
      ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> backend)
  {
    return new BackendTypeUIAdapter(backend);
  }

}
