/*
 * 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 2006-2009 Sun Microsystems, Inc.
 *      Portions Copyright 2014-2015 ForgeRock AS
 */
package org.opends.server.tools.makeldif;

import org.forgerock.i18n.LocalizableMessage;

import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;

import org.opends.server.types.AttributeType;
import org.opends.server.types.DN;

import static org.opends.messages.ToolMessages.*;
import static org.opends.server.util.StaticUtils.*;

/**
 * This class defines a template, which is a pattern that may be used to
 * generate entries.  A template may be used either below a branch or below
 * another template.
 */
public class Template
{
  /**
   * The attribute types that are used in the RDN for entries generated using
   * this template.
   */
  private AttributeType[] rdnAttributes;

  /** The number of entries to create for each subordinate template. */
  private int[] numEntriesPerTemplate;

  /** The name for this template. */
  private String name;

  /** The names of the subordinate templates below this template. */
  private String[] subordinateTemplateNames;

  /** The subordinate templates below this template. */
  private Template[] subordinateTemplates;

  /** The template file that contains this template. */
  private TemplateFile templateFile;

  /** The set of template lines for this template. */
  private TemplateLine[] templateLines;



  /**
   * Creates a new template with the provided information.
   *
   * @param  templateFile              The template file that contains this
   *                                   template.
   * @param  name                      The name for this template.
   * @param  rdnAttributes             The set of attribute types that are used
   *                                   in the RDN for entries generated using
   *                                   this template.
   * @param  subordinateTemplateNames  The names of the subordinate templates
   *                                   below this template.
   * @param  numEntriesPerTemplate     The number of entries to create below
   *                                   each subordinate template.
   */
  public Template(TemplateFile templateFile, String name,
                  AttributeType[] rdnAttributes,
                  String[] subordinateTemplateNames,
                  int[] numEntriesPerTemplate)
  {
    this.templateFile             = templateFile;
    this.name                     = name;
    this.rdnAttributes            = rdnAttributes;
    this.subordinateTemplateNames = subordinateTemplateNames;
    this.numEntriesPerTemplate    = numEntriesPerTemplate;

    templateLines        = new TemplateLine[0];
    subordinateTemplates = null;
  }



  /**
   * Creates a new template with the provided information.
   *
   * @param  templateFile              The template file that contains this
   *                                   template.
   * @param  name                      The name for this template.
   * @param  rdnAttributes             The set of attribute types that are used
   *                                   in the RDN for entries generated using
   *                                   this template.
   * @param  subordinateTemplateNames  The names of the subordinate templates
   *                                   below this template.
   * @param  numEntriesPerTemplate     The number of entries to create below
   *                                   each subordinate template.
   * @param  templateLines             The set of template lines for this
   *                                   template.
   */
  public Template(TemplateFile templateFile, String name,
                  AttributeType[] rdnAttributes,
                  String[] subordinateTemplateNames,
                  int[] numEntriesPerTemplate, TemplateLine[] templateLines)
  {
    this.templateFile             = templateFile;
    this.name                     = name;
    this.rdnAttributes            = rdnAttributes;
    this.subordinateTemplateNames = subordinateTemplateNames;
    this.numEntriesPerTemplate    = numEntriesPerTemplate;
    this.templateLines            = templateLines;

    subordinateTemplates = null;
  }



  /**
   * Performs any necessary processing to ensure that the template
   * initialization is completed.  In particular, it should make sure that all
   * referenced subordinate templates actually exist in the template file, and
   * that all of the RDN attributes are contained in the template lines.
   *
   * @param  templates  The set of templates defined in the template file.
   *
   * @throws  MakeLDIFException  If any of the subordinate templates are not
   *                             defined in the template file.
   */
  public void completeTemplateInitialization(Map<String,Template> templates)
         throws MakeLDIFException
  {
    // Make sure that all of the specified subordinate templates exist.
    if (subordinateTemplateNames == null)
    {
      subordinateTemplateNames = new String[0];
      subordinateTemplates     = new Template[0];
    }
    else
    {
      subordinateTemplates = new Template[subordinateTemplateNames.length];
      for (int i=0; i < subordinateTemplates.length; i++)
      {
        subordinateTemplates[i] =
             templates.get(toLowerCase(subordinateTemplateNames[i]));
        if (subordinateTemplates[i] == null)
        {
          LocalizableMessage message = ERR_MAKELDIF_UNDEFINED_TEMPLATE_SUBORDINATE.get(
              subordinateTemplateNames[i], name);
          throw new MakeLDIFException(message);
        }
      }
    }


    // Make sure that all of the RDN attributes are defined.
    HashSet<AttributeType> rdnAttrs = new HashSet<>(rdnAttributes.length);
    Collections.addAll(rdnAttrs, rdnAttributes);

    for (TemplateLine l : templateLines)
    {
      if (rdnAttrs.remove(l.getAttributeType())
          && rdnAttrs.isEmpty())
      {
        break;
      }
    }

    if (! rdnAttrs.isEmpty())
    {
      AttributeType t       = rdnAttrs.iterator().next();
      LocalizableMessage message =
          ERR_MAKELDIF_TEMPLATE_MISSING_RDN_ATTR.get(name, t.getNameOrOID());
      throw new MakeLDIFException(message);
    }
  }



  /**
   * Retrieves the name for this template.
   *
   * @return  The name for this template.
   */
  public String getName()
  {
    return name;
  }



  /**
   * Retrieves the set of attribute types that are used in the RDN for entries
   * generated using this template.
   *
   * @return  The set of attribute types that are used in the RDN for entries
   *          generated using this template.
   */
  public AttributeType[] getRDNAttributes()
  {
    return rdnAttributes;
  }



  /**
   * Retrieves the names of the subordinate templates used to generate entries
   * below entries created by this template.
   *
   * @return  The names of the subordinate templates used to generate entries
   *          below entries created by this template.
   */
  public String[] getSubordinateTemplateNames()
  {
    return subordinateTemplateNames;
  }



  /**
   * Retrieves the subordinate templates used to generate entries below entries
   * created by this template.
   *
   * @return  The subordinate templates used to generate entries below entries
   *          created by this template.
   */
  public Template[] getSubordinateTemplates()
  {
    return subordinateTemplates;
  }



  /**
   * Retrieves the number of entries that should be created for each subordinate
   * template.
   *
   * @return  The number of entries that should be created for each subordinate
   *          template.
   */
  public int[] getNumEntriesPerTemplate()
  {
    return numEntriesPerTemplate;
  }



  /**
   * Retrieves the set of template lines for this template.
   *
   * @return  The set of template lines for this template.
   */
  public TemplateLine[] getTemplateLines()
  {
    return templateLines;
  }



  /**
   * Adds the provided template line to this template.
   *
   * @param  line  The template line to add to this template.
   */
  public void addTemplateLine(TemplateLine line)
  {
    TemplateLine[] newTemplateLines = new TemplateLine[templateLines.length+1];
    System.arraycopy(templateLines, 0, newTemplateLines, 0,
                     templateLines.length);
    newTemplateLines[templateLines.length] = line;
    templateLines = newTemplateLines;
  }



  /**
   * Indicates whether this template contains any template lines that reference
   * the provided attribute type.
   *
   * @param  attributeType  The attribute type for which to make the
   *                        determination.
   *
   * @return  <CODE>true</CODE> if this template contains one or more template
   *          lines that reference the provided attribute type, or
   *          <CODE>false</CODE> if not.
   */
  public boolean hasAttribute(AttributeType attributeType)
  {
    for (TemplateLine l : templateLines)
    {
      if (l.getAttributeType().equals(attributeType))
      {
        return true;
      }
    }

    return false;
  }



  /**
   * Writes the entry for this template, as well as all appropriate subordinate
   * entries.
   *
   * @param  entryWriter  The entry writer that will be used to write the
   *                      entries.
   * @param  parentDN     The DN of the entry below which the subordinate
   *                      entries should be generated.
   * @param  count        The number of entries to generate based on this
   *                      template.
   *
   * @return  The result that indicates whether processing should continue.
   *
   * @throws  IOException  If a problem occurs while attempting to write to the
   *                       LDIF writer.
   *
   * @throws  MakeLDIFException  If some other problem occurs.
   */
  public TagResult writeEntries(EntryWriter entryWriter, DN parentDN, int count)
         throws IOException, MakeLDIFException
  {
    for (int i=0; i < count; i++)
    {
      templateFile.nextFirstAndLastNames();
      TemplateEntry templateEntry = new TemplateEntry(this, parentDN);

      for (TemplateLine l : templateLines)
      {
        TagResult r = l.generateLine(templateEntry);
        if (!r.keepProcessingEntry()
            || !r.keepProcessingParent()
            || !r.keepProcessingTemplateFile())
        {
          return r;
        }
      }

      if (! entryWriter.writeEntry(templateEntry))
      {
        return TagResult.STOP_PROCESSING;
      }

      for (int j=0; j < subordinateTemplates.length; j++)
      {
        TagResult r =
             subordinateTemplates[j].writeEntries(entryWriter,
                 templateEntry.getDN(), numEntriesPerTemplate[j]);
        if (!r.keepProcessingParent()
            || !r.keepProcessingTemplateFile())
        {
          if (r.keepProcessingTemplateFile())
          {
            // We don't want to propagate a "stop processing parent" all the
            // way up the chain.
            return TagResult.SUCCESS_RESULT;
          }

          return r;
        }
      }
    }

    return TagResult.SUCCESS_RESULT;
  }
}

