/**
 * Copyright (c) 2010 Yahoo! Inc. All rights reserved.
 *
 * 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. See accompanying
 * LICENSE file.
 */

package com.yahoo.ycsb;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.Properties;


/**
 * One experiment scenario. One object of this type will
 * be instantiated and shared among all client threads. This class
 * should be constructed using a no-argument constructor, so we can
 * load it dynamically. Any argument-based initialization should be
 * done by init().
 * 
 * If you extend this class, you should support the "insertstart" property. This
 * allows the Client to proceed from multiple clients on different machines, in case
 * the client is the bottleneck. For example, if we want to load 1 million records from
 * 2 machines, the first machine should have insertstart=0 and the second insertstart=500000. Additionally,
 * the "insertcount" property, which is interpreted by Client, can be used to tell each instance of the
 * client how many inserts to do. In the example above, both clients should have insertcount=500000.
 */
public abstract class Workload {
  public static final String INSERT_START_PROPERTY = "insertstart";
  public static final String INSERT_COUNT_PROPERTY = "insertcount";
  
  public static final String INSERT_START_PROPERTY_DEFAULT = "0";
  
  private volatile AtomicBoolean stopRequested = new AtomicBoolean(false);
  
  /**
   * Initialize the scenario. Create any generators and other shared objects here.
   * Called once, in the main client thread, before any operations are started.
   */
  public void init(Properties p) throws WorkloadException {
  }

  /**
   * Initialize any state for a particular client thread. Since the scenario object
   * will be shared among all threads, this is the place to create any state that is specific
   * to one thread. To be clear, this means the returned object should be created anew on each
   * call to initThread(); do not return the same object multiple times.
   * The returned object will be passed to invocations of doInsert() and doTransaction()
   * for this thread. There should be no side effects from this call; all state should be encapsulated
   * in the returned object. If you have no state to retain for this thread, return null. (But if you have
   * no state to retain for this thread, probably you don't need to override initThread().)
   * 
   * @return false if the workload knows it is done for this thread. Client will terminate the thread.
   * Return true otherwise. Return true for workloads that rely on operationcount. For workloads that read
   * traces from a file, return true when there are more to do, false when you are done.
   */
  public Object initThread(Properties p, int mythreadid, int threadcount) throws WorkloadException {
    return mythreadid;
  }
      
  /**
   * Cleanup the scenario. Called once, in the main client thread, after all operations have completed.
   */
  public void cleanup() throws WorkloadException {
  }

  /**
   * Do one insert operation. Because it will be called concurrently from multiple client threads, this
   * function must be thread safe. However, avoid synchronized, or the threads will block waiting for each
   * other, and it will be difficult to reach the target throughput. Ideally, this function would have no side
   * effects other than DB operations and mutations on threadstate. Mutations to threadstate do not need to be
   * synchronized, since each thread has its own threadstate instance.
   */
  public abstract boolean doInsert(DB db, Object threadstate);

  /**
   * Do one transaction operation. Because it will be called concurrently from multiple client threads, this
   * function must be thread safe. However, avoid synchronized, or the threads will block waiting for each
   * other, and it will be difficult to reach the target throughput. Ideally, this function would have no side
   * effects other than DB operations and mutations on threadstate. Mutations to threadstate do not need to be
   * synchronized, since each thread has its own threadstate instance.
   * 
   * @return false if the workload knows it is done for this thread. Client will terminate the thread. 
   * Return true otherwise. Return true for workloads that rely on operationcount. For workloads that read
   * traces from a file, return true when there are more to do, false when you are done.
   */
  public abstract boolean doTransaction(DB db, Object threadstate);

  /**
   * Allows scheduling a request to stop the workload.
   */
  public void requestStop() {
    stopRequested.set(true);
  }

  /**
   * Check the status of the stop request flag.
   * @return true if stop was requested, false otherwise.
   */
  public boolean isStopRequested() {
    return stopRequested.get();
  }
}
