package com.avaje.ebeaninternal.api;

import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

/**
 * Request for loading Associated Many Beans.
 */
public class LoadManyRequest extends LoadRequest {

  private static final Logger logger = LoggerFactory.getLogger(LoadManyRequest.class);

  private final List<BeanCollection<?>> batch;

  private final LoadManyBuffer loadContext;

  private final boolean onlyIds;

  private final boolean loadCache;

  /**
   * Construct for lazy loading.
   */
  public LoadManyRequest(LoadManyBuffer loadContext, boolean onlyIds, boolean loadCache) {
    this(loadContext, null, true, onlyIds, loadCache);
  }

  /**
   * Construct for secondary query.
   */
  public LoadManyRequest(LoadManyBuffer loadContext, OrmQueryRequest<?> parentRequest) {
    this(loadContext, parentRequest, false, false, false);
  }

  private LoadManyRequest(LoadManyBuffer loadContext, OrmQueryRequest<?> parentRequest,  boolean lazy, boolean onlyIds, boolean loadCache) {
    super(parentRequest, lazy);
    this.loadContext = loadContext;
    this.batch = loadContext.getBatch();
    this.onlyIds = onlyIds;
    this.loadCache = loadCache;
  }

  @Override
  public Class<?> getBeanType() {
    return loadContext.getBeanDescriptor().getBeanType();
  }

  public String getDescription() {
    return "path:" + loadContext.getFullPath() + " size:" + batch.size();
  }

  /**
   * Return the batch of collections to actually load.
   */
  public List<BeanCollection<?>> getBatch() {
    return batch;
  }

  /**
   * Return the load context.
   */
  public LoadManyBuffer getLoadContext() {
    return loadContext;
  }

  /**
   * Return true if lazy loading should only load the id values.
   * <p>
   * This for use when lazy loading is invoked on methods such as clear() and removeAll() where it
   * generally makes sense to only fetch the Id values as the other property information is not
   * used.
   * </p>
   */
  public boolean isOnlyIds() {
    return onlyIds;
  }

  /**
   * Return true if we should load the Collection ids into the cache.
   */
  public boolean isLoadCache() {
    return loadCache;
  }

  /**
   * Return the batch size used for this load context.
   */
  public int getBatchSize() {
    return loadContext.getBatchSize();
  }

  private List<Object> getParentIdList(int batchSize) {

    ArrayList<Object> idList = new ArrayList<Object>(batchSize);

    BeanPropertyAssocMany<?> many = getMany();
    for (int i = 0; i < batch.size(); i++) {
      BeanCollection<?> bc = batch.get(i);
      idList.add(many.getParentId(bc.getOwnerBean()));
    }
    int extraIds = batchSize - batch.size();
    if (extraIds > 0) {
      Object firstId = idList.get(0);
      for (int i = 0; i < extraIds; i++) {
        idList.add(firstId);
      }
    }

    return idList;
  }

  private BeanPropertyAssocMany<?> getMany() {
    return loadContext.getBeanProperty();
  }

  public SpiQuery<?> createQuery(EbeanServer server, int batchSize) {

    BeanPropertyAssocMany<?> many = getMany();

    SpiQuery<?> query = (SpiQuery<?>) server.createQuery(many.getTargetType());
    String orderBy = many.getLazyFetchOrderBy();
    if (orderBy != null) {
      query.orderBy(orderBy);
    }

    String extraWhere = many.getExtraWhere();
    if (extraWhere != null) {
      // replace special ${ta} placeholder with the base table alias
      // which is always t0 and add the extra where clause
      String ew = StringHelper.replaceString(extraWhere, "${ta}", "t0");
      query.where().raw(ew);
    }

    query.setLazyLoadForParents(many);

    List<Object> idList = getParentIdList(batchSize);
    many.addWhereParentIdIn(query, idList, loadContext.isUseDocStore());

    query.setPersistenceContext(loadContext.getPersistenceContext());

    String mode = isLazy() ? "+lazy" : "+query";
    query.setLoadDescription(mode, getDescription());

    if (isLazy()) {
      // cascade the batch size (if set) for further lazy loading
      query.setLazyLoadBatchSize(getBatchSize());
    }

    // potentially changes the joins and selected properties
    loadContext.configureQuery(query);

    if (isOnlyIds()) {
      // override to just select the Id values
      query.select(many.getTargetIdProperty());
    }

    return query;
  }

  /**
   * After the query execution check for empty collections and load L2 cache if desired.
   */
  public void postLoad() {

    BeanDescriptor<?> desc = loadContext.getBeanDescriptor();
    BeanPropertyAssocMany<?> many = getMany();

    // check for BeanCollection's that where never processed
    // in the +query or +lazy load due to no rows (predicates)
    for (int i = 0; i < batch.size(); i++) {
      BeanCollection<?> bc = batch.get(i);
      if (bc.checkEmptyLazyLoad()) {
        if (logger.isDebugEnabled()) {
          EntityBean ownerBean = bc.getOwnerBean();
          Object parentId = desc.getId(ownerBean);
          logger.debug("BeanCollection after lazy load was empty. type:" + ownerBean.getClass().getName() + " id:" + parentId + " owner:" + ownerBean);
        }
      } else if (isLoadCache()) {
        Object parentId = desc.getId(bc.getOwnerBean());
        desc.cacheManyPropPut(many, bc, parentId);
      }
    }

  }
}
