/*
 * 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
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2009 Sun Microsystems, Inc.
 */
package org.opends.server.replication.protocol;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.zip.DataFormatException;

import org.opends.server.replication.common.ChangeNumber;


/**
 * This class specifies the parameters of a search request on the ECL.
 * It is used as an interface between the requestor (plugin part)
 * - either as an han
 */
public class StartECLSessionMsg extends ReplicationMsg
{

  /**
   * This specifies that the ECL is requested from a provided cookie value
   * defined as a MultiDomainServerState.
   */
  public final static short REQUEST_TYPE_FROM_COOKIE = 0;

  /**
   * This specifies that the ECL is requested from a provided interval
   * of change numbers (as defined by draft-good-ldap-changelog [CHANGELOG]
   * and NOT replication change numbers).
   * TODO: not yet implemented
   */
  public final static short REQUEST_TYPE_FROM_DRAFT_CHANGE_NUMBER = 1;

  /**
   * This specifies that the ECL is requested ony for the entry that have
   * a repl change number matching the provided one.
   * TODO: not yet implemented
   */
  public final static short REQUEST_TYPE_EQUALS_REPL_CHANGE_NUMBER = 2;



  /**
   * This specifies that the request on the ECL is a PERSISTENT search
   * with changesOnly = false.
   */
  public final static short PERSISTENT = 0;

  /**
   * This specifies that the request on the ECL is a NOT a PERSISTENT search.
   */
  public final static short NON_PERSISTENT = 1;

  /**
   * This specifies that the request on the ECL is a PERSISTENT search
   * with changesOnly = false.
   */
  public final static short PERSISTENT_CHANGES_ONLY = 2;;



  // The type of request as defined by REQUEST_TYPE_...
  private short  eclRequestType;

  // When eclRequestType = FROM_COOKIE,
  // specifies the provided cookie value.
  private String crossDomainServerState = "";

  // When eclRequestType = FROM_CHANGE_NUMBER,
  // specifies the provided change number first and last - [CHANGELOG]
  private int    firstDraftChangeNumber = -1;
  private int    lastDraftChangeNumber = -1;

  // When eclRequestType = EQUALS_REPL_CHANGE_NUMBER,
  // specifies the provided replication change number.
  private ChangeNumber changeNumber;

  // Specifies whether the search is persistent and changesOnly
  private short  isPersistent = NON_PERSISTENT;

  // A string helping debuging and tracing the client operation related when
  // processing, on the RS side, a request on the ECL.
  private String operationId = "";

  // Excluded domains
  private ArrayList<String> excludedServiceIDs = new ArrayList<String>();

  /**
   * Creates a new StartSessionMsg message from its encoded form.
   *
   * @param in The byte array containing the encoded form of the message.
   * @throws java.util.zip.DataFormatException If the byte array does not
   * contain a valid encoded form of the message.
   */
  public StartECLSessionMsg(byte[] in) throws DataFormatException
  {
    /*
     * The message is stored in the form:
     * <message type><status><assured flag><assured mode><safe data level>
     * <list of referrals urls>
     * (each referral url terminates with 0)
     */

    try
    {
      /* first bytes are the header */
      int pos = 0;

      /* first byte is the type */
      if (in.length < 1 || in[pos++] != MSG_TYPE_START_ECL_SESSION)
      {
        throw new DataFormatException(
          "Input is not a valid " + this.getClass().getCanonicalName());
      }

      // start mode
      int length = getNextLength(in, pos);
      eclRequestType = Short.valueOf(new String(in, pos, length, "UTF-8"));
      pos += length +1;

      // sequenceNumber
      length = getNextLength(in, pos);
      firstDraftChangeNumber =
        Integer.valueOf(new String(in, pos, length, "UTF-8"));
      pos += length +1;

      // stopSequenceNumber
      length = getNextLength(in, pos);
      lastDraftChangeNumber =
        Integer.valueOf(new String(in, pos, length, "UTF-8"));
      pos += length +1;

      // replication changeNumber
      length = getNextLength(in, pos);
      String changenumberStr = new String(in, pos, length, "UTF-8");
      pos += length + 1;
      changeNumber = new ChangeNumber(changenumberStr);

      // persistentSearch mode
      length = getNextLength(in, pos);
      isPersistent = Short.valueOf(new String(in, pos, length, "UTF-8"));
      pos += length + 1;

      // generalized state
      length = getNextLength(in, pos);
      crossDomainServerState = new String(in, pos, length, "UTF-8");
      pos += length + 1;

      // operation id
      length = getNextLength(in, pos);
      operationId = new String(in, pos, length, "UTF-8");
      pos += length + 1;

      // excluded DN
      length = getNextLength(in, pos);
      String excludedDNsString = new String(in, pos, length, "UTF-8");
      if (excludedDNsString.length()>0)
      {
        String[] excludedDNsStr = excludedDNsString.split(";");
        for (String excludedDNStr : excludedDNsStr)
        {
          this.excludedServiceIDs.add(excludedDNStr);
        }
      }
      pos += length + 1;

    } catch (UnsupportedEncodingException e)
    {
      throw new DataFormatException("UTF-8 is not supported by this jvm.");
    } catch (IllegalArgumentException e)
    {
      throw new DataFormatException(e.getMessage());
    }
  }

  /**
   * Creates a new StartSessionMsg message with the given required parameters.
   */
  public StartECLSessionMsg()
  {
    eclRequestType = REQUEST_TYPE_FROM_COOKIE;
    crossDomainServerState = "";
    firstDraftChangeNumber = -1;
    lastDraftChangeNumber = -1;
    changeNumber = new ChangeNumber((short)0,0,(short)0);
    isPersistent = NON_PERSISTENT;
    operationId = "-1";
    excludedServiceIDs = new ArrayList<String>();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public byte[] getBytes()
  {
    String excludedSIDsString = new String();
    for (String excludedServiceID : excludedServiceIDs)
    {
      excludedSIDsString = excludedSIDsString.concat(excludedServiceID+";");
    }

    try
    {
      byte[] byteMode =
        Short.toString(eclRequestType).getBytes("UTF-8");
      byte[] byteSequenceNumber =
        String.valueOf(firstDraftChangeNumber).getBytes("UTF-8");
      byte[] byteStopSequenceNumber =
        String.valueOf(lastDraftChangeNumber).getBytes("UTF-8");
      byte[] byteChangeNumber =
        changeNumber.toString().getBytes("UTF-8");
      byte[] bytePsearch =
        Short.toString(isPersistent).getBytes();
      byte[] byteGeneralizedState =
        String.valueOf(crossDomainServerState).getBytes("UTF-8");
      byte[] byteOperationId =
        String.valueOf(operationId).getBytes("UTF-8");
      byte[] byteExcludedDNs =
        String.valueOf(excludedSIDsString).getBytes("UTF-8");

      int length =
        byteMode.length + 1 +
        byteSequenceNumber.length + 1 +
        byteStopSequenceNumber.length + 1 +
        byteChangeNumber.length + 1 +
        bytePsearch.length + 1 +
        byteGeneralizedState.length + 1 +
        byteOperationId.length + 1 +
        byteExcludedDNs.length + 1 +
        1;

      byte[] resultByteArray = new byte[length];
      int pos = 0;
      resultByteArray[pos++] = MSG_TYPE_START_ECL_SESSION;
      pos = addByteArray(byteMode, resultByteArray, pos);
      pos = addByteArray(byteSequenceNumber, resultByteArray, pos);
      pos = addByteArray(byteStopSequenceNumber, resultByteArray, pos);
      pos = addByteArray(byteChangeNumber, resultByteArray, pos);
      pos = addByteArray(bytePsearch, resultByteArray, pos);
      pos = addByteArray(byteGeneralizedState, resultByteArray, pos);
      pos = addByteArray(byteOperationId, resultByteArray, pos);
      pos = addByteArray(byteExcludedDNs, resultByteArray, pos);
      return resultByteArray;

    } catch (IOException e)
    {
      // never happens
      return null;
    }
  }


  /**
   * {@inheritDoc}
   */
  @Override
  public String toString()
  {
    return (this.getClass().getCanonicalName() + " [" +
            " requestType="+ eclRequestType +
            " persistentSearch="       + isPersistent +
            " changeNumber="           + changeNumber +
            " firstDraftChangeNumber=" + firstDraftChangeNumber +
            " lastDraftChangeNumber="  + lastDraftChangeNumber +
            " generalizedState="       + crossDomainServerState +
            " operationId="            + operationId +
            " excludedDNs="            + excludedServiceIDs + "]");
  }

  /**
   * Getter on the changer number start.
   * @return the changer number start.
   */
  public int getFirstDraftChangeNumber()
  {
    return firstDraftChangeNumber;
  }

  /**
   * Getter on the changer number stop.
   * @return the change number stop.
   */
  public int getLastDraftChangeNumber()
  {
    return lastDraftChangeNumber;
  }

  /**
   * Setter on the first changer number (as defined by [CHANGELOG]).
   * @param firstDraftChangeNumber the provided first change number.
   */
  public void setFirstDraftChangeNumber(int firstDraftChangeNumber)
  {
    this.firstDraftChangeNumber = firstDraftChangeNumber;
  }

  /**
   * Setter on the last changer number (as defined by [CHANGELOG]).
   * @param lastDraftChangeNumber the provided last change number.
   */
  public void setLastDraftChangeNumber(int lastDraftChangeNumber)
  {
    this.lastDraftChangeNumber = lastDraftChangeNumber;
  }

  /**
   * Getter on the replication change number.
   * @return the replication change number.
   */
  public ChangeNumber getChangeNumber()
  {
    return changeNumber;
  }

  /**
   * Setter on the replication change number.
   * @param changeNumber the provided replication change number.
   */
  public void setChangeNumber(ChangeNumber changeNumber)
  {
    this.changeNumber = changeNumber;
  }
  /**
   * Getter on the type of request.
   * @return the type of request.
   */
  public short getECLRequestType()
  {
    return eclRequestType;
  }

  /**
   * Setter on the type of request.
   * @param eclRequestType the provided type of request.
   */
  public void setECLRequestType(short eclRequestType)
  {
    this.eclRequestType = eclRequestType;
  }

  /**
   * Getter on the persistent property of the search request on the ECL.
   * @return the persistent property.
   */
  public short isPersistent()
  {
    return this.isPersistent;
  }

  /**
   * Setter on the persistent property of the search request on the ECL.
   * @param isPersistent the provided persistent property.
   */
  public void setPersistent(short isPersistent)
  {
    this.isPersistent = isPersistent;
  }

  /**
   * Getter of the cross domain server state.
   * @return the cross domain server state.
   */
  public String getCrossDomainServerState()
  {
    return this.crossDomainServerState;
  }

  /**
   * Setter of the cross domain server state.
   * @param crossDomainServerState the provided cross domain server state.
   */
  public void setCrossDomainServerState(String crossDomainServerState)
  {
    this.crossDomainServerState = crossDomainServerState;
  }

  /**
   * Setter of the operation id.
   * @param operationId The provided opration id.
   */
  public void setOperationId(String operationId)
  {
    this.operationId = operationId;
  }

  /**
   * Getter on the operation id.
   * @return the operation id.
   */
  public String getOperationId()
  {
    return this.operationId;
  }

  /**
   * Getter on the list of excluded ServiceIDs.
   * @return the list of excluded ServiceIDs.
   */
  public ArrayList<String> getExcludedServiceIDs()
  {
    return this.excludedServiceIDs;
  }

  /**
   * Setter on the list of excluded ServiceIDs.
   * @param excludedServiceIDs the provided list of excluded ServiceIDs.
   */
  public void setExcludedDNs(ArrayList<String> excludedServiceIDs)
  {
    this.excludedServiceIDs = excludedServiceIDs;
  }

}
