/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package org.openjdk.bench.vm.compiler;

import java.util.Arrays;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.infra.Blackhole;

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

/**
 * Related to JDK-8262067
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class SuperWordPlus {

  private float[] f1;
  private float[] f2;
  private float[] f3;
  private float[] f4;

  ArrayHolder a1;
  ArrayHolder a2;
  ArrayHolder a3;
  ArrayHolder a4;
  VectorPlus pf;
  
  ArrayHolder[]  inputs;

  @Param({"12", "147" /*, "3333" */})
  public int size;

  float[] randomFArray() {
    float[] srcArray;
    srcArray = new float[size];
    for (int i = 0; i < size; i++) {
      srcArray[i] = (float) 5.555;
    }
    return srcArray;
  }


  @Setup(Level.Iteration)
  public void setup() {
    f1 = randomFArray();
    f2 = randomFArray();
    f3 = randomFArray();
    f4 = randomFArray();

    a1 = new ArrayFloatHolder(f1);
    a2 = new ArrayFloatHolder(f2);
    a3 = new ArrayFloatHolder(f3);
    a4 = new ArrayFloatHolder(f4);
    
    inputs = new ArrayHolder[3];
    inputs[0] = a2;
    inputs[1] = a3;
    inputs[2] = a4;


    pf = new VectorPlus(new FPlus());

  }

  @Benchmark
  public float[] testAdd0() {
    float[] srcArray =  f1;
    float[] dstArray =  f2;
    for (int i = 0; i <  size ; ++i) {
      dstArray[i] += srcArray[i];
    }
    return dstArray;
  }

//  @Benchmark
//  public float[] testAdd1() {
//    float[] srcArray =  f1;
//    float[] dstArray =  f2;
//    
//    for (int i = 0; i < srcArray.length ; ++i) {
//      dstArray[i] += srcArray[i];
//    }
//    return dstArray;
//  }

  interface IVector {
    public Object getArray();
    public int size();
  }

  interface IOp {
    public void plus(IVector one, IVector two);
  }

  static class FPlus implements IOp {

    public void plus(IVector one, IVector two) {
      float[] dstArray = (float[]) one.getArray();
      float[] srcArray = (float[]) two.getArray();
      int srcLen = two.size();
      if (two.size() > one.size()) {

      } else if (two.size() == 0) {

      } else {
        for (int i = 0; i < srcLen; ++i) {
          dstArray[i] += srcArray[i];
        }
      }
    }
  }


  abstract static class ArrayHolder implements IVector {

    private Object dstArray;

    public ArrayHolder(Object src) {
      dstArray = src;
    }

    public Object getArray() {
      return dstArray;
    }
    public abstract void plus(ArrayHolder other);
    
  }

  static class ArrayFloatHolder extends ArrayHolder implements IVector {
    IOp plus;
    
    public ArrayFloatHolder(float[] src) {
      super(src);
      plus = new FPlus();
    }

    public float[] getArray() {
      return (float[]) super.getArray();
    }

    public int size() {
      return ((float[]) super.getArray()).length;
    }

    public void plus(ArrayHolder other) {
      plus.plus(this, other);
    }
  }


  static class VectorPlus {

    IOp p;

    public VectorPlus(IOp x) {
      p = x;
    }

    public void plus(IVector one, IVector two) {
      p.plus(one, two);
    }
  }

  private Object testAdd2Impl(float[] dstArray) {
     ArrayFloatHolder   aftmp = new ArrayFloatHolder(dstArray);

    pf.plus(aftmp, a2);
    return  a1.getArray();
  }

  @Benchmark
  public Object testAdd2() {
    return testAdd2Impl(f2);
  }

  @Benchmark
  public void testAdd3(Blackhole bh) {
    pf.plus(a1, a2);
    bh.consume(pf);
  }

  @Benchmark
  public void testAdd4(Blackhole bh) {
    for (ArrayHolder aa : inputs) {
      a1.plus( aa);
      bh.consume(a1);
    }
  }
}
