package nxm.sys.lib;

import java.util.Comparator;

import nxm.sys.inc.DataTypes;

/** This will sort the record oriented objects such as Tables and Data.

    @author  J. Czechowski IV
    @since   NeXtMidas 2.1.0
 */
public class RecordComparator implements Comparator<Object> {
  private String[] keys;             // The keys to compare based on.
  private boolean  reverse = false;  // For Data object only: Reverse?
  private String   format  = null;   // For Data object only: data format.
  private int      offset  = 0;      // For Data object only: data byte offset.

  /** Creates a new instance that compares a table based on a number of keys. Keys are listed in
      order of weight and may have an optional '+' prefix for ascending sort or '-' for descending
      sort (if neither '+' or '-' is specified than ascending sort is assumed).
      @param keys The keys to sort by.
   */
  public RecordComparator (String[] keys) {
    this.keys = keys;
  }

  /** Creates a new instance. WARNING: This constructor compares only ONE key from a Data object as
      specified by format and offset. Use the other constructor for a multiple-key comparison.
      @param in_keys   The keys to sort by.
      @param in_offset The offset into the data.
      @param in_format The type of data (B,I,L,F,D,...).
   */
  public RecordComparator (String[] in_keys, int in_offset, String in_format) {
    this.keys   = in_keys;
    this.offset = in_offset;
    this.format = in_format;
    if (keys[0].startsWith("-")) {
      this.reverse = true;
    } else {
      this.reverse = false;
    }
  }

  public String toString () {
    StringBuilder sb = new StringBuilder("Keys ");
    sb.append(StringUtil.join(keys, " "));
    sb.append(" format "+format);
    sb.append(" offset "+offset);
    sb.append(" reverse "+reverse);
    return sb.toString();
  }

  public int compare (Object first, Object second) {
    if (first instanceof Table) {
      return compare((Table)first, (Table)second, 0);
    } else {
      return compare( (Data)first, (Data)second, offset, format, reverse);
    }
  }

  private int compare (Data first, Data second, int offset, String format, boolean reverse) {
    int  test = 0; // The comparison results
    char formatType = format.charAt(1);
    double diff     = 0; // The difference between the two

    if (Data.isString((byte)formatType)) {
        int bpa = Data.getBPA(format);
        String s1 = first.unpackS(offset, bpa);
        String s2 = second.unpackS(offset,bpa);
        test = s1.compareTo(s2);
    } else {
      switch(formatType) {
        case DataTypes.DOUBLE: diff = first.unpackD(offset) - second.unpackD(offset); break;
        case DataTypes.FLOAT:  diff = first.unpackF(offset) - second.unpackF(offset); break;
        case DataTypes.XLONG:  diff = first.unpackX(offset) - second.unpackX(offset); break;
        case DataTypes.LONG:   diff = first.unpackL(offset) - second.unpackL(offset); break;
        case DataTypes.INT:    diff = first.unpackI(offset) - second.unpackI(offset); break;
        case DataTypes.BYTE:   diff = first.unpackB(offset) - second.unpackB(offset); break;
        default:  Shell.warning("unknown format type '" + formatType + "'");
      }
      test = (int)Math.signum(diff);
    }

    return (reverse)? -test : test;
  }

  private int compare (Table first, Table second, int keyIndex) {
    if (keyIndex >= keys.length) {
      return 0; // All comparisons done, all indicate equality.
    } else {
      String key = keys[keyIndex];
      boolean reverse = false;

           if (key.startsWith("-")) { key = key.substring(1); reverse = true;  }
      else if (key.startsWith("+")) { key = key.substring(1); reverse = false; }

      Object firstObj  = first.get(key);
      Object secondObj = second.get(key);

      if ((firstObj == null) || (secondObj == null)) {
        throw new RuntimeException("sort: Attempting to compare field (" + key + ") but found "
                                 + "null entry in (" + first + ") and (" + second + ").");
      }
      int test = Util.compare(firstObj, secondObj);

      if (test == 0) {
        return compare(first, second, (keyIndex + 1));
      } else {
        return (reverse)? -test: test;
      }
    }
  }
}
