/*
 * 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 2008-2010 Sun Microsystems, Inc.
 *      Portions Copyright 2014-2015 ForgeRock AS
 */
package org.opends.guitools.controlpanel.util;

import static org.forgerock.util.Reject.*;
import static org.opends.messages.ConfigMessages.*;
import static org.opends.server.util.StaticUtils.*;

import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Set;

import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.config.server.ConfigException;
import org.forgerock.opendj.ldap.ConditionResult;
import org.opends.server.admin.std.server.BackendCfg;
import org.opends.server.api.ConfigHandler;
import org.opends.server.config.ConfigEntry;
import org.opends.server.core.AddOperation;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.SearchOperation;
import org.opends.server.core.ServerContext;
import org.opends.server.types.AttributeType;
import org.opends.server.types.BackupConfig;
import org.opends.server.types.BackupDirectory;
import org.opends.server.types.CanceledOperationException;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryEnvironmentConfig;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.IndexType;
import org.opends.server.types.InitializationException;
import org.opends.server.types.LDIFExportConfig;
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.types.LDIFImportResult;
import org.opends.server.types.RestoreConfig;
import org.opends.server.util.LDIFException;
import org.opends.server.util.LDIFReader;

/**
 * A class used to read the configuration from a file.  This config file
 * handler does not allow to modify the configuration, only to read it.
 */
public class ReadOnlyConfigFileHandler extends ConfigHandler<BackendCfg>
{
  /**
   * The mapping that holds all of the configuration entries that have been read
   * from the LDIF file.
   */
  private HashMap<DN,ConfigEntry> configEntries = new HashMap<>();

  /** The reference to the configuration root entry. */
  private ConfigEntry configRootEntry;

  /** The server root. */
  private String serverRoot;

  /** The instance root. */
  private String instanceRoot;

  private DN[] baseDNs;

  /** {@inheritDoc} */
  @Override
  public void finalizeConfigHandler()
  {
    finalizeBackend();
  }

  /** {@inheritDoc} */
  @Override
  public ConfigEntry getConfigEntry(DN entryDN) throws ConfigException
  {
    return configEntries.get(entryDN);
  }

  /** {@inheritDoc} */
  @Override
  public ConfigEntry getConfigRootEntry() throws ConfigException
  {
    return configRootEntry;
  }

  /** {@inheritDoc} */
  @Override
  public String getServerRoot()
  {
    return serverRoot;
  }

  /** {@inheritDoc} */
  @Override
  public String getInstanceRoot()
  {
    return instanceRoot;
  }

  /** {@inheritDoc} */
  @Override
  public synchronized void initializeConfigHandler(String configFile,
      boolean checkSchema)
  throws InitializationException
  {
    File f = new File(configFile);
    // We will use the LDIF reader to read the configuration file.  Create an
    // LDIF import configuration to do this and then get the reader.
    LDIFReader reader = null;
    try
    {
      try
      {
        LDIFImportConfig importConfig =
          new LDIFImportConfig(f.getAbsolutePath());

        reader = new LDIFReader(importConfig);
      }
      catch (Throwable t)
      {
        throw new InitializationException(
            ERR_CONFIG_FILE_CANNOT_OPEN_FOR_READ.get(f.getAbsolutePath(), t), t);
      }

      if (! f.exists())
      {
        LocalizableMessage message =
          ERR_CONFIG_FILE_DOES_NOT_EXIST.get(f.getAbsolutePath());
        throw new InitializationException(message);
      }

      configEntries.clear();

      // Read the first entry from the configuration file.
      Entry entry;
      try
      {
        entry = reader.readEntry(checkSchema);
        if (entry == null)
        {
          LocalizableMessage message = ERR_CONFIG_FILE_EMPTY.get(f.getAbsolutePath());
          throw new InitializationException(message);
        }
        configRootEntry = new ConfigEntry(entry, null);

        baseDNs = new DN[] { configRootEntry.getDN() };

        configEntries.put(entry.getName(), configRootEntry);
        // Iterate through the rest of the configuration file and process the
        // remaining entries.
        while (entry != null)
        {
          // Read the next entry from the configuration.
          entry = reader.readEntry(checkSchema);
          if (entry != null)
          {
            DN entryDN = entry.getName();
            DN parentDN = entryDN.parent();
            ConfigEntry parentEntry = null;
            if (parentDN != null)
            {
              parentEntry = configEntries.get(parentDN);
            }
            if (parentEntry == null)
            {
              if (parentDN == null)
              {
                LocalizableMessage message = ERR_CONFIG_FILE_UNKNOWN_PARENT.get(
                    entryDN, reader.getLastEntryLineNumber(), f.getAbsolutePath());
                throw new InitializationException(message);
              }
              else
              {
                LocalizableMessage message = ERR_CONFIG_FILE_NO_PARENT.get(entryDN,
                    reader.getLastEntryLineNumber(), f.getAbsolutePath(), parentDN);
                throw new InitializationException(message);
              }
            }
            else
            {
              ConfigEntry configEntry = new ConfigEntry(entry, parentEntry);
              parentEntry.addChild(configEntry);
              configEntries.put(entryDN, configEntry);
            }
          }
        }
      }
      catch (InitializationException ie)
      {
        throw ie;
      }
      catch (LDIFException le)
      {
        throw new InitializationException(
            ERR_CONFIG_FILE_INVALID_LDIF_ENTRY.get(le.getLineNumber(), f.getAbsolutePath(), le), le);
      }
      catch (Throwable t)
      {
        throw new InitializationException(
            ERR_CONFIG_FILE_READ_ERROR.get(f.getAbsolutePath(), t), t);
      }


      // Determine the appropriate server root.
      File rootFile = DirectoryServer.getEnvironmentConfig().getServerRoot();
      serverRoot = rootFile.getAbsolutePath();

      File instanceRootFile =
        DirectoryEnvironmentConfig.getInstanceRootFromServerRoot(rootFile);
      instanceRoot = instanceRootFile.getAbsolutePath();
    }
    catch (InitializationException ie)
    {
      throw ie;
    }
    catch (Throwable t)
    {
    }
    finally
    {
      close(reader);
    }
  }

  /** {@inheritDoc} */
  @Override
  public void writeSuccessfulStartupConfig()
  {
  }

  /** {@inheritDoc} */
  @Override
  public void writeUpdatedConfig() throws DirectoryException
  {
  }

  /** {@inheritDoc} */
  @Override
  public void addEntry(Entry entry, AddOperation arg1)
  throws DirectoryException, CanceledOperationException
  {
  }

  /** {@inheritDoc} */
  @Override
  public void configureBackend(BackendCfg cfg, ServerContext serverContext) throws ConfigException
  {
  }

  /** {@inheritDoc} */
  @Override
  public void createBackup(BackupConfig arg0) throws DirectoryException
  {
  }

  /** {@inheritDoc} */
  @Override
  public void deleteEntry(DN arg0, DeleteOperation arg1)
  throws DirectoryException, CanceledOperationException
  {
  }

  /** {@inheritDoc} */
  @Override
  public void exportLDIF(LDIFExportConfig arg0) throws DirectoryException
  {
  }

  /** {@inheritDoc} */
  @Override
  public DN[] getBaseDNs()
  {
    return baseDNs;
  }

  /** {@inheritDoc} */
  @Override
  public Entry getEntry(DN entryDN)
  throws DirectoryException
  {
    ConfigEntry configEntry = configEntries.get(entryDN);
    if (configEntry != null)
    {
      return configEntry.getEntry();
    }
    return null;
  }

  /** {@inheritDoc} */
  @Override
  public long getEntryCount()
  {
    return configEntries.size();
  }

  /** {@inheritDoc} */
  @Override
  public Set<String> getSupportedControls()
  {
    return Collections.emptySet();
  }

  /** {@inheritDoc} */
  @Override
  public Set<String> getSupportedFeatures()
  {
    return Collections.emptySet();
  }

  /** {@inheritDoc} */
  @Override
  public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException
  {
    ConfigEntry baseEntry = configEntries.get(entryDN);
    if (baseEntry != null)
    {
      return ConditionResult.valueOf(baseEntry.hasChildren());
    }
    return ConditionResult.UNDEFINED;
  }

  /** {@inheritDoc} */
  @Override
  public LDIFImportResult importLDIF(LDIFImportConfig importConfig, ServerContext serverContext)
      throws DirectoryException
  {
    return null;
  }

  /** {@inheritDoc} */
  @Override
  public void openBackend() throws ConfigException, InitializationException
  {
  }

  /** {@inheritDoc} */
  @Override
  public boolean isIndexed(AttributeType arg0, IndexType arg1)
  {
    return false;
  }

  /** {@inheritDoc} */
  @Override
  public long getNumberOfChildren(DN parentDN) throws DirectoryException {
    checkNotNull(parentDN, "parentDN must not be null");
    return numSubordinates(parentDN, false);
  }

  /** {@inheritDoc} */
  @Override
  public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException {
    checkNotNull(baseDN, "baseDN must not be null");
    return numSubordinates(baseDN, true) + 1;
  }

  private long numSubordinates(DN entryDN, boolean subtree) throws DirectoryException
  {
    final ConfigEntry baseEntry = configEntries.get(entryDN);
    if (baseEntry == null)
    {
      return -1;
    }

    if(!subtree)
    {
      return baseEntry.getChildren().size();
    }
    long count = 0;
    for (ConfigEntry child : baseEntry.getChildren().values())
    {
      count += numSubordinates(child.getDN(), true);
      count++;
    }
    return count;
  }

  /** {@inheritDoc} */
  @Override
  public void removeBackup(BackupDirectory backupDirectory, String backupID)
  throws DirectoryException
  {
  }

  /** {@inheritDoc} */
  @Override
  public void renameEntry(DN currentDN, Entry entry, ModifyDNOperation modifyDNOperation)
  throws DirectoryException, CanceledOperationException
  {
  }

  /** {@inheritDoc} */
  @Override
  public void replaceEntry(Entry oldEntry, Entry newEntry, ModifyOperation modifyOperation)
  throws DirectoryException, CanceledOperationException
  {
  }

  /** {@inheritDoc} */
  @Override
  public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException
  {
  }

  /** {@inheritDoc} */
  @Override
  public void search(SearchOperation searchOperation)
  throws DirectoryException, CanceledOperationException
  {
  }

  /** {@inheritDoc} */
  @Override
  public boolean supports(BackendOperation backendOperation)
  {
    return false;
  }
}
