/**
 * Copyright (c) 2008-2021 Bird Dog Games, Inc.
 *
 * This file is part of Ardor3D.
 *
 * Ardor3D is free software: you can redistribute it and/or modify it
 * under the terms of its license which may be found in the accompanying
 * LICENSE file or at <https://git.io/fjRmv>.
 */

package com.ardor3d.scenegraph.shape;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;

import com.ardor3d.buffer.BufferUtils;
import com.ardor3d.buffer.FloatBufferData;
import com.ardor3d.math.Vector3;
import com.ardor3d.renderer.IndexMode;
import com.ardor3d.scenegraph.Mesh;
import com.ardor3d.util.export.InputCapsule;
import com.ardor3d.util.export.OutputCapsule;

public class StripBox extends Mesh implements Cloneable {

  public double _xExtent, _yExtent, _zExtent;

  public final Vector3 _center = new Vector3(0f, 0f, 0f);

  /**
   * instantiates a new <code>StripBox</code> object. All information must be applies later. For
   * internal usage only
   */
  public StripBox() {
    super("temp");
  }

  /**
   * Constructor instantiates a new <code>StripBox</code> object. Center and vertex information must
   * be supplied later.
   *
   * @param name
   *          the name of the scene element. This is required for identification and comparison
   *          purposes.
   */
  public StripBox(final String name) {
    super(name);
  }

  /**
   * Constructor instantiates a new <code>StripBox</code> object. The minimum and maximum point are
   * provided. These two points define the shape and size of the box, but not it's orientation or
   * position. You should use the <code>setTranslation</code> and <code>setLocalRotation</code> for
   * those attributes.
   *
   * @param name
   *          the name of the scene element. This is required for identification and comparison
   *          purposes.
   * @param min
   *          the minimum point that defines the box.
   * @param max
   *          the maximum point that defines the box.
   */
  public StripBox(final String name, final Vector3 min, final Vector3 max) {
    super(name);
    setData(min, max);
  }

  /**
   * Constructs a new box. The box has the given center and extends in the x, y, and z out from the
   * center (+ and -) by the given amounts. So, for example, a box with extent of .5 would be the unit
   * cube.
   *
   * @param name
   *          Name of the box.
   * @param center
   *          Center of the box.
   * @param xExtent
   *          x extent of the box, in both directions.
   * @param yExtent
   *          y extent of the box, in both directions.
   * @param zExtent
   *          z extent of the box, in both directions.
   */
  public StripBox(final String name, final Vector3 center, final double xExtent, final double yExtent,
    final double zExtent) {
    super(name);
    setData(center, xExtent, yExtent, zExtent);
  }

  /**
   * Changes the data of the box so that the two opposite corners are minPoint and maxPoint. The other
   * corners are created from those two poitns. If update buffers is flagged as true, the
   * vertex/normal/texture/color/index buffers are updated when the data is changed.
   *
   * @param minPoint
   *          The new minPoint of the box.
   * @param maxPoint
   *          The new maxPoint of the box.
   */
  public void setData(final Vector3 minPoint, final Vector3 maxPoint) {
    _center.set(maxPoint).addLocal(minPoint).multiplyLocal(0.5f);

    final double x = maxPoint.getX() - _center.getX();
    final double y = maxPoint.getY() - _center.getY();
    final double z = maxPoint.getZ() - _center.getZ();
    setData(_center, x, y, z);
  }

  /**
   * Changes the data of the box so that its center is <code>center</code> and it extends in the x, y,
   * and z directions by the given extent. Note that the actual sides will be 2x the given extent
   * values because the box extends in + & - from the center for each extent.
   *
   * @param center
   *          The center of the box.
   * @param xExtent
   *          x extent of the box, in both directions.
   * @param yExtent
   *          y extent of the box, in both directions.
   * @param zExtent
   *          z extent of the box, in both directions.
   */
  public void setData(final Vector3 center, final double xExtent, final double yExtent, final double zExtent) {
    if (center != null) {
      _center.set(center);
    }

    _xExtent = xExtent;
    _yExtent = yExtent;
    _zExtent = zExtent;

    setVertexData();
    setNormalData();
    setTextureData();
    setIndexData();

  }

  /**
   *
   * <code>setVertexData</code> sets the vertex positions that define the box. These eight points are
   * determined from the minimum and maximum point.
   *
   */
  private void setVertexData() {
    _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(_meshData.getVertexBuffer(), 8));
    final Vector3[] vert = computeVertices(); // returns 8
    _meshData.getVertexBuffer().clear();
    _meshData.getVertexBuffer().put(vert[0].getXf()).put(vert[0].getYf()).put(vert[0].getZf());
    _meshData.getVertexBuffer().put(vert[1].getXf()).put(vert[1].getYf()).put(vert[1].getZf());
    _meshData.getVertexBuffer().put(vert[2].getXf()).put(vert[2].getYf()).put(vert[2].getZf());
    _meshData.getVertexBuffer().put(vert[3].getXf()).put(vert[3].getYf()).put(vert[3].getZf());
    _meshData.getVertexBuffer().put(vert[4].getXf()).put(vert[4].getYf()).put(vert[4].getZf());
    _meshData.getVertexBuffer().put(vert[5].getXf()).put(vert[5].getYf()).put(vert[5].getZf());
    _meshData.getVertexBuffer().put(vert[6].getXf()).put(vert[6].getYf()).put(vert[6].getZf());
    _meshData.getVertexBuffer().put(vert[7].getXf()).put(vert[7].getYf()).put(vert[7].getZf());
  }

  /**
   *
   * <code>setNormalData</code> sets the normals of each of the box's planes.
   *
   *
   */
  private void setNormalData() {
    final Vector3[] vert = computeVertices(); // returns 8
    _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(_meshData.getNormalBuffer(), 8));
    final Vector3 norm = new Vector3();

    _meshData.getNormalBuffer().clear();
    for (int i = 0; i < 8; i++) {
      norm.set(vert[i]).normalizeLocal();
      _meshData.getNormalBuffer().put(norm.getXf()).put(norm.getYf()).put(norm.getZf());
    }
  }

  /**
   *
   * <code>setTextureData</code> sets the points that define the texture of the box. It's a one-to-one
   * ratio, where each plane of the box has it's own copy of the texture. That is, the texture is
   * repeated one time for each six faces.
   *
   */
  private void setTextureData() {
    if (_meshData.getTextureCoords(0) == null) {
      _meshData.setTextureCoords(new FloatBufferData(BufferUtils.createVector2Buffer(8), 2), 0);
      final FloatBuffer tex = _meshData.getTextureCoords(0).getBuffer();
      tex.put(1).put(0); // 0
      tex.put(0).put(0); // 1
      tex.put(0).put(1); // 2
      tex.put(1).put(1); // 3
      tex.put(1).put(0); // 4
      tex.put(0).put(0); // 5
      tex.put(1).put(1); // 6
      tex.put(0).put(1); // 7
    }
  }

  /**
   *
   * <code>setIndexData</code> sets the indices into the list of vertices, defining all triangles that
   * constitute the box.
   *
   */
  private void setIndexData() {
    _meshData.setIndexMode(IndexMode.TriangleStrip);
    if (_meshData.getIndices() == null) {
      final byte[] indices = new byte[] {2, 3, 6, 7, 5, 3, 0, 2, 1, 6, 4, 5, 1, 0};
      final ByteBuffer buf = BufferUtils.createByteBuffer(indices.length);
      buf.put(indices);
      buf.rewind();
      _meshData.setIndexBuffer(buf);
    }
  }

  /**
   * <code>clone</code> creates a new StripBox object containing the same data as this one.
   *
   * @return the new StripBox
   */
  @Override
  public StripBox clone() {
    return new StripBox(getName() + "_clone", _center.clone(), _xExtent, _yExtent, _zExtent);
  }

  /**
   *
   * @return a size 8 array of Vectors representing the 8 points of the box.
   */
  public Vector3[] computeVertices() {

    final Vector3 akEAxis[] = {Vector3.UNIT_X.multiply(_xExtent, Vector3.fetchTempInstance()),
        Vector3.UNIT_Y.multiply(_yExtent, Vector3.fetchTempInstance()),
        Vector3.UNIT_Z.multiply(_zExtent, Vector3.fetchTempInstance())};

    final Vector3 rVal[] = new Vector3[8];
    rVal[0] = _center.subtract(akEAxis[0], new Vector3()).subtractLocal(akEAxis[1]).subtractLocal(akEAxis[2]);
    rVal[1] = _center.add(akEAxis[0], new Vector3()).subtractLocal(akEAxis[1]).subtractLocal(akEAxis[2]);
    rVal[2] = _center.add(akEAxis[0], new Vector3()).addLocal(akEAxis[1]).subtractLocal(akEAxis[2]);
    rVal[3] = _center.subtract(akEAxis[0], new Vector3()).addLocal(akEAxis[1]).subtractLocal(akEAxis[2]);
    rVal[4] = _center.add(akEAxis[0], new Vector3()).subtractLocal(akEAxis[1]).addLocal(akEAxis[2]);
    rVal[5] = _center.subtract(akEAxis[0], new Vector3()).subtractLocal(akEAxis[1]).addLocal(akEAxis[2]);
    rVal[6] = _center.add(akEAxis[0], new Vector3()).addLocal(akEAxis[1]).addLocal(akEAxis[2]);
    rVal[7] = _center.subtract(akEAxis[0], new Vector3()).addLocal(akEAxis[1]).addLocal(akEAxis[2]);
    for (final Vector3 axis : akEAxis) {
      Vector3.releaseTempInstance(axis);
    }
    return rVal;
  }

  /**
   * Returns the current center of the box.
   *
   * @return The box's center.
   */
  public Vector3 getCenter() { return _center; }

  @Override
  public void write(final OutputCapsule capsule) throws IOException {
    super.write(capsule);
    capsule.write(_xExtent, "xExtent", 0);
    capsule.write(_yExtent, "yExtent", 0);
    capsule.write(_zExtent, "zExtent", 0);
    capsule.write(_center, "center", (Vector3) Vector3.ZERO);

  }

  @Override
  public void read(final InputCapsule capsule) throws IOException {
    super.read(capsule);
    _xExtent = capsule.readDouble("xExtent", 0);
    _yExtent = capsule.readDouble("yExtent", 0);
    _zExtent = capsule.readDouble("zExtent", 0);
    _center.set(capsule.readSavable("center", (Vector3) Vector3.ZERO));
  }
}
