package com.avaje.ebeaninternal.server.autotune.service;

import com.avaje.ebean.bean.NodeUsageCollector;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.query.SplitName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.LinkedHashSet;
import java.util.Set;

/**
 * Collects usages statistics for a given node in the object graph.
 */
public class ProfileOriginNodeUsage {

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

  private final Object monitor = new Object();

  private final String path;

  private int profileCount;

  private int profileUsedCount;

  private boolean modified;

  private final Set<String> aggregateUsed = new LinkedHashSet<String>();

  public ProfileOriginNodeUsage(String path) {
    // handle null paths as using ConcurrentHashMap
    this.path = "".equals(path) ? null : path;
  }

  protected void buildTunedFetch(PathProperties pathProps, BeanDescriptor<?> rootDesc, boolean addVersionProperty) {

    synchronized (monitor) {

      BeanDescriptor<?> desc = rootDesc;
      if (path != null) {
        ElPropertyValue elGetValue = rootDesc.getElGetValue(path);
        if (elGetValue == null) {
          logger.warn("AutoTune: Can't find join for path[" + path + "] for " + rootDesc.getName());
          return;
        } else {
          BeanProperty beanProperty = elGetValue.getBeanProperty();
          if (beanProperty instanceof BeanPropertyAssoc<?>) {
            desc = ((BeanPropertyAssoc<?>) beanProperty).getTargetDescriptor();
          }
        }
      }

      for (String propName : aggregateUsed) {
        BeanProperty beanProp = desc.getBeanPropertyFromPath(propName);
        if (beanProp == null) {
          logger.warn("AutoTune: Can't find property[" + propName + "] for " + desc.getName());

        } else {
          if (beanProp instanceof BeanPropertyAssoc<?>) {
            BeanPropertyAssoc<?> assocProp = (BeanPropertyAssoc<?>) beanProp;
            String targetIdProp = assocProp.getTargetIdProperty();
            String manyPath = SplitName.add(path, assocProp.getName());
            pathProps.addToPath(manyPath, targetIdProp);
          } else {
            //noinspection StatementWithEmptyBody
            if (beanProp.isLob() && !beanProp.isFetchEager()) {
              // AutoTune will not include Lob's marked FetchLazy
              // (which is the default for Lob's so typical).
            } else {
              pathProps.addToPath(path, beanProp.getName());
            }
          }
        }
      }

      if ((modified || addVersionProperty) && desc != null) {
        BeanProperty versionProp = desc.getVersionProperty();
        if (versionProp != null) {
          pathProps.addToPath(path, versionProp.getName());
        }
      }
    }
  }

  /**
   * Collect usage from a node.
   */
  protected void collectUsageInfo(NodeUsageCollector profile) {

    synchronized (monitor) {

      Set<String> used = profile.getUsed();

      profileCount++;
      if (!used.isEmpty()) {
        profileUsedCount++;
        aggregateUsed.addAll(used);
      }
      if (profile.isModified()) {
        modified = true;
      }
    }
  }

  public String toString() {
    return "path[" + path + "] profileCount[" + profileCount + "] used[" + profileUsedCount + "] props" + aggregateUsed;
  }
}