/*
 * 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.ambari.server.api.query;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Order;
import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.metamodel.SingularAttribute;

import org.apache.ambari.server.controller.spi.SortRequest;
import org.apache.ambari.server.controller.spi.SortRequestProperty;
import org.apache.commons.lang.ObjectUtils;

/**
 * The {@link JpaSortBuilder} class is used to convert and Ambari
 * {@link SortRequest} and list of {@link SortRequestProperty} into a JPA
 * {@link Order} list. This can then be given to a {@link CriteriaBuilder} to
 * provide sorting in the JPA layer during a query.
 * <p/>
 * This classes uses the {@link JpaPredicateVisitor} for the entity being
 * queried in order to convert the entity fields into {@link Path} expressions.
 */
public class JpaSortBuilder<T> {

  /**
   * Constructor.
   *
   */
  public JpaSortBuilder() {
  }

  /**
   * Builds the list of sort orders based on the supplied request and JPA
   * predicate visitor.
   *
   * @param sortRequest
   *          the Ambari sort request properties to turn into a JPA sort
   *          request. If {@code null} or the {@link SortRequestProperty} list
   *          is null, an empty list is returned.
   * @param visitor
   *          a visitor that knows how to convert the Ambari properties into
   *          {@link SingularAttribute} (not {@code null}).
   * @return a list of sorts or an empty list if none (never {@code null}).
   */
  public List<Order> buildSortOrders(SortRequest sortRequest,
      JpaPredicateVisitor<T> visitor) {

    if (null == sortRequest || null == sortRequest.getProperties()) {
      return Collections.emptyList();
    }

    CriteriaBuilder builder = visitor.getCriteriaBuilder();
    List<SortRequestProperty> sortProperties = sortRequest.getProperties();
    List<Order> sortOrders = new ArrayList<>(sortProperties.size());

    for (SortRequestProperty sort : sortProperties) {
      String propertyId = sort.getPropertyId();

      List<? extends SingularAttribute<?, ?>> singularAttributes = visitor.getPredicateMapping(propertyId);

      if (null == singularAttributes || singularAttributes.size() == 0) {
        continue;
      }

      Path<?> path = null;
      for (SingularAttribute<?, ?> singularAttribute : singularAttributes) {
        if (null == path) {

          CriteriaQuery<T> query = visitor.getCriteriaQuery();
          Set<Root<?>> roots = query.getRoots();

          // if there are existing roots; use the existing roots to prevent more
          // roots from being added potentially causing a cartesian product
          // where we don't want one
          if (null != roots && !roots.isEmpty()) {
            Iterator<Root<?>> iterator = roots.iterator();
            while (iterator.hasNext()) {
              Root<?> root = iterator.next();

              Class<?> visitorEntityClass = visitor.getEntityClass();
              if (ObjectUtils.equals(visitorEntityClass, root.getJavaType())
                  || ObjectUtils.equals(visitorEntityClass, root.getModel().getJavaType())) {
                path = root.get(singularAttribute.getName());
                break;
              }
            }
          }

          // no roots exist already which match this entity class, create a new
          // path
          if (null == path) {
            path = query.from(visitor.getEntityClass()).get(singularAttribute.getName());
          }
        } else {
          path = path.get(singularAttribute.getName());
        }
      }

      Order sortOrder = null;
      if (sort.getOrder() == org.apache.ambari.server.controller.spi.SortRequest.Order.ASC) {
        sortOrder = builder.asc(path);
      } else {
        sortOrder = builder.desc(path);
      }

      sortOrders.add(sortOrder);
    }

    return sortOrders;
  }
}
