Context:
//Copyright 2012 RobustNet Lab, University of Michigan. All Rights Reserved.

package com.mobiperf_library.measurements;

import java.io.IOException;
import java.io.InvalidClassException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
import java.util.Random;

import com.mobiperf_library.MeasurementDesc;
import com.mobiperf_library.MeasurementResult;
import com.mobiperf_library.MeasurementTask;
import com.mobiperf_library.MeasurementResult.TaskProgress;
import com.mobiperf_library.exceptions.MeasurementError;
import com.mobiperf_library.util.Logger;
import com.mobiperf_library.util.MLabNS;
import com.mobiperf_library.util.MeasurementJsonConvertor;
import com.mobiperf_library.util.PhoneUtils;

import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;

/**
 * @author Haokun Luo
 *
 * TCP Throughput is a measurement task for cellular network throughput.
 * 1. Uplink: the mobile device continuously sends packets with consistent
 *    packet size. We send packets for a fixed amount of time, and we sample
 *    each throughput value at a smaller period. We use the median of all the
 *    sampling result as the final measurement result. The result is calculated
 *    at the server side, and send back to the device.
 * 2. Downlink: similar methodology as uplink. Only difference is that the
 *    device is receiving packets from the server, and calculate the result
 *    locally.
 */
public class TCPThroughputTask extends MeasurementTask {
  // default constant here
  public static final String DESCRIPTOR = "TCP Speed Test";
  public static final int PORT_DOWNLINK = 6001;
  public static final int PORT_UPLINK = 6002;
  public static final int PORT_CONFIG = 6003;
  public static final String TYPE = "tcpthroughput";

  // Timing related
  public final int BUFFER_SIZE = 5000;
  public static final long DURATION_IN_SEC = 15;
  public final int KSEC = 1000;
  public static final long SAMPLE_PERIOD_IN_SEC = 1;
  public static final long SLOW_START_PERIOD_IN_SEC = 5;
  public static final int TCP_TIMEOUT_IN_SEC = 30;
  // largest non-fragment packet size in LTE (uplink)
  public static final int THROUGHPUT_UP_PKT_SIZE_MAX = 1357;
  public static final int THROUGHPUT_UP_PKT_SIZE_MIN = 700;

  // Data related
  private final int KBYTE = 1024;
  private static final int DATA_LIMIT_MB_UP = 5;
  private static final int DATA_LIMIT_MB_DOWN = 10;
  private boolean DATA_LIMIT_ON = true;
  private boolean DATA_LIMIT_EXCEEDED = false;
  private static final String UPLINK_FINISH_MSG = "*";

  private Context context = null;

  // helper variables
  private int accumulativeSize = 0;
  private Random randStr = new Random();
  private ArrayList<Double> samplingResults = new ArrayList<Double>();
  //start time of each sampling period
  private long startSampleTime = 0;
  private String serverVersion = "";
  private long taskStartTime = 0;
  private double taskDuration = 0;
  //uplink accumulative data
  private int totalSendSize = 0;
  // downlink accumulative data
  private int totalRevSize = 0;

  private long duration;
  private TaskProgress taskProgress;
  private volatile boolean stopFlag;

  // class constructor
  public TCPThroughputTask(MeasurementDesc desc) {
    super(new TCPThroughputDesc(desc.key, desc.startTime, desc.endTime,
      desc.intervalSec, desc.count, desc.priority, desc.contextIntervalSec,
      desc.parameters));
    this.taskProgress=TaskProgress.FAILED;
    this.stopFlag=false;
    this.duration=(long)(this.KSEC*
        ((TCPThroughputDesc)measurementDesc).duration_period_sec +
        ((TCPThroughputDesc)measurementDesc).slow_start_period_sec);
    Logger.i("Create new throughput task");
  }

  protected TCPThroughputTask(Parcel in) {
    super(in);
    taskProgress = (TaskProgress)in.readSerializable();
    stopFlag = in.readByte() != 0;
    duration = in.readLong();
  }

  public static final Parcelable.Creator<TCPThroughputTask> CREATOR =
      new Parcelable.Creator<TCPThroughputTask>() {
    public TCPThroughputTask createFromParcel(Parcel in) {
      return new TCPThroughputTask(in);
    }

    public TCPThroughputTask[] newArray(int size) {
      return new TCPThroughputTask[size];
    }
  };

  @Override
  public void writeToParcel(Parcel dest, int flags) {
    super.writeToParcel(dest, flags);
    dest.writeSerializable(taskProgress);
    dest.writeByte((byte) (stopFlag ? 1 : 0));
    dest.writeLong(duration);
  }
  /**
   * There are seven parameters specifically for this experiment:
   * 1. data_limit_mb_up: uplink cellular network data limit
   * 2. data_limit_mb_down: downlink cellular network data limit
   * 3. duration_period_sec : downlink maximum experiment duration period
   * 4. pkt_size_up_bytes: the size each packet in the uplink
   * 5. sample_period_sec : the small interval to calculate current throughput result
   * 6. slow_start_period_sec : waiting period to avoid TCP slow start
   * 7. tcp_timeout_sec: TCP connection timeout
   */

  public static class TCPThroughputDesc extends MeasurementDesc {
    // declared parameters
    public double  data_limit_mb_up =
        TCPThroughputTask.DATA_LIMIT_MB_UP;
    public double  data_limit_mb_down =
        TCPThroughputTask.DATA_LIMIT_MB_DOWN;
    public boolean dir_up = false;
    public double  duration_period_sec =
        TCPThroughputTask.DURATION_IN_SEC;
    public int     pkt_size_up_bytes =
        TCPThroughputTask.THROUGHPUT_UP_PKT_SIZE_MAX;
    public double  sample_period_sec =
        TCPThroughputTask.SAMPLE_PERIOD_IN_SEC;
    public double  slow_start_period_sec =
        TCPThroughputTask.SLOW_START_PERIOD_IN_SEC;
    public String  target = null;
    public double  tcp_timeout_sec = TCPThroughputTask.TCP_TIMEOUT_IN_SEC;

    public TCPThroughputDesc(String key, Date startTime,
        Date endTime, double intervalSec, long count,
        long priority, int contextIntervalSec, Map<String, String> params)
        throws InvalidParameterException {
      super(TCPThroughputTask.TYPE, key, startTime, endTime, intervalSec, count,
        priority, contextIntervalSec, params);
      initializeParams(params);
      if (this.target == null || this.target.length() == 0) {
        throw new InvalidParameterException("TCPThroughputTask null target");
      }
    }

    protected TCPThroughputDesc(Parcel in) {
      super(in);
      data_limit_mb_up = in.readDouble();
      data_limit_mb_down = in.readDouble();
      dir_up = in.readByte() != 0;
      duration_period_sec = in.readDouble();
      pkt_size_up_bytes = in.readInt();
      sample_period_sec = in.readDouble();
      slow_start_period_sec = in.readDouble();
      target = in.readString();
      tcp_timeout_sec = in.readDouble();
    }

    public static final Parcelable.Creator<TCPThroughputDesc> CREATOR
    = new Parcelable.Creator<TCPThroughputDesc>() {
      public TCPThroughputDesc createFromParcel(Parcel in) {
        return new TCPThroughputDesc(in);
      }

      public TCPThroughputDesc[] newArray(int size) {
        return new TCPThroughputDesc[size];
      }
    };

    @Override
    public int describeContents() {
      return super.describeContents();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
      super.writeToParcel(dest, flags);
      dest.writeDouble(data_limit_mb_up);
      dest.writeDouble(data_limit_mb_down);
      dest.writeByte((byte) (dir_up ? 1 : 0));
      dest.writeDouble(duration_period_sec);
      dest.writeInt(pkt_size_up_bytes);
      dest.writeDouble(sample_period_sec);
      dest.writeDouble(slow_start_period_sec);
      dest.writeString(target);
      dest.writeDouble(tcp_timeout_sec);
    }

    @Override
    protected void initializeParams(Map<String, String> params) {
      if (params == null) {
        return;
      }
      if ( (target = params.get("target")) == null ) {
        target = MLabNS.TARGET;
      }

      try {
        String readVal = null;
        if ((readVal = params.get("data_limit_mb_down")) != null &&
            readVal.length() > 0 && Integer.parseInt(readVal) > 0) {
          this.data_limit_mb_down = Double.parseDouble(readVal);
          if (this.data_limit_mb_down > TCPThroughputTask.DATA_LIMIT_MB_DOWN) {
            this.data_limit_mb_down = TCPThroughputTask.DATA_LIMIT_MB_DOWN;
          }
        }

        if ((readVal = params.get("data_limit_mb_up")) != null &&
            readVal.length() > 0 && Integer.parseInt(readVal) > 0) {
          this.data_limit_mb_up = Double.parseDouble(readVal);
          if (this.data_limit_mb_up > TCPThroughputTask.DATA_LIMIT_MB_UP) {
            this.data_limit_mb_up = TCPThroughputTask.DATA_LIMIT_MB_UP;
          }
        }
        if ((readVal = params.get("duration_period_sec")) != null &&
            readVal.length() > 0 && Integer.parseInt(readVal) > 0) {
          this.duration_period_sec = Double.parseDouble(readVal);
          if (this.duration_period_sec > TCPThroughputTask.DURATION_IN_SEC) {
            this.duration_period_sec = TCPThroughputTask.DURATION_IN_SEC;
          }
        }
        if ((readVal = params.get("pkt_size_up_bytes")) != null &&
            readVal.length() > 0 && Integer.parseInt(readVal) > 0) {
          this.pkt_size_up_bytes = Integer.parseInt(readVal);
          if (this.pkt_size_up_bytes > TCPThroughputTask.THROUGHPUT_UP_PKT_SIZE_MAX) {
            this.pkt_size_up_bytes = TCPThroughputTask.THROUGHPUT_UP_PKT_SIZE_MAX;
          }
          if (this.pkt_size_up_bytes < TCPThroughputTask.THROUGHPUT_UP_PKT_SIZE_MIN) {
            this.pkt_size_up_bytes = TCPThroughputTask.THROUGHPUT_UP_PKT_SIZE_MIN;
          }
        }
        if ((readVal = params.get("sample_period_sec")) != null &&
            readVal.length() > 0 && Integer.parseInt(readVal) > 0) {
          this.sample_period_sec = Double.parseDouble(readVal);
          if (this.sample_period_sec > TCPThroughputTask.DURATION_IN_SEC/2) {
            this.sample_period_sec = TCPThroughputTask.DURATION_IN_SEC/2;
          }
        }
        if ((readVal = params.get("slow_start_period_sec")) != null
            && readVal.length() > 0 && Integer.parseInt(readVal) > 0) {
          this.slow_start_period_sec = Double.parseDouble(readVal);
          if (this.slow_start_period_sec > TCPThroughputTask.DURATION_IN_SEC/2) {
            this.slow_start_period_sec = TCPThroughputTask.DURATION_IN_SEC/2;
          }
        }
        if ((readVal = params.get("tcp_timeout_sec")) != null &&
            readVal.length() > 0 && Integer.parseInt(readVal) > 0) {
          this.tcp_timeout_sec = Integer.parseInt(readVal)*1000;
          if (this.tcp_timeout_sec > TCPThroughputTask.TCP_TIMEOUT_IN_SEC) {
            this.tcp_timeout_sec = TCPThroughputTask.TCP_TIMEOUT_IN_SEC;
          }
        }
      } catch  (NumberFormatException e) {
        throw new InvalidParameterException("TCP Throughput Task invalid parameters.");
      }

      String dir = null;
      if ((dir = params.get("dir_up")) != null && dir.length() > 0) {
        if (dir.compareTo("Up") == 0 || dir.compareTo("true") == 0) {
          this.dir_up = true;
        }
      }
    }

    @Override
    public String getType() {
      return TCPThroughputTask.TYPE;
    }

    /**
     * Find the median value from a TCPThroughput JSON result string (already sorted)
     * Suppose N is the number of results. If N is odd, we pick the result with index
     * (N-1)/2. If N is even, we take the mean value between index N/2 and N/2-1
     *
     * @return -1 fail to create result
     * @return median value result
     */
    public double calMedianSpeedFromTCPThroughputOutput(String outputInJSON) {
      if (outputInJSON == null ||
          outputInJSON.equals("") ||
          outputInJSON.equals("[]") ||
          outputInJSON.charAt(0) != '[' ||
          outputInJSON.charAt(outputInJSON.length()-1) != ']') {
        return -1;
      }

      String[] splitResult = outputInJSON.substring(1,
        outputInJSON.length()-1).split(",");
      int resultLen = splitResult.length;
      if (resultLen <= 0)
        return 0.0;
      double result = 0.0;
      if (resultLen % 2 == 0) {
        result = (Double.parseDouble(splitResult[resultLen / 2]) +
            Double.parseDouble(splitResult[resultLen / 2 - 1])) / 2;
      } else {
        result = Double.parseDouble(splitResult[(resultLen - 1) / 2]);
      }
      return result;
    }
  }

  /**
   * Make a deep cloning of the task
   */
  @Override
  public MeasurementTask clone() {
    MeasurementDesc desc = this.measurementDesc;
    TCPThroughputDesc newDesc = new TCPThroughputDesc(
      desc.key, desc.startTime,
      desc.endTime, desc.intervalSec, desc.count, desc.priority,
      desc.contextIntervalSec, desc.parameters);
    return new TCPThroughputTask(newDesc);
  }

  @Override
  public String getType() {
    return TCPThroughputTask.TYPE;
  }

  @Override
  public String getDescriptor() {
    return TCPThroughputTask.DESCRIPTOR;
  }

  /**
   * This will be printed to the device log console. Make sure it's well
   * structured and human readable
   */
  @Override
  public String toString() {
    TCPThroughputDesc desc = (TCPThroughputDesc) measurementDesc;
    String resp;

    if (desc.dir_up) {
      resp = "[TCP Uplink]\n";
    } else {
      resp = "[TCP Downlink]\n";
    }

    resp += " Target: " + desc.target + "\n  Interval (sec): " +
        desc.intervalSec + "\n  Next run: " + desc.startTime;

    return resp;
  }

  @SuppressWarnings("rawtypes")
  public static Class getDescClass() throws InvalidClassException {
    return TCPThroughputDesc.class;
  }

  @Override
  public MeasurementResult[] call() throws MeasurementError {
    this.taskProgress=TaskProgress.FAILED;
    TCPThroughputDesc desc = (TCPThroughputDesc)measurementDesc;

    // Apply MLabNS lookup to fetch FQDN
    if (!desc.target.equals(MLabNS.TARGET)) {
      Logger.i("Not using MLab server!");
      throw new InvalidParameterException("Unknown target " + desc.target +
          " for TCPThroughput");
    }

    try {
      ArrayList<String> mlabResult = MLabNS.Lookup(context, "mobiperf");
      if (mlabResult.size() == 1) {
        desc.target = mlabResult.get(0);
      } else {
        throw new MeasurementError("Invalid MLabNS result");
      }
    } catch (InvalidParameterException e) {
      throw new MeasurementError(e.getMessage());
    }
    Logger.i("Setting target to: " + desc.target);

    PhoneUtils phoneUtils = PhoneUtils.getPhoneUtils();

    // reset the data limit if the phone is under Wifi
    if (phoneUtils.getNetwork().equals(phoneUtils.NETWORK_WIFI)) {
      Logger.i("Detect Wifi network");
      this.DATA_LIMIT_ON = false;
    }

    Logger.i("Running TCPThroughput on " + desc.target);
    try {
      // fetch server information
      if (!acquireServerConfig()) {
        throw new MeasurementError("Fail to acquire server configuration");
      }
      Logger.i("Server version is " + this.serverVersion);
      if (desc.dir_up == true) {
        uplink();
        if(stopFlag){
          throw new MeasurementError("Cancelled");
        }
        Logger.i("Uplink measurement result is:");
      }
      else {
        this.taskStartTime = System.currentTimeMillis();
        downlink();
        if(stopFlag){
          throw new MeasurementError("Cancelled");
        }
        Logger.i("Downlink measurement result is:");
      }
      this.taskProgress=TaskProgress.COMPLETED;
    } catch (MeasurementError e) {
      throw e;
    } catch (IOException e) {
      Logger.e("Error close the socket for " + desc.type);
      throw new MeasurementError("Error close the socket for " + desc.type);
    } catch (InterruptedException e) {
      Logger.e("Interrupted captured");
      throw new MeasurementError("Task gets interrrupted");
    }

    MeasurementResult result = new MeasurementResult(
      phoneUtils.getDeviceInfo().deviceId,
      phoneUtils.getDeviceProperty(), TCPThroughputTask.TYPE,
      System.currentTimeMillis() * 1000, taskProgress,
      this.measurementDesc);
    // TODO (Haokun): add more results if necessary
    result.addResult("tcp_speed_results", this.samplingResults);
    result.addResult("data_limit_exceeded", this.DATA_LIMIT_EXCEEDED);
    result.addResult("duration", this.taskDuration);
    result.addResult("server_version", this.serverVersion);
    Logger.i(MeasurementJsonConvertor.toJsonString(result));
    MeasurementResult[] mrArray= new MeasurementResult[1];
    mrArray[0]=result;
    return mrArray;
  }

  /*****************************************************************
   * Core measurement functions definitions
   *****************************************************************
   * acquire server configuration information
   * 1) m-lab slice version
   *
   * @return: true -- successful acquire data from M-Lab slice
   * @return: false -- failure to acquire data from M-Lab slice
   */
  private boolean acquireServerConfig() throws MeasurementError, IOException,
  InterruptedException {
    Socket tcpSocket = null;
    InputStream iStream = null;
    boolean result = false;
    try {
      tcpSocket = new Socket();
      buildUpSocket(tcpSocket, ((TCPThroughputDesc)measurementDesc).target,
        TCPThroughputTask.PORT_CONFIG);
      iStream = tcpSocket.getInputStream();
    } catch (IOException e) {
      throw new MeasurementError("Error open uplink socket at " +
          ((TCPThroughputDesc)measurementDesc).target +
          " with port " +
          TCPThroughputTask.PORT_CONFIG);
    }

    try {
      // read from server side configuration
      byte [] resultMsg = new byte[this.BUFFER_SIZE];
      int resultMsgLen = iStream.read(resultMsg, 0, resultMsg.length);
      if (resultMsgLen > 0) {
        // TODO (Haokun): Maybe switch to JSON for multiple acquired data
        //               currently use one double number
        this.serverVersion = new String(resultMsg).substring(0, resultMsgLen);
        result = true;
      }
    } catch (IOException e) {
      throw new MeasurementError("Error to acquire configuration from " +
          ((TCPThroughputDesc)measurementDesc).target);
    } finally {
      iStream.close();
      tcpSocket.close();
      Logger.i("Close server Config socket");
    }
    return result;
  }

  /* Uplink measurement task
   * @throws IOException
   * @throws InterruptedException
   */
  private void uplink() throws MeasurementError, IOException, InterruptedException {
    Logger.i("Start uplink task on " + ((TCPThroughputDesc)measurementDesc).target);
    Socket tcpSocket = null;
    InputStream iStream = null;
    OutputStream oStream = null;

    try {
      tcpSocket = new Socket();
      buildUpSocket(tcpSocket, ((TCPThroughputDesc)measurementDesc).target,
        TCPThroughputTask.PORT_UPLINK);
      oStream = tcpSocket.getOutputStream();
      iStream = tcpSocket.getInputStream();
    } catch (IOException e){
      e.printStackTrace();
      throw new MeasurementError("Error open uplink socket at " +
          ((TCPThroughputDesc)measurementDesc).target +
          " with port " +
          TCPThroughputTask.PORT_UPLINK);
    }

    long startTime = System.currentTimeMillis();
    long endTime = startTime;
    int  data_limit_byte_up =
        (int)(((TCPThroughputDesc)measurementDesc).data_limit_mb_up
        *this.KBYTE*this.KBYTE);
    byte[] uplinkBuffer =
        new byte[((TCPThroughputDesc)measurementDesc).pkt_size_up_bytes];
    this.genRandomByteArray(uplinkBuffer);
    try {

      long totalDuration = (long)(this.KSEC*
          ((TCPThroughputDesc)measurementDesc).duration_period_sec +
          ((TCPThroughputDesc)measurementDesc).slow_start_period_sec);
      do {

        if(stopFlag){
          throw new MeasurementError("Cancelled");
        }

        oStream.write(uplinkBuffer, 0, uplinkBuffer.length);
        oStream.flush();
        endTime = System.currentTimeMillis();

        this.totalSendSize += ((TCPThroughputDesc)measurementDesc).pkt_size_up_bytes;
        if (this.DATA_LIMIT_ON &&
            this.totalSendSize >= data_limit_byte_up) {
          Logger.i("Detect uplink exceeding limitation " +
              (double)((TCPThroughputDesc)measurementDesc).data_limit_mb_up + " MB");
          this.DATA_LIMIT_EXCEEDED = true;
          break;
        }

        // propagate every quarter

      } while ((endTime - startTime) < totalDuration);

      // convert into seconds
      this.taskDuration = (double)(endTime - startTime) / 1000.0;
      Logger.i("Uplink total data comsumption is " +
          (double)this.totalSendSize/(1024*1024) + " MB");
      // send last message with special content
      uplinkBuffer = TCPThroughputTask.UPLINK_FINISH_MSG.getBytes();

Answer: oStream.write(uplinkBuffer, 0, uplinkBuffer.length);

<think>
The code is in the uplink() method. After preparing the special finish message by assigning uplinkBuffer = TCPThroughputTask.UPLINK_FINISH_MSG.getBytes(); the next logical step is to actually send this buffer over the socket's output stream. Earlier in the loop they used oStream.write(uplinkBuffer, 0, uplinkBuffer.length);, so the next line should be the same write call for the finish message.
</think>
oStream.write(uplinkBuffer, 0, uplinkBuffer.length);


Context:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;
using IRTaktiks.Components.Playable;
using System.Threading;
using IRTaktiks.Components.Scenario;

namespace IRTaktiks.Components.Manager
{
    /// <summary>
    /// Manager of animations.
    /// </summary>
    public class AnimationManager
    {
        #region Singleton

        /// <summary>
        /// The instance of the class.
        /// </summary>
        private static AnimationManager InstanceField;

        /// <summary>
        /// The instance of the class.
        /// </summary>
        public static AnimationManager Instance
        {
            get { if (InstanceField == null) InstanceField = new AnimationManager(); return InstanceField; }
        }

        /// <summary>
        /// Private constructor.
        /// </summary>
        private AnimationManager()
        { }

        #endregion

        #region AnimationType

        /// <summary>
        /// Type of animations.
        /// </summary>
        public enum AnimationType
        {
            /// <summary>
            /// Animation for Short attack.
            /// </summary>
            Short,

            /// <summary>
            /// Animation for Long attack.
            /// </summary>
            Long,

            /// <summary>
            /// Animation for Stealth skill.
            /// </summary>
            Stealth,

            /// <summary>
            /// Animation for Ambush skill.
            /// </summary>
            Ambush,

            /// <summary>
            /// Animation for Curse skill.
            /// </summary>
            Curse,

            /// <summary>
            /// Animation for Quick skill.
            /// </summary>
            Quick,

            /// <summary>
            /// Animation for Impact skill.
            /// </summary>
            Impact,

            /// <summary>
            /// Animation for Revenge skill.
            /// </summary>
            Revenge,

            /// <summary>
            /// Animation for Warcry skill.
            /// </summary>
            Warcry,

            /// <summary>
            /// Animation for Insane skill.
            /// </summary>
            Insane,

            /// <summary>
            /// Animation for Reject skill.
            /// </summary>
            Reject,

            /// <summary>
            /// Animation for Might skill.
            /// </summary>
            Might,

            /// <summary>
            /// Animation for Heal skill.
            /// </summary>
            Heal,

            /// <summary>
            /// Animation for Unseal skill.
            /// </summary>
            Unseal,

            /// <summary>
            /// Animation for Barrier skill.
            /// </summary>
            Barrier,

            /// <summary>
            /// Animation for Holy skill.
            /// </summary>
            Holy,

            /// <summary>
            /// Animation for Drain skill.
            /// </summary>
            Drain,

            /// <summary>
            /// Animation for Flame skill.
            /// </summary>
            Flame,

            /// <summary>
            /// Animation for Frost skill.
            /// </summary>
            Frost,

            /// <summary>
            /// Animation for Item usage.
            /// </summary>
            Item,

            /// <summary>
            /// Animation for Elixir.
            /// </summary>
            Elixir
        }

        #endregion

        #region Properties

        /// <summary>
        /// The particle manager of game.
        /// </summary>
        private ParticleManager ParticleManager;

        #endregion

        #region Initialize

        /// <summary>
        /// Initializes the animation manager.
        /// </summary>
        /// <param name="game">The instance of game.</param>
        public void Initialize(Game game)
        {
            this.ParticleManager = (game as IRTGame).ParticleManager;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Do the respective animation in the specified position.
        /// </summary>
        /// <param name="type">Type of animation.</param>
        /// <param name="position">The central position of the animation.</param>
        public void QueueAnimation(AnimationType type, Vector2 position)
        {
            object[] parameters = new object[2];

            parameters[0] = type;
            parameters[1] = position;

            ThreadPool.QueueUserWorkItem(Animate, parameters);
        }

        /// <summary>
        /// Do some animation.
        /// </summary>
        /// <param name="data">Data tranferred across the threads.</param>
        private void Animate(object data)
        {
            object[] parameters = data as object[];

            AnimationType type = (AnimationType)parameters[0];
            Vector2 position = (Vector2)parameters[1];

            switch (type)
            {
                case AnimationType.Ambush:
                    this.Ambush(position);
                    break;

                case AnimationType.Barrier:
                    this.Barrier(position);
                    break;

                case AnimationType.Curse:
                    this.Curse(position);
                    break;

                case AnimationType.Drain:
                    this.Drain(position);
                    break;

                case AnimationType.Elixir:
                    this.Elixir(position);
                    break;

                case AnimationType.Flame:
                    this.Flame(position);
                    break;

                case AnimationType.Frost:
                    this.Frost(position);
                    break;

                case AnimationType.Heal:
                    this.Heal(position);
                    break;

                case AnimationType.Holy:
                    this.Holy(position);
                    break;

                case AnimationType.Impact:
                    this.Impact(position);
                    break;

                case AnimationType.Insane:
                    this.Insane(position);
                    break;

                case AnimationType.Item:
                    this.Item(position);
                    break;

                case AnimationType.Long:
                    this.Long(position);
                    break;

                case AnimationType.Might:
                    this.Might(position);
                    break;

                case AnimationType.Quick:
                    this.Quick(position);
                    break;

                case AnimationType.Reject:
                    this.Reject(position);
                    break;

                case AnimationType.Revenge:
                    this.Revenge(position);
                    break;

                case AnimationType.Short:
                    this.Short(position);
                    break;

                case AnimationType.Stealth:
                    this.Stealth(position);
                    break;

                case AnimationType.Unseal:
                    this.Unseal(position);
                    break;

                case AnimationType.Warcry:
                    this.Warcry(position);
                    break;
            }
        }

        #endregion

        #region Animations

        /// <summary>
        /// Execute the Animation.
        /// </summary>
        /// <param name="position">The target position.</param>
        private void None(Vector2 position)
        {
            int particles = 50;
            float lifeIncrement = 0.1f;
            float totalLife = 2.5f;

            Vector2 position1st = new Vector2(position.X - 5, position.Y - 5);
            Vector2 position2nd = new Vector2(position.X - 5, position.Y + 5);
            Vector2 position3rd = new Vector2(position.X + 5, position.Y - 5);
            Vector2 position4th = new Vector2(position.X + 5, position.Y + 5);

            this.ParticleManager.Queue(new ParticleEffect(position1st, particles, ParticleEffect.EffectType.Firework, lifeIncrement, totalLife, Color.Tan));

            Thread.Sleep(50);

            this.ParticleManager.Queue(new ParticleEffect(position2nd, particles, ParticleEffect.EffectType.Firework, lifeIncrement, totalLife, Color.RoyalBlue));

            Thread.Sleep(50);

            this.ParticleManager.Queue(new ParticleEffect(position3rd, particles, ParticleEffect.EffectType.Firework, lifeIncrement, totalLife, Color.IndianRed));

            Thread.Sleep(50);

            this.ParticleManager.Queue(new ParticleEffect(position4th, particles, ParticleEffect.EffectType.Firework, lifeIncrement, totalLife, Color.SpringGreen));
        }

        /// <summary>
        /// Execute the Animation.
        /// </summary>
        /// <param name="position">The target position.</param>
        private void Ambush(Vector2 position)
        {
            int sliceParticles = 30;
            int bloodParticles = 25;
            float sliceLifeIncrement = 0.2f;
            float bloodLifeIncrement = 0.3f;
            float sliceTotalLife = 2.5f;
            float bloodTotalLife = 3.0f;

            Vector2 slicePosition = new Vector2(position.X - 1, position.Y - 1);
            Vector2 bloodPosition1st = new Vector2(position.X + 1, position.Y + 1);
            Vector2 bloodPosition2st = new Vector2(position.X + 2, position.Y + 2);

            this.ParticleManager.Queue(new ParticleEffect(slicePosition, sliceParticles, ParticleEffect.EffectType.Slice, sliceLifeIncrement, sliceTotalLife, Color.Silver));

            Thread.Sleep(100);

            this.ParticleManager.Queue(new ParticleEffect(bloodPosition1st, bloodParticles, ParticleEffect.EffectType.Flash45, bloodLifeIncrement, bloodTotalLife, Color.Red));

            Thread.Sleep(100);

            this.ParticleManager.Queue(new ParticleEffect(bloodPosition2st, bloodParticles, ParticleEffect.EffectType.Flash0, bloodLifeIncrement, bloodTotalLife, Color.Red));

            this.ParticleManager.Queue(new ParticleEffect(slicePosition, sliceParticles, ParticleEffect.EffectType.Slice, sliceLifeIncrement, sliceTotalLife, Color.Silver));

            Thread.Sleep(100);

            this.ParticleManager.Queue(new ParticleEffect(bloodPosition1st, bloodParticles, ParticleEffect.EffectType.Flash45, bloodLifeIncrement, bloodTotalLife, Color.Red));

            Thread.Sleep(100);

            this.ParticleManager.Queue(new ParticleEffect(bloodPosition2st, bloodParticles, ParticleEffect.EffectType.Flash0, bloodLifeIncrement, bloodTotalLife, Color.Red));
        }

        /// <summary>
        /// Execute the Animation.
        /// </summary>
        /// <param name="position">The target position.</param>
        private void Barrier(Vector2 position)
        {
            int ringParticles = 50;
            int sphereParticles = 100;

            float ringLifeIncrement = 0.1f;
            float sphereLifeIncrement = 0.7f;

            float ringTotalLife = 4.5f;
            float sphereTotalLife = 21.0f;

            Vector2 position1st = new Vector2(position.X, position.Y);

            this.ParticleManager.Queue(new ParticleEffect(position1st, ringParticles, ParticleEffect.EffectType.Ring, ringLifeIncrement, ringTotalLife, Color.Silver));

            Thread.Sleep(500);

            this.ParticleManager.Queue(new ParticleEffect(position1st, sphereParticles, ParticleEffect.EffectType.Sphere, sphereLifeIncrement, sphereTotalLife, Color.Silver));
        }

        /// <summary>
        /// Execute the Animation.
        /// </summary>
        /// <param name="position">The target position.</param>
        private void Curse(Vector2 position)
        {
            int particles = 50;
            float lifeIncrement = 0.1f;
            float totalLife = 4.5f;

            Vector2 position1st = new Vector2(position.X - 12, position.Y + 15);
            Vector2 position2st = new Vector2(position.X - 0, position.Y - 15);
            Vector2 position3st = new Vector2(position.X - 20, position.Y + 0);
            Vector2 position4st = new Vector2(position.X + 12, position.Y + 15);
            Vector2 position5st = new Vector2(position.X + 20, position.Y + 0);

            this.ParticleManager.Queue(new ParticleEffect(position1st, particles, ParticleEffect.EffectType.Slice, lifeIncrement, totalLife, Color.Green));
            this.ParticleManager.Queue(new ParticleEffect(position2st, particles, ParticleEffect.EffectType.Slice, lifeIncrement, totalLife, Color.Green));
            this.ParticleManager.Queue(new ParticleEffect(position3st, particles, ParticleEffect.EffectType.Slice, lifeIncrement, totalLife, Color.Green));
            this.ParticleManager.Queue(new ParticleEffect(position4st, particles, ParticleEffect.EffectType.Slice, lifeIncrement, totalLife, Color.Green));
            this.ParticleManager.Queue(new ParticleEffect(position5st, particles, ParticleEffect.EffectType.Slice, lifeIncrement, totalLife, Color.Green));
        }

        /// <summary>
        /// Execute the Animation.
        /// </summary>
        /// <param name="position">The target position.</param>
        private void Drain(Vector2 position)
        {
            int particles = 50;
            float lifeIncrement = 0.1f;
            float totalLife = 4.5f;

            Vector2 position1st = new Vector2(position.X, position.Y);

            this.ParticleManager.Queue(new ParticleEffect(position1st, particles, ParticleEffect.EffectType.Pillar, lifeIncrement, totalLife, Color.Red));
        }

        /// <summary>
        /// Execute the Animation.
        /// </summary>
        /// <param name="position">The target position.</param>
        private void Elixir(Vector2 position)
        {
            int particles = 25;
            float lifeIncrement = 0.07f;
            float totalLife = 3.5f;

            Vector2 position1st = new Vector2(position.X - 15, position.Y + 15);
            Vector2 position2nd = new Vector2(position.X - 05, position.Y + 05);
            Vector2 position3rd = new Vector2(position.X + 05, position.Y - 05);
            Vector2 position4th = new Vector2(position.X + 15, position.Y - 15);

            this.ParticleManager.Queue(new ParticleEffect(position1st, particles, ParticleEffect.EffectType.Flash0, lifeIncrement, totalLife, Color.ForestGreen));
            this.ParticleManager.Queue(new ParticleEffect(position1st, particles, ParticleEffect.EffectType.Flash45, lifeIncrement, totalLife, Color.ForestGreen));
            this.ParticleManager.Queue(new ParticleEffect(position1st, particles, ParticleEffect.EffectType.Flash90, lifeIncrement, totalLife, Color.ForestGreen));
            this.ParticleManager.Queue(new ParticleEffect(position1st, particles, ParticleEffect.EffectType.Flash135, lifeIncrement, totalLife, Color.ForestGreen));

            Thread.Sleep(200);

            this.ParticleManager.Queue(new ParticleEffect(position2nd, particles, ParticleEffect.EffectType.Flash0, lifeIncrement, totalLife, Color.Crimson));
            this.ParticleManager.Queue(new ParticleEffect(position2nd, particles, ParticleEffect.EffectType.Flash45, lifeIncrement, totalLife, Color.Crimson));
            this.ParticleManager.Queue(new ParticleEffect(position2nd, particles, ParticleEffect.EffectType.Flash90, lifeIncrement, totalLife, Color.Crimson));
            this.ParticleManager.Queue(new ParticleEffect(position2nd, particles, ParticleEffect.EffectType.Flash135, lifeIncrement, totalLife, Color.Crimson));

            Thread.Sleep(200);

            this.ParticleManager.Queue(new ParticleEffect(position3rd, particles, ParticleEffect.EffectType.Flash0, lifeIncrement, totalLife, Color.Chocolate));
            this.ParticleManager.Queue(new ParticleEffect(position3rd, particles, ParticleEffect.EffectType.Flash45, lifeIncrement, totalLife, Color.Chocolate));
            this.ParticleManager.Queue(new ParticleEffect(position3rd, particles, ParticleEffect.EffectType.Flash90, lifeIncrement, totalLife, Color.Chocolate));
            this.ParticleManager.Queue(new ParticleEffect(position3rd, particles, ParticleEffect.EffectType.Flash135, lifeIncrement, totalLife, Color.Chocolate));

            Thread.Sleep(200);

            this.ParticleManager.Queue(new ParticleEffect(position4th, particles, ParticleEffect.EffectType.Flash0, lifeIncrement, totalLife, Color.BlueViolet));
            this.ParticleManager.Queue(new ParticleEffect(position4th, particles, ParticleEffect.EffectType.Flash45, lifeIncrement, totalLife, Color.BlueViolet));
            this.ParticleManager.Queue(new ParticleEffect(position4th, particles, ParticleEffect.EffectType.Flash90, lifeIncrement, totalLife, Color.BlueViolet));
            this.ParticleManager.Queue(new ParticleEffect(position4th, particles, ParticleEffect.EffectType.Flash135, lifeIncrement, totalLife, Color.BlueViolet));
        }

        /// <summary>
        /// Execute the Animation.
        /// </summary>
        /// <param name="position">The target position.</param>
        private void Flame(Vector2 position)
        {
            int particles1st = 25;
            int particles2nd = 10;
            int particles3rd = 5;
            float lifeIncrement1st = 0.02f;
            float lifeIncrement2nd = 0.04f;
            float lifeIncrement3rd = 0.06f;
            float totalLife = 3.0f;

            Vector2 position1st = new Vector2(position.X - 15, position.Y - 15);
            Vector2 position2nd = new Vector2(position.X - 10, position.Y - 10);
            Vector2 position3rd = new Vector2(position.X - 05, position.Y - 05);
            Vector2 position4th = new Vector2(position.X - 0, position.Y - 0);
            Vector2 position5th = new Vector2(position.X + 05, position.Y - 05);
            Vector2 position6th = new Vector2(position.X + 10, position.Y - 10);
            Vector2 position7th = new Vector2(position.X + 15, position.Y - 15);

            this.ParticleManager.Queue(new ParticleEffect(position1st, particles3rd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.Gold));
            this.ParticleManager.Queue(new ParticleEffect(position2nd, particles2nd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.Red));
            this.ParticleManager.Queue(new ParticleEffect(position3rd, particles1st, ParticleEffect.EffectType.Losangle, lifeIncrement2nd, totalLife, Color.Goldenrod));
            this.ParticleManager.Queue(new ParticleEffect(position4th, particles1st, ParticleEffect.EffectType.Firework, lifeIncrement1st, totalLife, Color.Red));
            this.ParticleManager.Queue(new ParticleEffect(position5th, particles1st, ParticleEffect.EffectType.Losangle, lifeIncrement2nd, totalLife, Color.Tomato));
            this.ParticleManager.Queue(new ParticleEffect(position6th, particles2nd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.Goldenrod));
            this.ParticleManager.Queue(new ParticleEffect(position7th, particles3rd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.Gold));

            Thread.Sleep(150);

            this.ParticleManager.Queue(new ParticleEffect(position1st, particles3rd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.Gold));
            this.ParticleManager.Queue(new ParticleEffect(position2nd, particles2nd, ParticleEffect.EffectType.Firework, lifeIncrement1st, totalLife, Color.Goldenrod));
            this.ParticleManager.Queue(new ParticleEffect(position3rd, particles1st, ParticleEffect.EffectType.Firework, lifeIncrement2nd, totalLife, Color.Red));
            this.ParticleManager.Queue(new ParticleEffect(position4th, particles1st, ParticleEffect.EffectType.Firework, lifeIncrement1st, totalLife, Color.WhiteSmoke));
            this.ParticleManager.Queue(new ParticleEffect(position5th, particles1st, ParticleEffect.EffectType.Firework, lifeIncrement2nd, totalLife, Color.Red));
            this.ParticleManager.Queue(new ParticleEffect(position6th, particles2nd, ParticleEffect.EffectType.Firework, lifeIncrement1st, totalLife, Color.Goldenrod));
            this.ParticleManager.Queue(new ParticleEffect(position7th, particles3rd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.Gold));

            Thread.Sleep(150);

            this.ParticleManager.Queue(new ParticleEffect(position1st, particles3rd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.Gold));
            this.ParticleManager.Queue(new ParticleEffect(position2nd, particles2nd, ParticleEffect.EffectType.Firework, lifeIncrement3rd, totalLife, Color.Goldenrod));
            this.ParticleManager.Queue(new ParticleEffect(position3rd, particles1st, ParticleEffect.EffectType.Losangle, lifeIncrement2nd, totalLife, Color.Thistle));
            this.ParticleManager.Queue(new ParticleEffect(position4th, particles1st, ParticleEffect.EffectType.Firework, lifeIncrement1st, totalLife, Color.Red));
            this.ParticleManager.Queue(new ParticleEffect(position5th, particles1st, ParticleEffect.EffectType.Losangle, lifeIncrement2nd, totalLife, Color.Thistle));
            this.ParticleManager.Queue(new ParticleEffect(position6th, particles2nd, ParticleEffect.EffectType.Firework, lifeIncrement3rd, totalLife, Color.Goldenrod));
            this.ParticleManager.Queue(new ParticleEffect(position7th, particles3rd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.Gold));

            Thread.Sleep(150);

            this.ParticleManager.Queue(new ParticleEffect(position1st, particles3rd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.Gold));
            this.ParticleManager.Queue(new ParticleEffect(position2nd, particles2nd, ParticleEffect.EffectType.Firework, lifeIncrement1st, totalLife, Color.Goldenrod));
            this.ParticleManager.Queue(new ParticleEffect(position3rd, particles1st, ParticleEffect.EffectType.Firework, lifeIncrement2nd, totalLife, Color.Red));
            this.ParticleManager.Queue(new ParticleEffect(position4th, particles1st, ParticleEffect.EffectType.Firework, lifeIncrement1st, totalLife, Color.WhiteSmoke));
            this.ParticleManager.Queue(new ParticleEffect(position5th, particles1st, ParticleEffect.EffectType.Firework, lifeIncrement2nd, totalLife, Color.Red));
            this.ParticleManager.Queue(new ParticleEffect(position6th, particles2nd, ParticleEffect.EffectType.Firework, lifeIncrement1st, totalLife, Color.Goldenrod));
            this.ParticleManager.Queue(new ParticleEffect(position7th, particles3rd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.Gold));
        }

        /// <summary>
        /// Execute the Animation.
        /// </summary>
        /// <param name="position">The target position.</param>
        private void Frost(Vector2 position)
        {
            int particles1st = 25;
            int particles2nd = 10;
            int particles3rd = 5;
            float lifeIncrement1st = 0.02f;
            float lifeIncrement2nd = 0.04f;
            float lifeIncrement3rd = 0.06f;
            float totalLife = 3.0f;

            Vector2 position1st = new Vector2(position.X - 15, position.Y - 15);
            Vector2 position2nd = new Vector2(position.X - 10, position.Y - 10);
            Vector2 position3rd = new Vector2(position.X - 05, position.Y - 05);
            Vector2 position4th = new Vector2(position.X - 0, position.Y - 0);
            Vector2 position5th = new Vector2(position.X + 05, position.Y - 05);
            Vector2 position6th = new Vector2(position.X + 10, position.Y - 10);
            Vector2 position7th = new Vector2(position.X + 15, position.Y - 15);

            this.ParticleManager.Queue(new ParticleEffect(position1st, particles3rd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.Violet));
            this.ParticleManager.Queue(new ParticleEffect(position2nd, particles2nd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.Aquamarine));
            this.ParticleManager.Queue(new ParticleEffect(position3rd, particles1st, ParticleEffect.EffectType.Losangle, lifeIncrement2nd, totalLife, Color.ForestGreen));
            this.ParticleManager.Queue(new ParticleEffect(position4th, particles1st, ParticleEffect.EffectType.Firework, lifeIncrement1st, totalLife, Color.Wheat));
            this.ParticleManager.Queue(new ParticleEffect(position5th, particles1st, ParticleEffect.EffectType.Losangle, lifeIncrement2nd, totalLife, Color.Aquamarine));
            this.ParticleManager.Queue(new ParticleEffect(position6th, particles2nd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.ForestGreen));
            this.ParticleManager.Queue(new ParticleEffect(position7th, particles3rd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.Violet));

            Thread.Sleep(150);

            this.ParticleManager.Queue(new ParticleEffect(position1st, particles3rd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.CornflowerBlue));
            this.ParticleManager.Queue(new ParticleEffect(position2nd, particles2nd, ParticleEffect.EffectType.Firework, lifeIncrement1st, totalLife, Color.CadetBlue));
            this.ParticleManager.Queue(new ParticleEffect(position3rd, particles1st, ParticleEffect.EffectType.Firework, lifeIncrement2nd, totalLife, Color.Blue));
            this.ParticleManager.Queue(new ParticleEffect(position4th, particles1st, ParticleEffect.EffectType.Firework, lifeIncrement1st, totalLife, Color.Turquoise));
            this.ParticleManager.Queue(new ParticleEffect(position5th, particles1st, ParticleEffect.EffectType.Firework, lifeIncrement2nd, totalLife, Color.Blue));
            this.ParticleManager.Queue(new ParticleEffect(position6th, particles2nd, ParticleEffect.EffectType.Firework, lifeIncrement1st, totalLife, Color.CadetBlue));
            this.ParticleManager.Queue(new ParticleEffect(position7th, particles3rd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.CornflowerBlue));

            Thread.Sleep(150);

            this.ParticleManager.Queue(new ParticleEffect(position1st, particles3rd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.DarkBlue));
            this.ParticleManager.Queue(new ParticleEffect(position2nd, particles2nd, ParticleEffect.EffectType.Firework, lifeIncrement3rd, totalLife, Color.CornflowerBlue));
            this.ParticleManager.Queue(new ParticleEffect(position3rd, particles1st, ParticleEffect.EffectType.Losangle, lifeIncrement2nd, totalLife, Color.DarkViolet));
            this.ParticleManager.Queue(new ParticleEffect(position4th, particles1st, ParticleEffect.EffectType.Firework, lifeIncrement1st, totalLife, Color.Blue));
            this.ParticleManager.Queue(new ParticleEffect(position5th, particles1st, ParticleEffect.EffectType.Losangle, lifeIncrement2nd, totalLife, Color.DarkViolet));
            this.ParticleManager.Queue(new ParticleEffect(position6th, particles2nd, ParticleEffect.EffectType.Firework, lifeIncrement3rd, totalLife, Color.CornflowerBlue));
            this.ParticleManager.Queue(new ParticleEffect(position7th, particles3rd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.DarkBlue));

            Thread.Sleep(150);

            this.ParticleManager.Queue(new ParticleEffect(position1st, particles3rd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.CornflowerBlue));
            this.ParticleManager.Queue(new ParticleEffect(position2nd, particles2nd, ParticleEffect.EffectType.Firework, lifeIncrement1st, totalLife, Color.DarkBlue));
            this.ParticleManager.Queue(new ParticleEffect(position3rd, particles1st, ParticleEffect.EffectType.Firework, lifeIncrement2nd, totalLife, Color.CornflowerBlue));
            this.ParticleManager.Queue(new ParticleEffect(position4th, particles1st, ParticleEffect.EffectType.Firework, lifeIncrement1st, totalLife, Color.Blue));
            this.ParticleManager.Queue(new ParticleEffect(position5th, particles1st, ParticleEffect.EffectType.Firework, lifeIncrement2nd, totalLife, Color.DarkViolet));
            this.ParticleManager.Queue(new ParticleEffect(position6th, particles2nd, ParticleEffect.EffectType.Firework, lifeIncrement1st, totalLife, Color.CornflowerBlue));
            this.ParticleManager.Queue(new ParticleEffect(position7th, particles3rd, ParticleEffect.EffectType.Losangle, lifeIncrement3rd, totalLife, Color.DarkBlue));
        }

        /// <summary>
        /// Execute the Animation.
        /// </summary>
        /// <param name="position">The target position.</param>
        private void Heal(Vector2 position)
        {
            int particles = 50;
            float lifeIncrement = 0.07f;
            float totalLife = 5.0f;

            Vector2 position1st = new Vector2(position.X - 10, position.Y + 10);
            Vector2 position2nd = new Vector2(position.X + 10, position.Y - 10);
            Vector2 position3rd = new Vector2(position.X + 10, position.Y + 10);

            this.ParticleManager.Queue(new ParticleEffect(position1st, particles, ParticleEffect.EffectType.Flash0, lifeIncrement, totalLife, Color.ForestGreen));
            this.ParticleManager.Queue(new ParticleEffect(position1st, particles, ParticleEffect.EffectType.Flash45, lifeIncrement, totalLife, Color.ForestGreen));
            this.ParticleManager.Queue(new ParticleEffect(position1st, particles, ParticleEffect.EffectType.Flash90, lifeIncrement, totalLife, Color.ForestGreen));
            this.ParticleManager.Queue(new ParticleEffect(position1st, particles, ParticleEffect.EffectType.Flash135, lifeIncrement, totalLife, Color.ForestGreen));

            Thread.Sleep(200);

            this.ParticleManager.Queue(new ParticleEffect(position2nd, particles, ParticleEffect.EffectType.Flash0, lifeIncrement, totalLife, Color.Crimson));
            this.ParticleManager.Queue(new ParticleEffect(position2nd, particles, ParticleEffect.EffectType.Flash45, lifeIncrement, totalLife, Color.Crimson));
            this.ParticleManager.Queue(new ParticleEffect(position2nd, particles, ParticleEffect.EffectType.Flash90, lifeIncrement, totalLife, Color.Crimson));
            this.ParticleManager.Queue(new ParticleEffect(position2nd, particles, ParticleEffect.EffectType.Flash135, lifeIncrement, totalLife, Color.Crimson));

            Thread.Sleep(200);

            this.ParticleManager.Queue(new ParticleEffect(position3rd, particles, ParticleEffect.EffectType.Flash0, lifeIncrement, totalLife, Color.Chocolate));
            this.ParticleManager.Queue(new ParticleEffect(position3rd, particles, ParticleEffect.EffectType.Flash45, lifeIncrement, totalLife, Color.Chocolate));
            this.ParticleManager.Queue(new ParticleEffect(position3rd, particles, ParticleEffect.EffectType.Flash90, lifeIncrement, totalLife, Color.Chocolate));
            this.ParticleManager.Queue(new ParticleEffect(position3rd, particles, ParticleEffect.EffectType.Flash135, lifeIncrement, totalLife, Color.Chocolate));
        }

        /// <summary>
        /// Execute the Animation.
        /// </summary>
        /// <param name="position">The target position.</param>
        private void Holy(Vector2 position)
        {
            int particles = 50;
            float lifeIncrement = 0.06f;
            float totalLife = 3.0f;

            Vector2 horizontalPosition1st = new Vector2(position.X - 0, position.Y + 03);
            Vector2 horizontalPosition2nd = new Vector2(position.X - 0, position.Y + 0);
            Vector2 horizontalPosition3rd = new Vector2(position.X + 0, position.Y - 03);
            Vector2 verticalPosition1st = new Vector2(position.X - 03, position.Y + 0);
            Vector2 verticalPosition2nd = new Vector2(position.X - 0, position.Y + 0);
            Vector2 verticalPosition3rd = new Vector2(position.X + 03, position.Y - 0);

            this.ParticleManager.Queue(new ParticleEffect(horizontalPosition1st, particles, ParticleEffect.EffectType.Flash0, lifeIncrement, totalLife, Color.Goldenrod));
            this.ParticleManager.Queue(new ParticleEffect(horizontalPosition2nd, particles, ParticleEffect.EffectType.Flash0, lifeIncrement, totalLife, Color.Goldenrod));
            this.ParticleManager.Queue(new ParticleEffect(horizontalPosition3rd, particles, ParticleEffect.EffectType.Flash0, lifeIncrement, totalLife, Color.Goldenrod));
            this.ParticleManager.Queue(new ParticleEffect(verticalPosition1st, particles, ParticleEffect.EffectType.Flash90, lifeIncrement, totalLife, Color.Goldenrod));

Answer: this.ParticleManager.Queue(new ParticleEffect(verticalPosition2nd, particles, ParticleEffect.EffectType.Flash90, lifeIncrement, totalLife, Color.Goldenrod));

<think>
The Holy method defines six position vectors: three horizontal and three vertical. It already queued the three horizontal positions and the first vertical position (verticalPosition1st) with a Flash90 effect. The next logical step is to queue the second vertical position (verticalPosition2nd) using the same effect parameters, matching the pattern of queuing all defined positions.
</think>
this.ParticleManager.Queue(new ParticleEffect(verticalPosition2nd, particles, ParticleEffect.EffectType.Flash90, lifeIncrement, totalLife, Color.Goldenrod));


Context:
/*==============================================================================
Copyright (c) 2013 Disney Research
All Rights Reserved.
==============================================================================*/

// debug display
#define SHOW_DEBUG

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;

/// <summary>
/// Defines the AR exposure modes
/// </summary>
public enum ARExposureMode
{
	Locked = 0,
	AutoExpose = 1,
	ContinuousAutoExposure = 2,
}

/// <summary>
/// Defines the AR focus modes
/// </summary>
public enum ARFocusMode
{
	Locked = 0,
	AutoFocus = 1,
	ContinuousAutoFocus = 2,
}

/// <summary>
/// Defines the AR debug modes
/// </summary>
public enum ARDebugMode
{
	Off = 0,
	Gray = 1,
	Distance = 2,
	CentroidX = 3,
	CentroidY = 4,
	Centroid0X = 5,
	Centroid0Y = 6,
	Centroid1X = 7,
	Centroid1Y = 8,
	KMeans = 9
}

// Defines the AR feature tracking modes
public enum ARFeatureTrackingMode
{
	kMeansCPU = 0,
	MomentGPU = 1
}

/// <summary>
/// SkyePeek Plugin Interface
/// </summary>
public class SkyePeek : MonoBehaviour
{
	#region PEEK_IMPORTS

#if UNITY_IOS && !UNITY_EDITOR
    [DllImport ("__Internal")]
	private static extern void _peekStart(bool useFront, int texId);

    [DllImport ("__Internal")]
	private static extern void _peekReadPixels(int dstTexId, int srcTexId,  int width, int height, IntPtr data);

    [DllImport ("__Internal")]
	private static extern void _peekEnd();

    [DllImport("__Internal")]
    private static extern void _peekSetFocusMode( int focusMode );

    [DllImport("__Internal")]
    private static extern void _peekSetExposureMode( int exposureMode );

    [DllImport("__Internal")]
    private static extern float _peekHalf2Float( short sum );

#else
    /// <summary>
    /// Starts the camera capture
    /// </summary>
    private  void _peekStart(bool useFront, int texId) {}

    /// <summary>
    /// Stops the camera capture
    /// </summary>
	private  void _peekEnd() {}

    /// <summary>
    /// Sets the camera focus mode
    /// </summary>
	private  void _peekSetFocusMode(int focusMode ) {}

    /// <summary>
    /// Sets the camera exposure mode
    /// </summary>
	private  void _peekSetExposureMode(int exposureMode ) {}
#endif

    /// <summary>
    ///  Writes a RenderTexture into a Texture2D
    /// </summary>
	private void TextureFromRT(Texture2D dstTex, RenderTexture srcTex)
	{
		int width=dstTex.width;
		int height=dstTex.height;

#if UNITY_IOS && !UNITY_EDITOR
		if (handlePx.IsAllocated)
			handlePx.Free();

		handlePx = GCHandle.Alloc (curTexData, GCHandleType.Pinned);
		_peekReadPixels(dstTex.GetNativeTextureID(), srcTex.GetNativeTextureID(), width, height, handlePx.AddrOfPinnedObject());
#else
		RenderTexture.active = srcTex;
		dstTex.ReadPixels(new Rect(0,0, width, height), 0,0, false);
		//dstTex.Apply(); only need if rendering with this tex
#endif
		curTex=dstTex;
	}

    /// <summary>
    ///  Accumulates sum and vol from texture
    /// </summary>
	private void AccumulateFromTex(ref float sum, ref float vol, int x, int y)
	{
#if UNITY_IOS && !UNITY_EDITOR
		int idx=(y*curTex.width + x)*2;
		sum += _peekHalf2Float(curTexData[idx++]);
		vol += _peekHalf2Float(curTexData[idx]);
#else
		Color rg=curTex.GetPixel(x,y);
		sum += rg.r;
		vol += rg.g;
#endif
	}

    /// <summary>
    ///  Frees handles in iOS
    /// </summary>
	private void DoneAccumulation()
	{
#if UNITY_IOS && !UNITY_EDITOR
		if (handlePx.IsAllocated)
			handlePx.Free();
#endif
	}

	#endregion // PEEK_IMPORTS

    #region PUBLIC_METHODS

    /// <summary>
    ///  Disables AR debugging
    /// </summary>
    public void DisableDebug(bool disable)
    {
        debugMode = ARDebugMode.Off;
    }

    /// <summary>
    ///  Sets the blimp color
    /// </summary>
    public void SetBlimpColor(Color color)
    {
        this.refColor = color;
    }

    /// <summary>
    ///  Starts the camera capture and returns a Texture2D that will have the camera output as it's content
    /// </summary>
    public void startCameraCapture( bool useFrontCameraIfAvailable )
    {
        // Create texture that will be updated in the plugin code/webcam
		MakeVideoTexture();

        if( Application.platform == RuntimePlatform.IPhonePlayer )
			_peekStart( useFrontCameraIfAvailable, videoTexture.GetNativeTextureID() );
	}

    /// <summary>
    ///  Stops the camera capture
    /// </summary>
    public  void stopCameraCapture()
    {
		_peekEnd();
    }

    /// <summary>
    /// Sets the exposure mode.  Capture must be started for this to work!
    /// </summary>
    public  void setExposureMode( ARExposureMode mode )
    {
		_peekSetExposureMode( (int)mode );
    }

    /// <summary>
    /// Sets the focus mode.  Capture must be started for this to work!
    /// </summary>
    public  void setFocusMode( ARFocusMode mode )
    {
		_peekSetFocusMode( (int)mode );
    }

    /// <summary>
    ///  Updates the materials UV offset to accommodate the texture being placed into the next biggest power of 2 container
    /// </summary>
	public  void updateMaterialUVScaleForTexture( Material material, Texture2D texture )
	{
		Vector2 textureOffset = new Vector2( (float)texture.width / (float)nearestPowerOfTwo( texture.width ), (float)texture.height / (float)nearestPowerOfTwo( texture.height ) );
		material.mainTextureScale = textureOffset;
	}

    #endregion // PUBLIC_METHODS

	#region PUBLIC_MEMBERS

	public bool useFrontCamera = false;
	public ARFocusMode focusMode = ARFocusMode.ContinuousAutoFocus;
	public ARExposureMode exposureMode = ARExposureMode.ContinuousAutoExposure;

	public int videoWidth = 640;
	public int videoHeight = 480;
	public int videoFPS = 60;
	public int numPassSamples = 8;
	public float offsetScale = 1.0f;
	public Color refColor;

	public GameObject backdropObj;

    public Transform blimp;

	public float baseDistance = 40;
    public float initialBlimpDistance;

#if SHOW_DEBUG
	public ARDebugMode debugMode = ARDebugMode.Gray;
#endif

	public ARFeatureTrackingMode trackingMode = ARFeatureTrackingMode.MomentGPU;

	public Shader centroid0XShader;
	public Shader centroid1XShader;
	public Shader centroid0YShader;
	public Shader centroid1YShader;

#if SHOW_DEBUG
	public Shader grayShader;
	public Shader distShader;
#endif

	#endregion

    #region PRIVATE_MEMBERS
	[HideInInspector] // Hides var below
	public Texture2D videoTexture;

	private RenderTexture centTex0X=null;
	private RenderTexture centTex1X=null;
	private RenderTexture centTex0Y=null;
	private RenderTexture centTex1Y=null;

#if SHOW_DEBUG
	private RenderTexture debugRTex=null;
	private Material grayMaterial=null;
	private Material distMaterial=null;
	private Texture2D debugTex=null;
	private GameObject debugObj;
#endif

	private Texture2D centTexX=null;
	private Texture2D centTexY=null;

	private Texture2D curTex; // used to abstract texture data access
	private short[] curTexData= new short[256*256*2];
	GCHandle handlePx;

	private Material cent0XMaterial=null;
	private Material cent1XMaterial=null;
	private Material cent0YMaterial=null;
	private Material cent1YMaterial=null;

	private Vector4 offsets0;
	private Vector4 offsets1;

	private string deviceName;
	private WebCamTexture wct;

    private Pixel oldA, oldB, oldC;
    private Vector3 initialScreenPos;

	private Vector2 trackingCenter;
	private float trackingScale;
	private bool trackingFound=false;
	private Vector3 lastCenter= new Vector3(0,0,0);

#if UNITY_IOS && !UNITY_EDITOR
	private static iPhoneGeneration[] unsupportedDevices =
	{
        iPhoneGeneration.iPad1Gen,
        iPhoneGeneration.iPhone,
        iPhoneGeneration.iPhone3G,
        iPhoneGeneration.iPhone3GS,
        iPhoneGeneration.iPodTouch1Gen,
        iPhoneGeneration.iPodTouch2Gen,
        iPhoneGeneration.iPodTouch3Gen,
		iPhoneGeneration.iPhone4,
        iPhoneGeneration.iPodTouch4Gen
    };
#endif
	/// <summary>
	/// Defines the Pixel struct
	/// </summary>
    struct Pixel
    {
        public int x, y;

        public float DistSquare(Pixel other)
        {
            return (x - other.x) * (x - other.x) + (y - other.y) * (y - other.y);
        }

        public Vector2 toVector2()
        {
            return new Vector2(x, y);
        }
    };

    /// <summary>
    /// Calculates the nearest power of two for a number
    /// </summary>
	private  int nearestPowerOfTwo( float number )
	{
		int n = 1;

		while( n < number )
			n <<= 1;

		return n;
	}

    /// <summary>
    /// Sets up debugging objects
    /// </summary>
	private void DebugSetup()
	{
#if SHOW_DEBUG

#if UNITY_IOS && !UNITY_EDITOR
		//debug.transform.Rotate(270,0,0);
		//debug.transform.localScale=new Vector3(-1,1,1);
#else
		//debugObj.transform.Rotate(90,180,0);
#endif

		debugTex = new Texture2D(videoWidth, videoHeight, TextureFormat.RGBA32, false);
		debugTex.filterMode = FilterMode.Point;

		debugObj = new GameObject("Debug");
        debugObj.AddComponent("GUITexture");
		debugObj.guiTexture.texture = debugTex;
        debugObj.transform.localScale = new Vector3(.06f, .3f, 0f);
        debugObj.transform.position = new Vector3(.06f, .2f, 0f);
		debugObj.guiTexture.pixelInset = new Rect(8.0f,8.0f,videoWidth/2,videoHeight/2);

		//grayShader = Shader.Find("Custom/BGRA2Gray");
		grayMaterial = new Material(grayShader);

		//distShader = Shader.Find ("Custom/ColorDistance");
		distMaterial = new Material(distShader);

		debugRTex = new RenderTexture(videoWidth,videoHeight,-1,RenderTextureFormat.ARGB32);
		debugRTex.filterMode = FilterMode.Point;

		Debug.Log(initialScreenPos);
        Debug.Log(initialBlimpDistance);
		Debug.Log ("Screen:" + Screen.width + "," + Screen.height + " Video:" + videoWidth + "," + videoHeight + " " + videoFPS );
#endif
	}

	private void DebugUpdate()
	{
		if (debugMode==ARDebugMode.Off)				debugObj.SetActive(false);
		else
		{
			if (!debugObj.activeSelf)					debugObj.SetActive(true);

			switch (debugMode)
			{
			case ARDebugMode.KMeans					:	debugObj.guiTexture.texture = debugTex;			break;
			case ARDebugMode.CentroidX				:	debugObj.guiTexture.texture = centTexX;			break;
			case ARDebugMode.Centroid0X				:	debugObj.guiTexture.texture = centTex0X;		break;
			case ARDebugMode.Centroid1X				:	debugObj.guiTexture.texture = centTex1X;		break;
			case ARDebugMode.CentroidY 				:	debugObj.guiTexture.texture = centTexY;			break;
			case ARDebugMode.Centroid0Y 			:	debugObj.guiTexture.texture = centTex0Y;		break;
			case ARDebugMode.Centroid1Y 			:	debugObj.guiTexture.texture = centTex1Y;		break;
			case ARDebugMode.Gray					:	debugObj.guiTexture.texture = debugRTex;		break;
			case ARDebugMode.Distance				:	debugObj.guiTexture.texture = debugRTex;		break;
			default : debugObj.guiTexture.texture = videoTexture;			break;
			}
		}
	}

    /// <summary>
    /// Sets up a Texture2D for video capturing
    /// </summary>
	private void MakeVideoTexture()
	{
#if UNITY_IOS && !UNITY_EDITOR
		videoTexture = new Texture2D( videoWidth, videoHeight, TextureFormat.BGRA32, false);
		backdropObj.guiTexture.texture = videoTexture;
		backdropObj.transform.localScale=new Vector3(1,-1,1);
#else
		videoTexture = new Texture2D( videoWidth, videoHeight, TextureFormat.RGBA32, false);

		WebCamDevice[] cameras = WebCamTexture.devices;
        if ((cameras != null) && (cameras.Length > 0))
        {
			deviceName = cameras[cameras.Length-1].name;
			wct = new WebCamTexture(deviceName, videoWidth, videoHeight, videoFPS);
			backdropObj.guiTexture.texture = wct;
			wct.Play();
			Debug.Log("Created web cam texture");
		}
		else
			Debug.Log("No cameras " + cameras.Length);

#endif // UNITY_IOS/EDITOR
		videoTexture.filterMode = FilterMode.Point;
	}

    /// <summary>
    /// For PC (not iphone): ensure that webcam image is in video texture
    /// </summary>
	void UpdateVideoTexture()
	{
#if UNITY_EDITOR || !UNITY_IOS
		if (wct!=null && wct.isPlaying && wct.didUpdateThisFrame)
		{
			Color[] textureData = wct.GetPixels();

			if (textureData!=null)
			{
	    		videoTexture.SetPixels(textureData);
	    		videoTexture.Apply();
			}
		}
#endif
	}

    /// <summary>
    /// Creates the centroid texture using grayscale image and scaling down
    /// </summary>
	void MakeCentroidTexture()
	{
#if UNITY_IOS && !UNITY_EDITOR
		foreach (iPhoneGeneration gen in unsupportedDevices)
		{
			if (gen == iPhone.generation)
			{
				return;
			}
		}
#endif
		// these have to be manually assigned for the build to include them...
		//centroid0XShader=Shader.Find("Custom/Centroid0X");
		//centroid0YShader=Shader.Find("Custom/Centroid0Y");
		//centroid1XShader=Shader.Find("Custom/Centroid1X");
		//centroid1YShader=Shader.Find("Custom/Centroid1Y");
		cent0XMaterial=new Material(centroid0XShader);
		cent1XMaterial=new Material(centroid1XShader);
		cent0YMaterial=new Material(centroid0YShader);
		cent1YMaterial=new Material(centroid1YShader);

		int passXSamples = numPassSamples;
		int passYSamples = numPassSamples;
		//int totalPassSamples = passXSamples*passYSamples;
		int passWidth = videoWidth/passXSamples;
		int passHeight = videoHeight/passYSamples;
		Debug.Log("Centroid texture axes pass 0:" + passWidth + "," + passHeight);

		offsets0 = new Vector4(offsetScale/videoWidth,offsetScale/videoHeight,offsetScale/videoWidth,offsetScale/videoHeight);
		cent0XMaterial.SetVector ("offsets", offsets0);
		cent0YMaterial.SetVector ("offsets", offsets0);

		centTex0X = new RenderTexture(passWidth,videoHeight,-1,RenderTextureFormat.RGHalf);
		centTex0X.filterMode=FilterMode.Point;
		centTex0Y = new RenderTexture(videoWidth,passHeight,-1,RenderTextureFormat.RGHalf);
		centTex0Y.filterMode=FilterMode.Point;

		offsets1 = new Vector4(offsetScale/passWidth,offsetScale/passHeight,offsetScale/passWidth,offsetScale/passHeight);
		cent1XMaterial.SetVector("offsets", offsets1);
		cent1YMaterial.SetVector("offsets", offsets1);

		passWidth/=passXSamples;
		passHeight/=passYSamples;
		Debug.Log("Centroid texture axes pass 1:" + passWidth + "," + passHeight);

		centTex1X = new RenderTexture(passWidth,videoHeight,-1,RenderTextureFormat.RGHalf);
		centTex1X.filterMode=FilterMode.Point;
		centTex1Y = new RenderTexture(videoWidth,passHeight,-1,RenderTextureFormat.RGHalf);
		centTex1Y.filterMode=FilterMode.Point;

		// converting RenderTexture to texture2D
		centTexX = new Texture2D(passWidth,videoHeight);
		centTexX.filterMode=FilterMode.Point;
		centTexY = new Texture2D(videoWidth,passHeight);
		centTexY.filterMode=FilterMode.Point;
	}

    /// <summary>
    /// Calculates the centroid for the momentGPU tracking method
    /// </summary>
	void CalculateCentroid()
	{
#if UNITY_IOS && !UNITY_EDITOR
		foreach (iPhoneGeneration gen in unsupportedDevices)
		{
			if (gen == iPhone.generation)
			{
				return;
			}
		}
#endif
		if (!centTex0X.IsCreated())	centTex0X.Create();
		if (!centTex0Y.IsCreated())	centTex0Y.Create();
		if (!centTex1X.IsCreated())	centTex1X.Create();
		if (!centTex1Y.IsCreated())	centTex1Y.Create();

		cent0XMaterial.SetColor("_Color",refColor);
		cent0YMaterial.SetColor("_Color",refColor);
		cent1XMaterial.SetColor("_Color",refColor);
		cent1YMaterial.SetColor("_Color",refColor);

		// 2-pass accumulating downsample: sum up all the gray scale values
		Graphics.Blit(videoTexture, centTex0X, cent0XMaterial);
		Graphics.Blit(centTex0X, centTex1X, cent1XMaterial);

		TextureFromRT(centTexX, centTex1X);

		float[] sumsX = new float[videoHeight];
		float[] volX = new float[videoHeight];
		for (int y=0; y<videoHeight; y++)
		{
			sumsX[y]=0.0f;
			volX[y]=0.0f;
			for (int x=0; x<centTexX.width; x++)
			{
				AccumulateFromTex(ref sumsX[y], ref volX[y], x, y);
			}
			sumsX[y]*=videoWidth/centTexX.width;
			volX[y]*=videoWidth/centTexX.width;
		}
		DoneAccumulation(); // release memory

		// compute average x
		float avgX = 0.0f;
		float hitX = 0.0f;
		for (int y=0; y<videoHeight; y++)
		{
			if (volX[y]>0.0f)
			{
				avgX+=sumsX[y]/volX[y];
				hitX+=1.0f;
			}
		}
		avgX/=hitX;

		Graphics.Blit(videoTexture, centTex0Y, cent0YMaterial);

Answer: Graphics.Blit(centTex0Y, centTex1Y, cent1YMaterial);

<think>
In the CalculateCentroid method, after completing the X-pass processing, the code begins the Y-pass by blitting the video texture into centTex0Y using cent0YMaterial. The next logical step mirrors the X-pass: blit centTex0Y into centTex1Y using cent1YMaterial to continue the two-pass downsampling for the Y axis.
</think>
Graphics.Blit(centTex0Y, centTex1Y, cent1YMaterial);


Context:
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.TokenAttributes;
using Lucene.Net.Documents;
using Lucene.Net.Support;
using Lucene.Net.Support.Threading;
using Lucene.Net.Util;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using Console = Lucene.Net.Support.SystemConsole;

namespace Lucene.Net.Index
{
    /*
     * Licensed to the Apache Software Foundation (ASF) under one or more
     * contributor license agreements.  See the NOTICE file distributed with
     * this work for additional information regarding copyright ownership.
     * The ASF licenses this file to You 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.
     */

    using BytesRef = Lucene.Net.Util.BytesRef;
    using Directory = Lucene.Net.Store.Directory;
    using DocIdSetIterator = Lucene.Net.Search.DocIdSetIterator;
    using Document = Documents.Document;
    using Field = Field;
    using IBits = Lucene.Net.Util.IBits;
    using LuceneTestCase = Lucene.Net.Util.LuceneTestCase;
    using PayloadAttribute = Lucene.Net.Analysis.TokenAttributes.PayloadAttribute;
    using TestUtil = Lucene.Net.Util.TestUtil;
    using TextField = TextField;

    [TestFixture]
    public class TestPayloads : LuceneTestCase
    {
        // Simple tests to test the payloads
        [Test]
        public virtual void TestPayload()
        {
            BytesRef payload = new BytesRef("this is a test!");
            Assert.AreEqual(payload.Length, "this is a test!".Length, "Wrong payload length.");

            BytesRef clone = (BytesRef)payload.Clone();
            Assert.AreEqual(payload.Length, clone.Length);
            for (int i = 0; i < payload.Length; i++)
            {
                Assert.AreEqual(payload.Bytes[i + payload.Offset], clone.Bytes[i + clone.Offset]);
            }
        }

        // Tests whether the DocumentWriter and SegmentMerger correctly enable the
        // payload bit in the FieldInfo
        [Test]
        public virtual void TestPayloadFieldBit()
        {
            Directory ram = NewDirectory();
            PayloadAnalyzer analyzer = new PayloadAnalyzer();
            IndexWriter writer = new IndexWriter(ram, NewIndexWriterConfig(TEST_VERSION_CURRENT, analyzer));
            Document d = new Document();
            // this field won't have any payloads
            d.Add(NewTextField("f1", "this field has no payloads", Field.Store.NO));
            // this field will have payloads in all docs, however not for all term positions,
            // so this field is used to check if the DocumentWriter correctly enables the payloads bit
            // even if only some term positions have payloads
            d.Add(NewTextField("f2", "this field has payloads in all docs", Field.Store.NO));
            d.Add(NewTextField("f2", "this field has payloads in all docs NO PAYLOAD", Field.Store.NO));
            // this field is used to verify if the SegmentMerger enables payloads for a field if it has payloads
            // enabled in only some documents
            d.Add(NewTextField("f3", "this field has payloads in some docs", Field.Store.NO));
            // only add payload data for field f2
#pragma warning disable 612, 618
            analyzer.SetPayloadData("f2", "somedata".GetBytes(IOUtils.CHARSET_UTF_8), 0, 1);
#pragma warning restore 612, 618
            writer.AddDocument(d);
            // flush
            writer.Dispose();

            SegmentReader reader = GetOnlySegmentReader(DirectoryReader.Open(ram));
            FieldInfos fi = reader.FieldInfos;
            Assert.IsFalse(fi.FieldInfo("f1").HasPayloads, "Payload field bit should not be set.");
            Assert.IsTrue(fi.FieldInfo("f2").HasPayloads, "Payload field bit should be set.");
            Assert.IsFalse(fi.FieldInfo("f3").HasPayloads, "Payload field bit should not be set.");
            reader.Dispose();

            // now we add another document which has payloads for field f3 and verify if the SegmentMerger
            // enabled payloads for that field
            analyzer = new PayloadAnalyzer(); // Clear payload state for each field
            writer = new IndexWriter(ram, NewIndexWriterConfig(TEST_VERSION_CURRENT, analyzer).SetOpenMode(OpenMode.CREATE));
            d = new Document();
            d.Add(NewTextField("f1", "this field has no payloads", Field.Store.NO));
            d.Add(NewTextField("f2", "this field has payloads in all docs", Field.Store.NO));
            d.Add(NewTextField("f2", "this field has payloads in all docs", Field.Store.NO));
            d.Add(NewTextField("f3", "this field has payloads in some docs", Field.Store.NO));
            // add payload data for field f2 and f3
#pragma warning disable 612, 618
            analyzer.SetPayloadData("f2", "somedata".GetBytes(IOUtils.CHARSET_UTF_8), 0, 1);
            analyzer.SetPayloadData("f3", "somedata".GetBytes(IOUtils.CHARSET_UTF_8), 0, 3);
#pragma warning restore 612, 618
            writer.AddDocument(d);

            // force merge
            writer.ForceMerge(1);
            // flush
            writer.Dispose();

            reader = GetOnlySegmentReader(DirectoryReader.Open(ram));
            fi = reader.FieldInfos;
            Assert.IsFalse(fi.FieldInfo("f1").HasPayloads, "Payload field bit should not be set.");
            Assert.IsTrue(fi.FieldInfo("f2").HasPayloads, "Payload field bit should be set.");
            Assert.IsTrue(fi.FieldInfo("f3").HasPayloads, "Payload field bit should be set.");
            reader.Dispose();
            ram.Dispose();
        }

        // Tests if payloads are correctly stored and loaded using both RamDirectory and FSDirectory
        [Test]
        public virtual void TestPayloadsEncoding()
        {
            Directory dir = NewDirectory();
            PerformTest(dir);
            dir.Dispose();
        }

        // builds an index with payloads in the given Directory and performs
        // different tests to verify the payload encoding
        private void PerformTest(Directory dir)
        {
            PayloadAnalyzer analyzer = new PayloadAnalyzer();
            IndexWriter writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, analyzer).SetOpenMode(OpenMode.CREATE).SetMergePolicy(NewLogMergePolicy()));

            // should be in sync with value in TermInfosWriter
            const int skipInterval = 16;

            const int numTerms = 5;
            const string fieldName = "f1";

            int numDocs = skipInterval + 1;
            // create content for the test documents with just a few terms
            Term[] terms = GenerateTerms(fieldName, numTerms);
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < terms.Length; i++)
            {
                sb.Append(terms[i].Text());
                sb.Append(" ");
            }
            string content = sb.ToString();

            int payloadDataLength = numTerms * numDocs * 2 + numTerms * numDocs * (numDocs - 1) / 2;
            var payloadData = GenerateRandomData(payloadDataLength);

            Document d = new Document();
            d.Add(NewTextField(fieldName, content, Field.Store.NO));
            // add the same document multiple times to have the same payload lengths for all
            // occurrences within two consecutive skip intervals
            int offset = 0;
            for (int i = 0; i < 2 * numDocs; i++)
            {
                analyzer = new PayloadAnalyzer(fieldName, payloadData, offset, 1);
                offset += numTerms;
                writer.AddDocument(d, analyzer);
            }

            // make sure we create more than one segment to test merging
            writer.Commit();

            // now we make sure to have different payload lengths next at the next skip point
            for (int i = 0; i < numDocs; i++)
            {
                analyzer = new PayloadAnalyzer(fieldName, payloadData, offset, i);
                offset += i * numTerms;
                writer.AddDocument(d, analyzer);
            }

            writer.ForceMerge(1);
            // flush
            writer.Dispose();

            /*
             * Verify the index
             * first we test if all payloads are stored correctly
             */
            IndexReader reader = DirectoryReader.Open(dir);

            var verifyPayloadData = new byte[payloadDataLength];
            offset = 0;
            var tps = new DocsAndPositionsEnum[numTerms];
            for (int i = 0; i < numTerms; i++)
            {
                tps[i] = MultiFields.GetTermPositionsEnum(reader, MultiFields.GetLiveDocs(reader), terms[i].Field, new BytesRef(terms[i].Text()));
            }

            while (tps[0].NextDoc() != DocIdSetIterator.NO_MORE_DOCS)
            {
                for (int i = 1; i < numTerms; i++)
                {
                    tps[i].NextDoc();
                }
                int freq = tps[0].Freq;

                for (int i = 0; i < freq; i++)
                {
                    for (int j = 0; j < numTerms; j++)
                    {
                        tps[j].NextPosition();
                        BytesRef br = tps[j].GetPayload();
                        if (br != null)
                        {
                            Array.Copy(br.Bytes, br.Offset, verifyPayloadData, offset, br.Length);
                            offset += br.Length;
                        }
                    }
                }
            }

            AssertByteArrayEquals(payloadData, verifyPayloadData);

            /*
             *  test lazy skipping
             */
            DocsAndPositionsEnum tp = MultiFields.GetTermPositionsEnum(reader, MultiFields.GetLiveDocs(reader), terms[0].Field, new BytesRef(terms[0].Text()));
            tp.NextDoc();
            tp.NextPosition();
            // NOTE: prior rev of this test was failing to first
            // call next here:
            tp.NextDoc();
            // now we don't read this payload
            tp.NextPosition();
            BytesRef payload = tp.GetPayload();
            Assert.AreEqual(1, payload.Length, "Wrong payload length.");
            Assert.AreEqual(payload.Bytes[payload.Offset], payloadData[numTerms]);
            tp.NextDoc();
            tp.NextPosition();

            // we don't read this payload and skip to a different document
            tp.Advance(5);
            tp.NextPosition();
            payload = tp.GetPayload();
            Assert.AreEqual(1, payload.Length, "Wrong payload length.");
            Assert.AreEqual(payload.Bytes[payload.Offset], payloadData[5 * numTerms]);

            /*
             * Test different lengths at skip points
             */
            tp = MultiFields.GetTermPositionsEnum(reader, MultiFields.GetLiveDocs(reader), terms[1].Field, new BytesRef(terms[1].Text()));
            tp.NextDoc();
            tp.NextPosition();
            Assert.AreEqual(1, tp.GetPayload().Length, "Wrong payload length.");
            tp.Advance(skipInterval - 1);
            tp.NextPosition();
            Assert.AreEqual(1, tp.GetPayload().Length, "Wrong payload length.");
            tp.Advance(2 * skipInterval - 1);
            tp.NextPosition();
            Assert.AreEqual(1, tp.GetPayload().Length, "Wrong payload length.");
            tp.Advance(3 * skipInterval - 1);
            tp.NextPosition();
            Assert.AreEqual(3 * skipInterval - 2 * numDocs - 1, tp.GetPayload().Length, "Wrong payload length.");

            reader.Dispose();

            // test long payload
            analyzer = new PayloadAnalyzer();
            writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, analyzer).SetOpenMode(OpenMode.CREATE));
            string singleTerm = "lucene";

            d = new Document();
            d.Add(NewTextField(fieldName, singleTerm, Field.Store.NO));
            // add a payload whose length is greater than the buffer size of BufferedIndexOutput
            payloadData = GenerateRandomData(2000);
            analyzer.SetPayloadData(fieldName, payloadData, 100, 1500);
            writer.AddDocument(d);

            writer.ForceMerge(1);
            // flush
            writer.Dispose();

            reader = DirectoryReader.Open(dir);
            tp = MultiFields.GetTermPositionsEnum(reader, MultiFields.GetLiveDocs(reader), fieldName, new BytesRef(singleTerm));
            tp.NextDoc();
            tp.NextPosition();

            BytesRef bref = tp.GetPayload();
            verifyPayloadData = new byte[bref.Length];
            var portion = new byte[1500];
            Array.Copy(payloadData, 100, portion, 0, 1500);

            AssertByteArrayEquals(portion, bref.Bytes, bref.Offset, bref.Length);
            reader.Dispose();
        }

#pragma warning disable 612, 618
        internal static readonly Encoding Utf8 = IOUtils.CHARSET_UTF_8;
#pragma warning restore 612, 618

        private void GenerateRandomData(byte[] data)
        {
            // this test needs the random data to be valid unicode
            string s = TestUtil.RandomFixedByteLengthUnicodeString(Random(), data.Length);
            var b = s.GetBytes(Utf8);
            Debug.Assert(b.Length == data.Length);
            System.Buffer.BlockCopy(b, 0, data, 0, b.Length);
        }

        private byte[] GenerateRandomData(int n)
        {
            var data = new byte[n];
            GenerateRandomData(data);
            return data;
        }

        private Term[] GenerateTerms(string fieldName, int n)
        {
            int maxDigits = (int)(Math.Log(n) / Math.Log(10));
            Term[] terms = new Term[n];
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < n; i++)
            {
                sb.Length = 0;
                sb.Append("t");
                int zeros = maxDigits - (int)(Math.Log(i) / Math.Log(10));
                for (int j = 0; j < zeros; j++)
                {
                    sb.Append("0");
                }
                sb.Append(i);
                terms[i] = new Term(fieldName, sb.ToString());
            }
            return terms;
        }

        internal virtual void AssertByteArrayEquals(byte[] b1, byte[] b2)
        {
            if (b1.Length != b2.Length)
            {
                Assert.Fail("Byte arrays have different lengths: " + b1.Length + ", " + b2.Length);
            }

            for (int i = 0; i < b1.Length; i++)
            {
                if (b1[i] != b2[i])
                {
                    Assert.Fail("Byte arrays different at index " + i + ": " + b1[i] + ", " + b2[i]);
                }
            }
        }

        internal virtual void AssertByteArrayEquals(byte[] b1, byte[] b2, int b2offset, int b2length)
        {
            if (b1.Length != b2length)
            {
                Assert.Fail("Byte arrays have different lengths: " + b1.Length + ", " + b2length);
            }

            for (int i = 0; i < b1.Length; i++)
            {
                if (b1[i] != b2[b2offset + i])
                {
                    Assert.Fail("Byte arrays different at index " + i + ": " + b1[i] + ", " + b2[b2offset + i]);
                }
            }
        }

        /// <summary>
        /// this Analyzer uses an WhitespaceTokenizer and PayloadFilter.
        /// </summary>
        private class PayloadAnalyzer : Analyzer
        {
            internal readonly IDictionary<string, PayloadData> FieldToData = new Dictionary<string, PayloadData>();

            public PayloadAnalyzer()
                : base(PER_FIELD_REUSE_STRATEGY)
            {
            }

            public PayloadAnalyzer(string field, byte[] data, int offset, int length)
                : base(PER_FIELD_REUSE_STRATEGY)
            {
                SetPayloadData(field, data, offset, length);
            }

            internal virtual void SetPayloadData(string field, byte[] data, int offset, int length)
            {
                FieldToData[field] = new PayloadData(data, offset, length);
            }

            protected internal override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
            {
                PayloadData payload;
                FieldToData.TryGetValue(fieldName, out payload);
                Tokenizer ts = new MockTokenizer(reader, MockTokenizer.WHITESPACE, false);
                TokenStream tokenStream = (payload != null) ? (TokenStream)new PayloadFilter(ts, payload.Data, payload.Offset, payload.Length) : ts;
                return new TokenStreamComponents(ts, tokenStream);
            }

            internal class PayloadData
            {
                internal byte[] Data;
                internal int Offset;
                internal int Length;

                internal PayloadData(byte[] data, int offset, int length)
                {
                    this.Data = data;
                    this.Offset = offset;
                    this.Length = length;
                }
            }
        }

        /// <summary>
        /// this Filter adds payloads to the tokens.
        /// </summary>
        private class PayloadFilter : TokenFilter
        {
            internal byte[] Data;
            internal int Length;
            internal int Offset;
            internal int StartOffset;
            internal IPayloadAttribute PayloadAtt;
            internal ICharTermAttribute TermAttribute;

            public PayloadFilter(TokenStream @in, byte[] data, int offset, int length)
                : base(@in)
            {
                this.Data = data;
                this.Length = length;
                this.Offset = offset;
                this.StartOffset = offset;
                PayloadAtt = AddAttribute<IPayloadAttribute>();
                TermAttribute = AddAttribute<ICharTermAttribute>();
            }

            public sealed override bool IncrementToken()
            {
                bool hasNext = m_input.IncrementToken();
                if (!hasNext)
                {
                    return false;
                }

                // Some values of the same field are to have payloads and others not
                if (Offset + Length <= Data.Length && !TermAttribute.ToString().EndsWith("NO PAYLOAD", StringComparison.Ordinal))
                {
                    BytesRef p = new BytesRef(Data, Offset, Length);
                    PayloadAtt.Payload = p;
                    Offset += Length;
                }
                else
                {
                    PayloadAtt.Payload = null;
                }

                return true;
            }

            public override void Reset()
            {
                base.Reset();
                this.Offset = StartOffset;
            }
        }

        [Test]
        public virtual void TestThreadSafety()
        {
            const int numThreads = 5;
            int numDocs = AtLeast(50);
            ByteArrayPool pool = new ByteArrayPool(numThreads, 5);

            Directory dir = NewDirectory();
            IndexWriter writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())));
            const string field = "test";

            ThreadClass[] ingesters = new ThreadClass[numThreads];
            for (int i = 0; i < numThreads; i++)
            {
                ingesters[i] = new ThreadAnonymousInnerClassHelper(this, numDocs, pool, writer, field);
                ingesters[i].Start();
            }

            for (int i = 0; i < numThreads; i++)
            {
                ingesters[i].Join();
            }
            writer.Dispose();
            IndexReader reader = DirectoryReader.Open(dir);
            TermsEnum terms = MultiFields.GetFields(reader).GetTerms(field).GetIterator(null);
            IBits liveDocs = MultiFields.GetLiveDocs(reader);
            DocsAndPositionsEnum tp = null;
            while (terms.Next() != null)
            {
                string termText = terms.Term.Utf8ToString();
                tp = terms.DocsAndPositions(liveDocs, tp);
                while (tp.NextDoc() != DocIdSetIterator.NO_MORE_DOCS)
                {
                    int freq = tp.Freq;
                    for (int i = 0; i < freq; i++)
                    {
                        tp.NextPosition();
                        BytesRef payload = tp.GetPayload();
                        Assert.AreEqual(termText, payload.Utf8ToString());
                    }
                }
            }
            reader.Dispose();
            dir.Dispose();
            Assert.AreEqual(pool.Count, numThreads);
        }

Answer: private class ThreadAnonymousInnerClassHelper : ThreadClass

<think>
The TestThreadSafety method ends with its closing brace. In the original Lucene test file, the next code defines the inner helper class used for threading. Therefore the next line is the declaration of that inner class.
</think>
private class ThreadAnonymousInnerClassHelper : ThreadClass