package com.orientechnologies.orient.core.sql.executor;

import com.orientechnologies.common.concur.OTimeoutException;
import com.orientechnologies.orient.core.command.OCommandContext;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.sql.parser.OIdentifier;

import java.util.Map;
import java.util.Optional;

/**
 * Returns the number of records contained in a class (including subclasses)
 * Executes a count(*) on a class and returns a single record that contains that value (with a specific alias).
 *
 * @author Luigi Dell'Aquila (luigi.dellaquila - at - gmail.com)
 */
public class CountFromClassStep extends AbstractExecutionStep {
  private final OIdentifier target;
  private final String      alias;

  private long cost = 0;

  private boolean executed = false;

  /**
   *
   * @param targetClass An identifier containing the name of the class to count
   * @param alias the name of the property returned in the result-set
   * @param ctx the query context
   * @param profilingEnabled true to enable the profiling of the execution (for SQL PROFILE)
   */
  public CountFromClassStep(OIdentifier targetClass, String alias, OCommandContext ctx, boolean profilingEnabled) {
    super(ctx, profilingEnabled);
    this.target = targetClass;
    this.alias = alias;
  }

  @Override
  public OResultSet syncPull(OCommandContext ctx, int nRecords) throws OTimeoutException {
    getPrev().ifPresent(x -> x.syncPull(ctx, nRecords));
    return new OResultSet() {
      @Override
      public boolean hasNext() {
        return !executed;
      }

      @Override
      public OResult next() {
        if (executed) {
          throw new IllegalStateException();
        }
        long begin = profilingEnabled ? System.nanoTime() : 0;
        try {
          OClass clazz = ctx.getDatabase().getClass(target.getStringValue());
          long size = clazz.count();
          executed = true;
          OResultInternal result = new OResultInternal();
          result.setProperty(alias, size);
          return result;
        } finally {
          if(profilingEnabled){cost += (System.nanoTime() - begin);}
        }
      }

      @Override
      public void close() {

      }

      @Override
      public Optional<OExecutionPlan> getExecutionPlan() {
        return null;
      }

      @Override
      public Map<String, Long> getQueryStats() {
        return null;
      }

      @Override
      public void reset() {
        CountFromClassStep.this.reset();
      }
    };
  }

  @Override
  public void reset() {
    executed = false;
  }

  @Override
  public String prettyPrint(int depth, int indent) {
    String spaces = OExecutionStepInternal.getIndent(depth, indent);
    String result = spaces + "+ CALCULATE CLASS SIZE: " + target;
    if (profilingEnabled) {
      result += " (" + getCostFormatted() + ")";
    }
    return result;
  }

  @Override
  public long getCost() {
    return cost;
  }

}

@@ -178,16 +178,95 @@
 ontext;%0A
+import com.orientechnologies.orient.core.exception.OCommandExecutionException;%0A
 import c
@@ -322,16 +322,16 @@
 OClass;%0A
-
 import c
@@ -518,19 +518,16 @@
 classes)
-%0A *
  Execute
@@ -570,16 +570,19 @@
 a single
+%0A *
  record 
@@ -912,13 +912,8 @@
 /**%0A
-   *%0A
    *
@@ -932,16 +932,21 @@
 etClass 
+     
 An ident
@@ -1011,16 +1011,27 @@
 m alias 
+           
 the name
@@ -1090,16 +1090,29 @@
 ram ctx 
+             
 the quer
@@ -1980,24 +1980,196 @@
 ngValue());%0A
+          if (clazz == null) %7B%0A            throw new OCommandExecutionException(%22Class %22 + target.getStringValue() + %22 does not exist in the database schema%22);%0A          %7D%0A
           lo
@@ -2378,16 +2378,17 @@
       if
+ 
 (profili
@@ -2397,17 +2397,31 @@
 Enabled)
-%7B
+ %7B%0A            
 cost += 
@@ -2448,16 +2448,27 @@
  begin);
+%0A          
 %7D%0A      

Fix NullPointer on queries with wrong class