package com.jme3.scene.plugins.blender.constraints.definitions;

import com.jme3.math.FastMath;
import com.jme3.math.Transform;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
import com.jme3.scene.plugins.blender.file.Structure;

/**
 * This class represents 'Rot limit' constraint type in blender.
 * 
 * @author Marcin Roguski (Kaelthas)
 */
/* package */class ConstraintDefinitionRotLimit extends ConstraintDefinition {
    private static final int    LIMIT_XROT = 0x01;
    private static final int    LIMIT_YROT = 0x02;
    private static final int    LIMIT_ZROT = 0x04;

    private transient float[][] limits     = new float[3][2];
    private transient float[]   angles     = new float[3];

    public ConstraintDefinitionRotLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
        super(constraintData, ownerOMA, blenderContext);
        if (blenderContext.getBlenderKey().isFixUpAxis()) {
            limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
            limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
            limits[2][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue();
            limits[2][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue();
            limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
            limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();

            // swapping Y and X limits flag in the bitwise flag
            int limitY = flag & LIMIT_YROT;
            int limitZ = flag & LIMIT_ZROT;
            flag &= LIMIT_XROT;// clear the other flags to swap them
            flag |= limitY << 1;
            flag |= limitZ >> 1;
        } else {
            limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
            limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
            limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue();
            limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue();
            limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
            limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
        }

        // until blender 2.49 the rotations values were stored in degrees
        if (blenderContext.getBlenderVersion() <= 249) {
            for (int i = 0; i < 3; ++i) {
                limits[i][0] *= FastMath.DEG_TO_RAD;
                limits[i][1] *= FastMath.DEG_TO_RAD;
            }
        }

        // make sure that the limits are always in range [0, 2PI)
        // TODO: left it here because it is essential to make sure all cases
        // work poperly
        // but will do it a little bit later ;)
        /*
         * for (int i = 0; i < 3; ++i) { for (int j = 0; j < 2; ++j) { int
         * multFactor = (int)Math.abs(limits[i][j] / FastMath.TWO_PI) ; if
         * (limits[i][j] < 0) { limits[i][j] += FastMath.TWO_PI * (multFactor +
         * 1); } else { limits[i][j] -= FastMath.TWO_PI * multFactor; } } //make
         * sure the lower limit is not greater than the upper one
         * if(limits[i][0] > limits[i][1]) { float temp = limits[i][0];
         * limits[i][0] = limits[i][1]; limits[i][1] = temp; } }
         */

        trackToBeChanged = (flag & (LIMIT_XROT | LIMIT_YROT | LIMIT_ZROT)) != 0;
    }

    @Override
    public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
        if (influence == 0 || !trackToBeChanged) {
            return;
        }
        Transform ownerTransform = this.getOwnerTransform(ownerSpace);

        ownerTransform.getRotation().toAngles(angles);
        // make sure that the rotations are always in range [0, 2PI)
        // TODO: same comment as in constructor
        /*
         * for (int i = 0; i < 3; ++i) { int multFactor =
         * (int)Math.abs(angles[i] / FastMath.TWO_PI) ; if(angles[i] < 0) {
         * angles[i] += FastMath.TWO_PI * (multFactor + 1); } else { angles[i]
         * -= FastMath.TWO_PI * multFactor; } }
         */
        if ((flag & LIMIT_XROT) != 0) {
            float difference = 0.0f;
            if (angles[0] < limits[0][0]) {
                difference = (angles[0] - limits[0][0]) * influence;
            } else if (angles[0] > limits[0][1]) {
                difference = (angles[0] - limits[0][1]) * influence;
            }
            angles[0] -= difference;
        }
        if ((flag & LIMIT_YROT) != 0) {
            float difference = 0.0f;
            if (angles[1] < limits[1][0]) {
                difference = (angles[1] - limits[1][0]) * influence;
            } else if (angles[1] > limits[1][1]) {
                difference = (angles[1] - limits[1][1]) * influence;
            }
            angles[1] -= difference;
        }
        if ((flag & LIMIT_ZROT) != 0) {
            float difference = 0.0f;
            if (angles[2] < limits[2][0]) {
                difference = (angles[2] - limits[2][0]) * influence;
            } else if (angles[2] > limits[2][1]) {
                difference = (angles[2] - limits[2][1]) * influence;
            }
            angles[2] -= difference;
        }
        ownerTransform.getRotation().fromAngles(angles);

        this.applyOwnerTransform(ownerTransform, ownerSpace);
    }

    @Override
    public String getConstraintTypeName() {
        return "Limit rotation";
    }

    @Override
    public boolean isTargetRequired() {
        return false;
    }
}
