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

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

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.ldap.ByteString;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;

/**
 * This class defines a branch that should be included in the resulting LDIF.  A
 * branch may or may not have subordinate entries.
 */
public class Branch
{
  /** The DN for this branch entry. */
  private DN branchDN;

  /**
   * The number of entries that should be created below this branch for each
   * subordinate template.
   */
  private int[] numEntriesPerTemplate;

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

  /** The set of subordinate templates for this branch. */
  private Template[] subordinateTemplates;

  /** The set of template lines that correspond to the RDN components. */
  private TemplateLine[] rdnLines;

  /** The set of extra lines that should be included in this branch entry. */
  private TemplateLine[] extraLines;



  /**
   * Creates a new branch with the provided information.
   *
   * @param  templateFile  The template file in which this branch appears.
   * @param  branchDN      The DN for this branch entry.
   */
  public Branch(TemplateFile templateFile, DN branchDN)
  {
    this(templateFile, branchDN, new String[0], new int[0],
         new TemplateLine[0]);
  }



  /**
   * Creates a new branch with the provided information.
   *
   * @param  templateFile              The template file in which this branch
   *                                   appears.
   * @param  branchDN                  The DN for this branch entry.
   * @param  subordinateTemplateNames  The names of the subordinate templates
   *                                   used to generate entries below this
   *                                   branch.
   * @param  numEntriesPerTemplate     The number of entries that should be
   *                                   created below this branch for each
   *                                   subordinate template.
   * @param  extraLines                The set of extra lines that should be
   *                                   included in this branch entry.
   */
  public Branch(TemplateFile templateFile, DN branchDN,
                String[] subordinateTemplateNames, int[] numEntriesPerTemplate,
                TemplateLine[] extraLines)
  {
    this.branchDN                 = branchDN;
    this.subordinateTemplateNames = subordinateTemplateNames;
    this.numEntriesPerTemplate    = numEntriesPerTemplate;
    this.extraLines               = extraLines;

    subordinateTemplates = null;


    // Get the RDN template lines based just on the entry DN.
    Entry entry = createEntry(branchDN);

    ArrayList<LocalizableMessage> warnings = new ArrayList<>();
    ArrayList<TemplateLine> lineList = new ArrayList<>();

    for (String ocName : entry.getObjectClasses().values())
    {
      try
      {
        String[] valueStrings = new String[] { ocName };
        Tag[] tags = new Tag[1];
        tags[0] = new StaticTextTag();
        tags[0].initializeForBranch(templateFile, this, valueStrings, 0,
                                    warnings);

        TemplateLine l =
             new TemplateLine(DirectoryServer.getObjectClassAttributeType(), 0,
                              tags);
        lineList.add(l);
      }
      catch (Exception e)
      {
        // This should never happen.
        e.printStackTrace();
      }
    }

    for (List<Attribute> attrList : entry.getUserAttributes().values())
    {
      for (Attribute a : attrList)
      {
        for (ByteString v : a)
        {
          try
          {
            String[] valueStrings = new String[] { v.toString() };
            Tag[] tags = new Tag[] { new StaticTextTag() };
            tags[0].initializeForBranch(templateFile, this, valueStrings, 0, warnings);
            lineList.add(new TemplateLine(a.getAttributeType(), 0, tags));
          }
          catch (Exception e)
          {
            // This should never happen.
            e.printStackTrace();
          }
        }
      }
    }

    for (List<Attribute> attrList : entry.getOperationalAttributes().values())
    {
      for (Attribute a : attrList)
      {
        for (ByteString v : a)
        {
          try
          {
            String[] valueStrings = new String[] { v.toString() };
            Tag[] tags = new Tag[] { new StaticTextTag() };
            tags[0].initializeForBranch(templateFile, this, valueStrings, 0, warnings);
            lineList.add(new TemplateLine(a.getAttributeType(), 0, tags));
          }
          catch (Exception e)
          {
            // This should never happen.
            e.printStackTrace();
          }
        }
      }
    }

    rdnLines = new TemplateLine[lineList.size()];
    lineList.toArray(rdnLines);
  }



  /**
   * Performs any necessary processing to ensure that the branch initialization
   * is completed.  In particular, it should make sure that all referenced
   * subordinate templates actually exist in the template file.
   *
   * @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 completeBranchInitialization(Map<String,Template> templates)
         throws MakeLDIFException
  {
    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)
        {
          throw new MakeLDIFException(ERR_MAKELDIF_UNDEFINED_BRANCH_SUBORDINATE.get(
              branchDN, subordinateTemplateNames[i]));
        }
      }
    }
  }



  /**
   * Retrieves the DN for this branch entry.
   *
   * @return  The DN for this branch entry.
   */
  public DN getBranchDN()
  {
    return branchDN;
  }



  /**
   * Retrieves the names of the subordinate templates for this branch.
   *
   * @return  The names of the subordinate templates for this branch.
   */
  public String[] getSubordinateTemplateNames()
  {
    return subordinateTemplateNames;
  }



  /**
   * Retrieves the set of subordinate templates used to generate entries below
   * this branch.  Note that the subordinate templates will not be available
   * until the <CODE>completeBranchInitialization</CODE> method has been called.
   *
   * @return  The set of subordinate templates used to generate entries below
   *          this branch.
   */
  public Template[] getSubordinateTemplates()
  {
    return subordinateTemplates;
  }



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



  /**
   * Adds a new subordinate template to this branch.  Note that this should not
   * be used after <CODE>completeBranchInitialization</CODE> has been called.
   *
   * @param  name        The name of the template to use to generate the
   *                     entries.
   * @param  numEntries  The number of entries to create based on the template.
   */
  public void addSubordinateTemplate(String name, int numEntries)
  {
    String[] newNames  = new String[subordinateTemplateNames.length+1];
    int[]    newCounts = new int[numEntriesPerTemplate.length+1];

    System.arraycopy(subordinateTemplateNames, 0, newNames, 0,
                     subordinateTemplateNames.length);
    System.arraycopy(numEntriesPerTemplate, 0, newCounts, 0,
                     numEntriesPerTemplate.length);

    newNames[subordinateTemplateNames.length] = name;
    newCounts[numEntriesPerTemplate.length]   = numEntries;

    subordinateTemplateNames = newNames;
    numEntriesPerTemplate    = newCounts;
  }



  /**
   * Retrieves the set of extra lines that should be included in this branch
   * entry.
   *
   * @return  The set of extra lines that should be included in this branch
   *          entry.
   */
  public TemplateLine[] getExtraLines()
  {
    return extraLines;
  }



  /**
   * Adds the provided template line to the set of extra lines for this branch.
   *
   * @param  line  The line to add to the set of extra lines for this branch.
   */
  public void addExtraLine(TemplateLine line)
  {
    TemplateLine[] newExtraLines = new TemplateLine[extraLines.length+1];
    System.arraycopy(extraLines, 0, newExtraLines, 0, extraLines.length);
    newExtraLines[extraLines.length] = line;

    extraLines = newExtraLines;
  }



  /**
   * Indicates whether this branch contains a reference to the specified
   * attribute type, either in the RDN components of the DN or in the extra
   * lines.
   *
   * @param  attributeType  The attribute type for which to make the
   *                        determination.
   *
   * @return  <CODE>true</CODE> if the branch does contain the specified
   *          attribute type, or <CODE>false</CODE> if it does not.
   */
  public boolean hasAttribute(AttributeType attributeType)
  {
    if (branchDN.rdn().hasAttributeType(attributeType))
    {
      return true;
    }

    for (TemplateLine l : extraLines)
    {
      if (l.getAttributeType().equals(attributeType))
      {
        return true;
      }
    }

    return false;
  }



  /**
   * Writes the entry for this branch, as well as all appropriate subordinate
   * entries.
   *
   * @param  entryWriter  The entry writer to which the entries should be
   *                      written.
   *
   * @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)
         throws IOException, MakeLDIFException
  {
    // Create a new template entry and populate it based on the RDN attributes
    // and extra lines.
    TemplateEntry entry = new TemplateEntry(this);

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

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

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


    for (int i=0; i < subordinateTemplates.length; i++)
    {
      TagResult r =
           subordinateTemplates[i].writeEntries(entryWriter, branchDN,
                                                numEntriesPerTemplate[i]);
      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;
  }
}

