/*
 * Copyright © 2017 <code@io7m.com> http://io7m.com
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package com.io7m.jtensors.generators;

import java.security.SecureRandom;
import java.util.Objects;
import com.io7m.jtensors.core.unparameterized.matrices.Matrix4x4F;
import net.java.quickcheck.Generator;
import net.java.quickcheck.generator.PrimitiveGenerators;

/**
 * A matrix generator.
 */

public final class Matrix4x4FGenerator implements Generator<Matrix4x4F>
{
  private final Generator<Double> gen;

  /**
   * Construct a generator.
   *
   * @param in_gen A component generator
   */

  public Matrix4x4FGenerator(
    final Generator<Double> in_gen)
  {
    this.gen = Objects.requireNonNull(in_gen, "Generator");
  }

  /**
   * Create a generator initialized with a default component generator.
   *
   * @return A generator
   */

  public static Generator<Matrix4x4F> create()
  {
    return new Matrix4x4FGenerator(PrimitiveGenerators.doubles(
      GeneratorConstants.BOUND_LARGE_FLOAT_LOWER,
      GeneratorConstants.BOUND_LARGE_FLOAT_UPPER
    ));
  }

  /**
   * Create a generator initialized with a default component generator that only
   * produces values in the range {@code [-1.0, 1.0]}.
   *
   * @return A generator
   */

  public static Generator<Matrix4x4F> createNormal()
  {
    return new Matrix4x4FGenerator(PrimitiveGenerators.doubles(
      GeneratorConstants.BOUND_NORMAL_FLOAT_LOWER,
      GeneratorConstants.BOUND_NORMAL_FLOAT_UPPER
    ));
  }

  /**
   * Create a generator initialized with a default component generator that only
   * produces orthogonal matrices.
   *
   * @return A generator
   */

  public static Generator<Matrix4x4F> createOrthogonal()
  {
    return () -> {
      final SecureRandom rng = new SecureRandom();

      final float r0c0 = 1.0F;
      final float r0c1 = 0.0F;
      final float r0c2 = 0.0F;
      final float r0c3 = rng.nextFloat();

      final float r1c0 = 0.0F;
      final float r1c1 = 1.0F;
      final float r1c2 = 0.0F;
      final float r1c3 = rng.nextFloat();

      final float r2c0 = 0.0F;
      final float r2c1 = 0.0F;
      final float r2c2 = 1.0F;
      final float r2c3 = rng.nextFloat();

      final float r3c0 = 0.0F;
      final float r3c1 = 0.0F;
      final float r3c2 = 0.0F;
      final float r3c3 = 1.0F;

      return Matrix4x4F.of(
        r0c0, r0c1, r0c2, r0c3,
        r1c0, r1c1, r1c2, r1c3,
        r2c0, r2c1, r2c2, r2c3,
        r3c0, r3c1, r3c2, r3c3);
    };
  }

  @Override
  public Matrix4x4F next()
  {
    return Matrix4x4F.of(
      this.gen.next().floatValue(),
      this.gen.next().floatValue(),
      this.gen.next().floatValue(),
      this.gen.next().floatValue(),
      this.gen.next().floatValue(),
      this.gen.next().floatValue(),
      this.gen.next().floatValue(),
      this.gen.next().floatValue(),
      this.gen.next().floatValue(),
      this.gen.next().floatValue(),
      this.gen.next().floatValue(),
      this.gen.next().floatValue(),
      this.gen.next().floatValue(),
      this.gen.next().floatValue(),
      this.gen.next().floatValue(),
      this.gen.next().floatValue());
  }
}
