content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.camel.component.mail;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Date;
  import java.util.List;
  
  import jakarta.mail.Message;
  import jakarta.mail.MessagingException;
  import jakarta.mail.internet.InternetAddress;
  
  import org.eclipse.angus.mail.imap.SortTerm;
  
  /**
   * Utility class for sorting of mail messages
   */
  public final class MailSorter {
      /**
       * No instances
       */
      private MailSorter() {
      }
  
      /**
       * Sort the messages. This emulates sorting the messages on the server if the server doesn't have the sorting
       * capability. See RFC 5256 Does not support complex sorting like in the RFC (with Base Subject or other similar
       * stuff), just simple comparisons.
       *
       * @param messages Messages to sort. Are sorted in place
       * @param sortTerm Sort term
       */
      public static void sortMessages(Message[] messages, final SortTerm[] sortTerm) {
          final List<SortTermWithDescending> sortTermsWithDescending = getSortTermsWithDescending(sortTerm);
          sortMessages(messages, sortTermsWithDescending);
      }
  
      /**
       * Compute the potentially descending sort terms from the input list
       *
       * @param  sortTerm Input list
       * @return          Sort terms list including if the respective sort should be sorted in descending order
       */
      private static List<SortTermWithDescending> getSortTermsWithDescending(SortTerm[] sortTerm) {
          // List of reversable sort terms. If the boolean is true the respective sort term is descending
          final List<SortTermWithDescending> sortTermsWithDescending = new ArrayList<>(sortTerm.length);
          // Descending next item in input because the last item was a "descending"
          boolean descendingNext = false;
          for (SortTerm term : sortTerm) {
              if (term.equals(SortTerm.REVERSE)) {
                  if (descendingNext) {
                      throw new IllegalArgumentException("Double reverse in sort term is not allowed");
                  }
                  descendingNext = true;
              } else {
                  sortTermsWithDescending.add(new SortTermWithDescending(term, descendingNext));
                  descendingNext = false;
              }
          }
          return sortTermsWithDescending;
      }
  
      /**
       * Sort messages using the list of properties
       *
       * @param messages                Messages to sort. Are sorted in place
       * @param sortTermsWithDescending Sort terms list including if the respective sort should be sorted in descending
       *                                order
       */
      private static void sortMessages(Message[] messages, final List<SortTermWithDescending> sortTermsWithDescending) {
          Arrays.sort(messages, (Message m1, Message m2) -> {
              try {
                  for (SortTermWithDescending reversableTerm : sortTermsWithDescending) {
                      int comparison = compareMessageProperty(m1, m2, reversableTerm.getTerm());
                      // Descending
                      if (reversableTerm.isDescending()) {
                          comparison = -comparison;
                      }
                      // Abort on first non-equal
                      if (comparison != 0) {
                          return comparison;
                      }
                  }
                  // Equal
                  return 0;
              } catch (MessagingException e) {
                  throw new IllegalArgumentException(e);
              }
          });
      }
  
      /**
       * Compare the value of the property of the two messages.
       *
       * @param  msg1                            Message 1
       * @param  msg2                            Message 2
       * @param  property                        Property to compare
       * @return                                 msg1.property.compareTo(msg2.property)
       * @throws jakarta.mail.MessagingException If message data could not be read.
       */
      private static int compareMessageProperty(Message msg1, Message msg2, SortTerm property) throws MessagingException {
          if (property.equals(SortTerm.TO)) {
              InternetAddress addr1 = (InternetAddress) msg1.getRecipients(Message.RecipientType.TO)[0];
              InternetAddress addr2 = (InternetAddress) msg2.getRecipients(Message.RecipientType.TO)[0];
              return addr1.getAddress().compareTo(addr2.getAddress());
          } else if (property.equals(SortTerm.CC)) {
              InternetAddress addr1 = (InternetAddress) msg1.getRecipients(Message.RecipientType.CC)[0];
              InternetAddress addr2 = (InternetAddress) msg2.getRecipients(Message.RecipientType.CC)[0];
              return addr1.getAddress().compareTo(addr2.getAddress());
          } else if (property.equals(SortTerm.FROM)) {
              InternetAddress addr1 = (InternetAddress) msg1.getFrom()[0];
              InternetAddress addr2 = (InternetAddress) msg2.getFrom()[0];
              return addr1.getAddress().compareTo(addr2.getAddress());
          } else if (property.equals(SortTerm.ARRIVAL)) {
              Date arr1 = msg1.getReceivedDate();
              Date arr2 = msg2.getReceivedDate();
              return arr1.compareTo(arr2);
          } else if (property.equals(SortTerm.DATE)) {
              Date sent1 = msg1.getSentDate();
              Date sent2 = msg2.getSentDate();
              return sent1.compareTo(sent2);
          } else if (property.equals(SortTerm.SIZE)) {
              int size1 = msg1.getSize();
              int size2 = msg2.getSize();
              return Integer.compare(size1, size2);
          } else if (property.equals(SortTerm.SUBJECT)) {
              String sub1 = msg1.getSubject();
              String sub2 = msg2.getSubject();
              return sub1.compareTo(sub2);
          }
          throw new IllegalArgumentException(String.format("Unknown sort term: %s", property.toString()));
      }
  
      /**
       * A sort term with a bit indicating if sorting should be descending for this term
       */
      private static final class SortTermWithDescending {
          private SortTerm term;
          private boolean descending;
  
          private SortTermWithDescending(SortTerm term, boolean descending) {
              this.term = term;
              this.descending = descending;
          }
  
          /**
           * @return Actual search term
           */
          public SortTerm getTerm() {
              return term;
          }
  
          /**
           * @return true if sorting should be descending, false if it should be ascending
           */
          public boolean isDescending() {
              return descending;
          }
      }
  }
  