/*
 * Copyright 2012 LinkedIn, Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.linkedin.parseq.example.composite;

import static com.linkedin.parseq.example.common.ExampleUtil.callService;
import static com.linkedin.parseq.example.common.ExampleUtil.printTracingResults;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import com.linkedin.parseq.BaseTask;
import com.linkedin.parseq.Context;
import com.linkedin.parseq.Engine;
import com.linkedin.parseq.Task;
import com.linkedin.parseq.example.common.AbstractExample;
import com.linkedin.parseq.example.common.MockService;
import com.linkedin.parseq.example.common.SimpleMockRequest;
import com.linkedin.parseq.function.Action;
import com.linkedin.parseq.promise.Promise;
import com.linkedin.parseq.promise.Promises;
import com.linkedin.parseq.promise.SettablePromise;


/**
 * @author Chris Pettitt (cpettitt@linkedin.com)
 * @author Jaroslaw Odzga (jodzga@linkedin.com)
 */
public class TimeBoundSearchExample extends AbstractExample {
  // How long it takes to get a response for each request
  private static final long[] REQUEST_LATENCIES = new long[] { 175, 67, 30, 20, 177, 350 };

  // How long the engine will wait for index number of responses
  private static final long[] WAIT_TIMES = new long[] { 400, 300, 200, 100, 0 };

  public static void main(String[] args) throws Exception {
    new TimeBoundSearchExample().runExample();
  }

  @Override
  protected void doRunExample(final Engine engine) throws Exception {
    final MockService<Integer> service = getService();

    final SearchTask example = new SearchTask(service);

    System.out.printf("This com.linkedin.asm.example will issue %d parallel requests\n", REQUEST_LATENCIES.length);
    System.out.println();
    for (int i = 0; i < REQUEST_LATENCIES.length; i++) {
      System.out.printf("Request %d will take %3dms to complete\n", i, REQUEST_LATENCIES[i]);
    }

    System.out.println();
    System.out.println("Latency rules:");
    System.out.println("--------------");
    for (int i = 0; i < WAIT_TIMES.length; i++) {
      System.out.printf("Finish if received %d responses after %3dms\n", i, WAIT_TIMES[i]);
    }

    System.out.println();
    System.out.println("Execution:");
    System.out.println("----------");
    final long startMillis = System.currentTimeMillis();
    engine.run(example);
    example.await();
    final long endMillis = System.currentTimeMillis();

    System.out.println("Responses: " + example.get());
    System.out.println("Execution time: " + (endMillis - startMillis) + "ms");

    printTracingResults(example);
  }

  private static class SearchTask extends BaseTask<List<Integer>> {
    private final MockService<Integer> _service;
    private final List<Integer> _responses = new ArrayList<Integer>();
    private final SettablePromise<List<Integer>> _result = Promises.settable();

    private long _startMillis;

    public SearchTask(final MockService<Integer> service) {
      super("search");
      _service = service;
    }

    @Override
    public Promise<List<Integer>> run(final Context ctx) {
      // Save the start time so we can determine when to finish
      _startMillis = System.currentTimeMillis();

      // Set up timeouts for responses
      long lastWaitTime = Integer.MAX_VALUE;
      for (final long waitTime : WAIT_TIMES) {
        if (waitTime < lastWaitTime && waitTime > 0) {
          ctx.createTimer(waitTime, TimeUnit.MILLISECONDS, checkDone());
          lastWaitTime = waitTime;
        }
      }

      // Issue requests
      for (int i = 0; i < REQUEST_LATENCIES.length; i++) {
        final long requestLatency = REQUEST_LATENCIES[i];
        final Task<Integer> callSvc =
            callService("subSearch[" + i + "]", _service, new SimpleMockRequest<Integer>(requestLatency, i), i);

        ctx.run(callSvc.andThen(addResponse(callSvc)).andThen(checkDone()));
      }

      return _result;
    }

    private Task<?> checkDone() {
      return Task.action("checkDone", new Action() {
        @Override
        public void run() {
          final int index = Math.min(WAIT_TIMES.length - 1, _responses.size());
          if (WAIT_TIMES[index] + _startMillis <= System.currentTimeMillis()) {
            _result.done(_responses);
          }
        }
      });
    }

    private Task<?> addResponse(final Promise<Integer> response) {
      return Task.action("addResponse", new Action() {
        @Override
        public void run() {
          _responses.add(response.get());
        }
      });
    }
  }
}
