/*
 * 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 Sun Microsystems, Inc.
 *      Portions Copyright 2013-2015 ForgeRock AS.
 */

package org.opends.guitools.controlpanel.datamodel;

import static org.opends.messages.AdminToolMessages.*;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

import org.opends.guitools.controlpanel.util.Utilities;
import org.forgerock.i18n.LocalizableMessage;

/**
 * The table model used to display all the base DNs.
 *
 */
public class BaseDNTableModel extends SortableTableModel
implements Comparator<BaseDNDescriptor>
{
  private static final long serialVersionUID = -5650762484071136983L;
  private HashSet<BaseDNDescriptor> data = new HashSet<>();
  private ServerDescriptor.ServerStatus serverStatus;
  private boolean isAuthenticated;

  private ArrayList<String[]> dataArray = new ArrayList<>();
  private String[] COLUMN_NAMES;
  private int sortColumn;
  private boolean sortAscending = true;
  private boolean displayReplicationInformation;

  /**
   * Key value to identify the case of a value not available because the server
   * is down.
   */
  public static String NOT_AVAILABLE_SERVER_DOWN = "NOT_AVAILABLE_SERVER_DOWN";

  /**
   * Key value to identify the case of a value not available because
   * authentication is required.
   */
  public static String NOT_AVAILABLE_AUTHENTICATION_REQUIRED =
    "NOT_AVAILABLE_AUTHENTICATION_REQUIRED";

  /**
   * Key value to identify the case of a value not available.
   */
  public static String NOT_AVAILABLE = "NOT_AVAILABLE";

  /**
   * Constructor for this table model.
   * @param displayReplicationInformation whether to display replication.
   * monitoring information or not.
   */
  public BaseDNTableModel(boolean displayReplicationInformation)
  {
    this(displayReplicationInformation, true);
  }

  /**
   * Constructor for this table model.
   * @param displayReplicationInformation whether to display replication.
   * @param wrapHeader whether to wrap the headers or not.
   * monitoring information or not.
   */
  public BaseDNTableModel(boolean displayReplicationInformation,
      boolean wrapHeader)
  {
    this.displayReplicationInformation = displayReplicationInformation;
    if (wrapHeader)
    {
     COLUMN_NAMES = new String[] {
          getHeader(INFO_BASEDN_COLUMN.get()),
          getHeader(INFO_BACKENDID_COLUMN.get()),
          getHeader(INFO_NUMBER_ENTRIES_COLUMN.get()),
          getHeader(INFO_REPLICATED_COLUMN.get()),
          getHeader(INFO_MISSING_CHANGES_COLUMN.get()),
          getHeader(INFO_AGE_OF_OLDEST_MISSING_CHANGE_COLUMN.get())
      };
    }
    else
    {
      COLUMN_NAMES = new String[] {
          INFO_BASEDN_COLUMN.get().toString(),
          INFO_BACKENDID_COLUMN.get().toString(),
          INFO_NUMBER_ENTRIES_COLUMN.get().toString(),
          INFO_REPLICATED_COLUMN.get().toString(),
          INFO_MISSING_CHANGES_COLUMN.get().toString(),
          INFO_AGE_OF_OLDEST_MISSING_CHANGE_COLUMN.get().toString()
      };
    }
  }

  /**
   * Sets the data for this table model.
   * @param newData the data for this table model.
   * @param status the server status.
   * @param isAuthenticated whether the user provided authentication or not.
   */
  public void setData(Set<BaseDNDescriptor> newData,
      ServerDescriptor.ServerStatus status, boolean isAuthenticated)
  {
    if (!newData.equals(data) || serverStatus != status || this.isAuthenticated != isAuthenticated)
    {
      serverStatus = status;
      this.isAuthenticated = isAuthenticated;
      data.clear();
      data.addAll(newData);
      updateDataArray();
      fireTableDataChanged();
    }
  }

  /**
   * Updates the table model contents and sorts its contents depending on the
   * sort options set by the user.
   */
  public void forceResort()
  {
    updateDataArray();
    fireTableDataChanged();
  }

  /**
   * Comparable implementation.
   * @param desc1 the first replica descriptor to compare.
   * @param desc2 the second replica descriptor to compare.
   * @return 1 if according to the sorting options set by the user the first
   * base DN descriptor must be put before the second descriptor, 0 if they
   * are equivalent in terms of sorting and -1 if the second descriptor must
   * be put before the first descriptor.
   */
  public int compare(BaseDNDescriptor desc1, BaseDNDescriptor desc2)
  {
    int result = 0;
    if (sortColumn == 0)
    {
      result = compareDns(desc1, desc2);

      if (result == 0)
      {
        result = compareBackendIDs(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareEntries(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareRepl(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareMissingChanges(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareAgeOfOldestMissingChange(desc1, desc2);
      }
    }

    if (sortColumn == 1)
    {
      result = compareBackendIDs(desc1, desc2);

      if (result == 0)
      {
        result = compareDns(desc1, desc2);

      }

      if (result == 0)
      {
        result = compareEntries(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareRepl(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareMissingChanges(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareAgeOfOldestMissingChange(desc1, desc2);
      }
    }
    else if (sortColumn == 2)
    {
      result = compareEntries(desc1, desc2);

      if (result == 0)
      {
        result = compareBackendIDs(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareDns(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareRepl(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareMissingChanges(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareAgeOfOldestMissingChange(desc1, desc2);
      }
    }
    else if (sortColumn == 3)
    {
      result = compareRepl(desc1, desc2);

      if (result == 0)
      {
        result = compareBackendIDs(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareDns(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareEntries(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareMissingChanges(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareAgeOfOldestMissingChange(desc1, desc2);
      }
    }
    else if (sortColumn == 4)
    {
      result = compareMissingChanges(desc1, desc2);

      if (result == 0)
      {
        result = compareBackendIDs(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareDns(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareEntries(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareRepl(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareAgeOfOldestMissingChange(desc1, desc2);
      }
    }
    else if (sortColumn == 5)
    {
      result = compareAgeOfOldestMissingChange(desc1, desc2);

      if (result == 0)
      {
        result = compareBackendIDs(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareDns(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareEntries(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareRepl(desc1, desc2);
      }

      if (result == 0)
      {
        result = compareMissingChanges(desc1, desc2);
      }
    }

    if (!sortAscending)
    {
      result = -result;
    }

    return result;
  }

  /** {@inheritDoc} */
  public int getColumnCount()
  {
    return displayReplicationInformation ? 6 : 4;
  }

  /** {@inheritDoc} */
  public int getRowCount()
  {
    return dataArray.size();
  }

  /** {@inheritDoc} */
  public Object getValueAt(int row, int col)
  {
    return dataArray.get(row)[col];
  }

  /** Updates the array data.  This includes resorting it. */
  private void updateDataArray()
  {
    TreeSet<BaseDNDescriptor> sortedSet = new TreeSet<>(this);
    sortedSet.addAll(data);
    dataArray.clear();
    for (BaseDNDescriptor desc : sortedSet)
    {
      dataArray.add(new String[] {
        Utilities.unescapeUtf8(desc.getDn().toString()),
        desc.getBackend().getBackendID(),
        getValueForEntries(desc),
        getStringForReplState(desc),
        getValueForMissingChanges(desc),
        getValueForOldestMissingChange(desc)
      });
    }
  }

  /** {@inheritDoc} */
  public String getColumnName(int col) {
    return COLUMN_NAMES[col];
  }

  /**
   * Returns whether the sort is ascending or descending.
   * @return <CODE>true</CODE> if the sort is ascending and <CODE>false</CODE>
   * otherwise.
   */
  public boolean isSortAscending()
  {
    return sortAscending;
  }

  /**
   * Sets whether to sort ascending of descending.
   * @param sortAscending whether to sort ascending or descending.
   */
  public void setSortAscending(boolean sortAscending)
  {
    this.sortAscending = sortAscending;
  }

  /**
   * Returns the column index used to sort.
   * @return the column index used to sort.
   */
  public int getSortColumn()
  {
    return sortColumn;
  }

  /**
   * Sets the column index used to sort.
   * @param sortColumn column index used to sort..
   */
  public void setSortColumn(int sortColumn)
  {
    this.sortColumn = sortColumn;
  }

  /** Several comparison methods to be able to sort the table model. */
  private int compareBackendIDs(BaseDNDescriptor desc1, BaseDNDescriptor desc2)
  {
    return desc1.getBackend().getBackendID().compareTo(
      desc2.getBackend().getBackendID());
  }

  private int compareEntries(BaseDNDescriptor desc1, BaseDNDescriptor desc2)
  {
    int n1 = desc1.getEntries();
    int n2 = desc2.getEntries();
    return compareIntegers(n1, n2);
  }

  private int compareIntegers(int n1, int n2)
  {
    if (n1 == n2)
    {
      return 0;
    }
    if (n1 > n2)
    {
      return 1;
    }
    return -1;
  }

  private int compareLongs(long n1, long n2)
  {
    if (n1 == n2)
    {
      return 0;
    }
    if (n1 > n2)
    {
      return 1;
    }
    return -1;
  }

  private int compareDns(BaseDNDescriptor desc1, BaseDNDescriptor desc2)
  {
    return Utilities.unescapeUtf8(desc1.getDn().toString()).compareTo(
        Utilities.unescapeUtf8(desc2.getDn().toString()));
  }

  private int compareRepl(BaseDNDescriptor desc1, BaseDNDescriptor desc2)
  {
    String val1 = String.valueOf(desc1.getType());
    String val2 = String.valueOf(desc2.getType());
    return val1.compareTo(val2);
  }

  private int compareMissingChanges(BaseDNDescriptor desc1,
      BaseDNDescriptor desc2)
  {
    return compareIntegers(desc1.getMissingChanges(),
        desc2.getMissingChanges());
  }

  private int compareAgeOfOldestMissingChange(BaseDNDescriptor desc1,
      BaseDNDescriptor desc2)
  {
    return compareLongs(desc1.getAgeOfOldestMissingChange(),
        desc2.getAgeOfOldestMissingChange());
  }

  /**
   * Returns the Object describing the number of entries of a given Base DN.
   * The Object will be an Integer.
   * @param rep the Base DN object to handle.
   * @return the Object describing the number of entries of a given Base DN.
   */
  private String getValueForEntries(BaseDNDescriptor rep)
  {
    String returnValue;
    if (serverStatus != ServerDescriptor.ServerStatus.STARTED)
    {
      returnValue = NOT_AVAILABLE_SERVER_DOWN;
    }
    else if (!isAuthenticated)
    {
      returnValue = NOT_AVAILABLE_AUTHENTICATION_REQUIRED;
    }
    else
    {
      if (rep.getEntries() < 0)
      {
        returnValue = NOT_AVAILABLE;
      }
      else
      {
        returnValue = String.valueOf(rep.getEntries());
      }
    }
    return returnValue;
  }

  /**
   * Returns the Object describing the number of missing changes of a given Base
   * DN.  The Object will be a String unless the base DN is
   * replicated and we could not find a valid value (in this case we return
   * an Integer with the invalid value).
   * @param rep the Base DN object to handle.
   * @return the Object describing the number of missing changes of
   * a given Base DN.
   */
  private String getValueForMissingChanges(BaseDNDescriptor rep)
  {
    String returnValue;
    if (rep.getType() == BaseDNDescriptor.Type.REPLICATED)
    {
      if (serverStatus != ServerDescriptor.ServerStatus.STARTED)
      {
        returnValue = NOT_AVAILABLE_SERVER_DOWN;
      }
      else if (!isAuthenticated)
      {
        returnValue = NOT_AVAILABLE_AUTHENTICATION_REQUIRED;
      }
      else
      {
        if (rep.getMissingChanges() < 0)
        {
          returnValue = NOT_AVAILABLE;
        }
        else
        {
          returnValue = String.valueOf(rep.getMissingChanges());
        }
      }
    }
    else
    {
      returnValue = INFO_NOT_APPLICABLE_LABEL.get().toString();
    }
    return returnValue;
  }

  /**
   * Returns the Object describing the age of oldest missing change of
   * a given Base DN.  The Object will be a String unless the base DN is
   * replicated and we could not find a valid value (in this case we return
   * an Integer with the invalid value).
   * @param rep the Base DN object to handle.
   * @return the Object describing the age of oldest missing change of
   * a given Base DN.
   */
  private String getValueForOldestMissingChange(BaseDNDescriptor rep)
  {
    String returnValue;
    if (rep.getType() == BaseDNDescriptor.Type.REPLICATED)
    {
      if (serverStatus != ServerDescriptor.ServerStatus.STARTED)
      {
        returnValue = NOT_AVAILABLE_SERVER_DOWN;
      }
      else if (!isAuthenticated)
      {
        returnValue = NOT_AVAILABLE_AUTHENTICATION_REQUIRED;
      }
      else
      {
        long age = rep.getAgeOfOldestMissingChange();
        if (age > 0)
        {
          Date date = new Date(age);
          returnValue = date.toString();
        }
        else
        {
          // Not available
          returnValue = NOT_AVAILABLE;
        }
      }
    }
    else
    {
      returnValue = INFO_NOT_APPLICABLE_LABEL.get().toString();
    }
    return returnValue;
  }

  /**
   * Returns the localized String describing the replication state of
   * a given Base DN.
   * @param rep the Base DN object to handle.
   * @return the localized String describing the replication state of
   * a given Base DN.
   */
  private String getStringForReplState(BaseDNDescriptor rep)
  {
    LocalizableMessage s;
    if (rep.getType() == BaseDNDescriptor.Type.REPLICATED)
    {
      s = INFO_BASEDN_REPLICATED_LABEL.get();
    }
    else if (rep.getType() == BaseDNDescriptor.Type.NOT_REPLICATED)
    {
      s = INFO_BASEDN_NOT_REPLICATED_LABEL.get();
    }
    else
    {
      s = INFO_BASEDN_DISABLED_LABEL.get();
    }
    return s.toString();
  }
}
