/**
 * @license Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */
package foam.dao.index;

import foam.core.FObject;
import foam.core.PropertyInfo;
import foam.dao.AbstractDAO;
import foam.dao.Sink;
import foam.mlang.order.Comparator;
import foam.mlang.predicate.*;
import foam.mlang.predicate.Binary;
import foam.mlang.sink.Count;
import foam.mlang.sink.GroupBy;
import java.util.Arrays;

public class TreeIndex
  extends AbstractIndex
{
  protected Index        tail_;
  protected PropertyInfo prop_;
  protected long         selectCount_;

  public TreeIndex(PropertyInfo prop) {
    this(prop, ValueIndex.instance());
  }

  public TreeIndex(PropertyInfo prop, Index tail) {
    prop_        = prop;
    selectCount_ = 0;
    tail_        = tail;
  }

  public Object bulkLoad(FObject[] a) {
    Arrays.sort(a);
    return TreeNode.getNullNode().bulkLoad(tail_, prop_, 0, a.length-1, a);
  }

  /**
   *This fuction help to smaller state by predicate efficiently
   * @param state: When we could deal with predicate efficiently by index, the return sata will smaller than origin state
   * @param predicate: If the state is kind of Binary state, when we deal with it it will become null. If it is kind of N-arry state, the part of their predicate will become True or null.
   * @return Return an Object[] which contains two elements, first one is update state and second one is update predicate.
   */
  protected Object[] simplifyPredicate(Object state, Predicate predicate) {
    if ( predicate == null || prop_ == null ) {
      return new Object[]{state, predicate};
    }

    if ( predicate instanceof Binary ) {
      Binary expr = (Binary) predicate;
      if ( predicate.getClass().equals(Eq.class) && expr.getArg1().toString().equals(prop_.toString()) ) {
        state = ( (TreeNode) state ).get((TreeNode) state, expr.getArg2().f(expr), prop_);
        return new Object[]{state, null};
      }

      // TODO: Handle NEQ
//      if ( predicate.getClass().equals(Neq.class) && expr.getArg1().toString().equals(prop_.toString()) ) {
//        state = ( (TreeNode) state ).neq((TreeNode) state, expr.getArg2().f(expr), prop_);
//        return new Object[]{state, null};
//      }

      if ( predicate.getClass().equals(Gt.class) && expr.getArg1().toString().equals(prop_.toString()) ) {
        state = ( (TreeNode) state ).gt((TreeNode) state, expr.getArg2().f(expr), prop_);
        return new Object[]{state, null};
      }

      if ( predicate.getClass().equals(Gte.class) && expr.getArg1().toString().equals(prop_.toString()) ) {
        state = ( (TreeNode) state ).gte((TreeNode) state, expr.getArg2().f(expr), prop_);
        return new Object[]{state, null};
      }

      if ( predicate.getClass().equals(Lt.class) && expr.getArg1().toString().equals(prop_.toString()) ) {
        state = ( (TreeNode) state ).lt((TreeNode) state, expr.getArg2().f(expr), prop_);
        return new Object[]{state, null};
      }

      if ( predicate.getClass().equals(Lte.class) && expr.getArg1().toString().equals(prop_.toString()) ) {
        state = ( (TreeNode) state ).lte((TreeNode) state, expr.getArg2().f(expr), prop_);
        return new Object[]{state, null};
      }
    } else if ( predicate instanceof And ) {
      int length = ((And) predicate).getArgs().length;
      for ( int i = 0; i < length; i++ ) {
        Predicate arg = ( (And) predicate ).getArgs()[i];
        if ( arg != null && state != null ) {
          // Each args deal with by 'simplifyPredicate()' function recursively.
          Object[] statePredicate = simplifyPredicate(state, arg);
          state = statePredicate[0];
          arg   = (Predicate) statePredicate[1];
        }
        if ( arg == null ) {
          ((And) predicate).getArgs()[i] = new True();
        }
      }
      // use partialEval to simplify predicate themselves.
      predicate = predicate.partialEval();
      if ( predicate instanceof True ) return new Object[]{state, null};
    }

    return new Object[]{state, predicate};
  }

  public Object put(Object state, FObject value) {
    if ( state == null ) state = TreeNode.getNullNode();
    Object key;
    try {
      key = prop_.f(value);
    } catch (ClassCastException e) {
      return state;
    }

    return ((TreeNode) state).putKeyValue((TreeNode)state,
      prop_, key, value, tail_);
  }

  public Object remove(Object state, FObject value) {
    try {
      return ((TreeNode) state).removeKeyValue((TreeNode) state, prop_, prop_.f(value), value, tail_);
    } catch (ClassCastException e) {
      return state;
    }
  }

  public Object removeAll() {
    return TreeNode.getNullNode();
  }

  //TODO
  @Override
  public FindPlan planFind(Object state, Object key) {
    return new TreeLookupFindPlan(prop_, (state != null ? ((TreeNode) state).size : 0) );
  }


  /**
   * This function tries to return an optimal plan based on its arguments.
   */
  @Override
  public SelectPlan planSelect(Object state, Sink sink, long skip, long limit, Comparator order, Predicate predicate) {
    if ( state == null || predicate instanceof False ) return NotFoundPlan.instance();

    Object[] statePredicate = simplifyPredicate(state, predicate);
    state     = statePredicate[0];
    predicate = (Predicate) statePredicate[1];

    if ( predicate == null ) {
      // See if it's possible to do Count or GroupBy select efficiently.
      if ( sink instanceof Count && state != null ) {
        return new CountPlan(((TreeNode) state).size);
      }

      // We return a groupByPlan only if no order, no limit, no skip, no predicate
      if ( sink instanceof GroupBy
          && ( (GroupBy) sink ).getArg1().toString().equals(prop_.toString())
          && order == null && skip == 0 && limit == AbstractDAO.MAX_SAFE_INTEGER )
      {
        return new GroupByPlan(state, sink, predicate, prop_, tail_);
      }
    }

    return new ScanPlan(state, sink, skip, limit, order, predicate, prop_, tail_);
  }

  public long size(Object state) {
    return ((TreeNode) state).size;
  }
}

@@ -3837,15 +3837,20 @@
 %0A   
+ %7D%0A%0A
    
+ 
 // 
-u
+U
 se p
@@ -3876,32 +3876,41 @@
 ify 
-predicate themselves.%0A  
+and nullify the predicate itself%0A
     
@@ -3946,26 +3946,24 @@
 Eval();%0A    
-  
 if ( predica
@@ -3987,47 +3987,25 @@
 e ) 
+p
 re
-turn new Object%5B%5D%7Bstate,
+dicate =
  null
-%7D;%0A    %7D
+;
 %0A%0A  

Simplify using partialEval() and nullify the predicate after simplifyPredicate() recursion