package water;

import jsr166y.CountedCompleter;

/** Map/Reduce style distributed computation. */
public abstract class MRTask<T extends MRTask> extends DRemoteTask<T> {

  transient protected int _lo, _hi; // Range of keys to work on
  transient private T _left, _rite; // In-progress execution tree

  // This method is another backpressure mechanism to make sure we do not
  // exhaust system's resources by running too many tasks at the same time.
  // Tasks are expected to reserve memory before proceeding with their
  // execution and making sure they release it when done.
  public long memOverheadPerChunk() { return 0; }

  static final long log2(long a) {
    long x = a, y = 0;
    while( (x >>= 1) > 0 ) ++y;
    return (a > (1L << y)) ? y+1 : y;
  }

  @Override public void init() {
    _lo = 0;
    _hi = _keys.length;
    long reqMem = (log2(_hi - _lo)+2)*memOverheadPerChunk();
    MemoryManager.reserveTaskMem(reqMem); // min. memory required to run at least single threaded
    _reservedMem = reqMem;
  }

  /** Run some useful function over this <strong>local</strong> key, and
   * record the results in the <em>this</em> MRTask. */
  abstract public void map( Key key );

  protected boolean _runSingleThreaded = false;
  transient long _reservedMem;
  /** Do all the keys in the list associated with this Node.  Roll up the
   * results into <em>this</em> MRTask. */
  @Override public final void lcompute() {
    if( _hi-_lo >= 2 ) { // Multi-key case: just divide-and-conquer to 1 key
      final int mid = (_lo+_hi)>>>1; // Mid-point
      assert _left == null && _rite == null;
      T l = clone();
      T r = clone();
      _left = l; l._reservedMem = 0;
      _rite = r; r._reservedMem = 0;
      _left._hi = mid;          // Reset mid-point
      _rite._lo = mid;          // Also set self mid-point
      setPendingCount(1);
      // Compute min. memory required to run the right branch in parallel.  Min
      // memory equals to the max memory used if the right branch will be
      // executed single threaded (but in parallel with our left branch).
      // Assuming all memory is kept in the tasks and it is halved by reduce
      // operation, the min memory is proportional to the depth of the right
      // subtree.
      long reqMem = (log2(_hi - mid)+3)*memOverheadPerChunk();
      if(!_runSingleThreaded && MemoryManager.tryReserveTaskMem(reqMem)){
        _reservedMem += reqMem;   // Remember the amount of reserved memory to free it later.
        _left.fork();             // Runs in another thread/FJ instance
        _rite.compute2();         // Runs in THIS F/J thread
      } else {
        _left.setCompleter(new H2O.H2OCallback(null,this) {
          @Override
          public void callback(H2O.H2OCountedCompleter caller) {
            _rite.compute2();         // Runs in THIS F/J thread
            tryComplete();
          }
        });
        _left.compute2();
      }
    } else {
      if( _hi > _lo ) {         // Single key?
        try {
          map(_keys[_lo]);      // Get it, run it locally
        } catch( RuntimeException re ) { // Catch user-map-thrown exceptions
          throw H2O.setDetailMessage(re,re.getMessage()+" while mapping key "+_keys[_lo]);
        } catch( AssertionError re ) { // Catch user-map-thrown exceptions
          throw H2O.setDetailMessage(re,re.getMessage()+" while mapping key "+_keys[_lo]);
        } catch( OutOfMemoryError re ) { // Catch user-map-thrown exceptions
          throw H2O.setDetailMessage(re,re.getMessage()+" while mapping key "+_keys[_lo]);
        }
      }
      tryComplete();            // And this task is complete
    }
  }

  private final void returnReservedMemory(){
    if(_reservedMem > 0)MemoryManager.freeTaskMem(_reservedMem);
  }

  @Override public void lonCompletion( CountedCompleter caller ) {
    // Reduce results into 'this' so they collapse going up the execution tree.
    // NULL out child-references so we don't accidentally keep large subtrees
    // alive: each one may be holding large partial results.
    if( _left != null ) reduceAlsoBlock(_left); _left = null;
    if( _rite != null ) reduceAlsoBlock(_rite); _rite = null;
    returnReservedMemory();
  }
  @Override public final boolean onExceptionalCompletion(Throwable ex, CountedCompleter caller ) {
    _left = null;
    _rite = null;
    returnReservedMemory();
    return super.onExceptionalCompletion(ex, caller);
  }

  // Caveat Emptor:
  // Hopefully used for debugging only... not only are these likely to change
  // in the near future, there's very few guarantees placed on these values.
  // At various points they are chunk-number ranges (before & during maps), and
  // stale values that *look* like ranges but are not (during reduces) or maybe
  // they will morph into row#'s (new not-yet-ready api) and/or forms of
  // "visited" flags (also new api).
  public final int lo() { return _lo; }
  public final int hi() { return _hi; }
}
@@ -41,16 +41,49 @@
 mpleter;
+%0Aimport water.fvec.ParseDataset2;
 %0A%0A/** Ma
@@ -2344,24 +2344,25 @@
 PerChunk();%0A
+%0A
       if(!_r
@@ -2824,36 +2824,43 @@
       _rite.
-compute2(); 
+fork();%0A          %7D
         // R
@@ -2885,46 +2885,8 @@
 read
-%0A            tryComplete();%0A          
 %7D%0A  
@@ -2909,24 +2909,20 @@
   _left.
-compute2
+fork
 ();%0A    

Fix in MRTask: when going single threaded, fork instead of calling compute directly to prevent stack overflow.