/*
 * Copyright (C) 2009 Klaus Reimer <k@ailis.de>
 * See LICENSE.txt file for licensing information.
 */

package de.ailis.gramath;


import static de.ailis.gramath.support.Assert.assertArrayEquals;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.FloatBuffer;
import java.util.Arrays;

import org.junit.Test;


/**
 * Tests the ImmutableMatrix3f class.
 *
 * @author Klaus Reimer (k@ailis.de)
 * @version $Revision$
 */

public class ImmutableMatrix3fTest
{
    /** A test pattern for testing matrix elements */
    private static final float[] TEST_PATTERN = new float[] { 1, 2, 3, 4, 5, 6,
        7, 8, 9 };

    /** Another test pattern for testing matrix elements */
    private static final float[] TEST_PATTERN2 = new float[] { 10, 11, 12, 13,
        14, 15, 16, 17, 18 };

    /** Array with both test patterns in it */
    private static final float[] TEST_PATTERNS = new float[] { 1, 2, 3, 4, 5,
        6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };

    /**
     * Tests the array constructor
     */

    @Test
    public void testArrayConstructor()
    {
        final ImmutableMatrix3f matrix = new ImmutableMatrix3f(TEST_PATTERN);
        assertNotSame(TEST_PATTERN, matrix.m);
        assertTrue(Arrays.equals(TEST_PATTERN, matrix.m));
    }


    /**
     * Tests the array constructor with custom start index.
     */

    @Test
    public void testArrayIndexConstructor()
    {
        final ImmutableMatrix3f matrix = new ImmutableMatrix3f(TEST_PATTERNS,
            9);
        assertNotSame(TEST_PATTERN2, matrix.m);
        assertTrue(Arrays.equals(TEST_PATTERN2, matrix.m));
    }


    /**
     * Tests the constructor with Matrix4f argument.
     */

    @Test
    public void testMatrix4fConstructor()
    {
        final ImmutableMatrix4f matrix1 = new ImmutableMatrix4f(0, 1, 2, 3, 4,
            5,
            6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
        final ImmutableMatrix3f matrix2 = new ImmutableMatrix3f(matrix1);
        assertNotSame(matrix1.m, matrix2.m);
        assertArrayEquals(new double[] { 0, 1, 2, 4, 5, 6, 8, 9, 10 }, matrix2.m, 0.001);
    }


    /**
     * Tests the constructor with Matrix3d argument.
     */

    @Test
    public void testMatrix4dConstructor()
    {
        final ImmutableMatrix4d matrix1 = new ImmutableMatrix4d(0, 1, 2, 3, 4,
            5,
            6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
        final ImmutableMatrix3f matrix2 = new ImmutableMatrix3f(matrix1);
        assertNotSame(matrix1.m, matrix2.m);
        assertArrayEquals(new double[] { 0, 1, 2, 4, 5, 6, 8, 9, 10 }, matrix2.m, 0.001);
    }


    /**
     * Tests the constructor with Matrix3d argument.
     */

    @Test
    public void testMatrix3dConstructor()
    {
        final ImmutableMatrix3d matrix1 = new ImmutableMatrix3d(0, 1, 2, 3, 4,
            5,
            6, 7, 8, 9);
        final ImmutableMatrix3f matrix2 = new ImmutableMatrix3f(matrix1);
        assertNotSame(matrix1.m, matrix2.m);
        assertArrayEquals(matrix1.m, matrix2.m, 0.001);
    }


    /**
     * Tests the constructor with Matrix3f argument.
     */

    @Test
    public void testMatrix3fConstructor()
    {
        final ImmutableMatrix3f matrix1 = new ImmutableMatrix3f(0, 1, 2, 3, 4,
            5,
            6, 7, 8, 9);
        final ImmutableMatrix3f matrix2 = new ImmutableMatrix3f(matrix1);
        assertNotSame(matrix1.m, matrix2.m);
        assertArrayEquals(matrix1.m, matrix2.m, 0.001f);
    }


    /**
     * Tests constructor with invalid arguments.
     */

    @Test(expected=IllegalArgumentException.class)
    public void testInvalidConstructor()
    {
        assertNull(new ImmutableMatrix3f(TEST_PATTERNS, 10));
    }


    /**
     * Tests the identity matrix.
     */

    @Test
    public void testIdentity()
    {
        final ImmutableMatrix3f matrix = ImmutableMatrix3f.identity();
        assertTrue(Arrays
            .equals(new float[] { 1, 0, 0, 0, 1, 0, 0, 0, 1 }, matrix.m));
    }


    /**
     * Tests the clone method.
     */

    @Test
    public void testClone()
    {
        final ImmutableMatrix3f matrix = new ImmutableMatrix3f(TEST_PATTERN);
        final ImmutableMatrix3f matrix2 = matrix.clone();
        assertNotSame(matrix.m, matrix2.m);
        assertTrue(Arrays.equals(matrix.m, matrix2.m));
    }


    /**
     * Tests the newInstance method.
     */

    @Test
    public void testNewInstance()
    {
        final ImmutableMatrix3f matrix = new ImmutableMatrix3f(TEST_PATTERN);
        final ImmutableMatrix3f matrix2 = matrix.newInstance();
        assertNotSame(matrix.m, matrix2.m);
        assertFalse(Arrays.equals(matrix.m, matrix2.m));
    }


    /**
     * Tests the getDeterminant() method.
     */

    @Test
    public void testGetDeterminant()
    {
        final ImmutableMatrix3f matrix = new ImmutableMatrix3f(
            1, 0, 0,
            0, 2, 0,
            2, 3, 1);
        assertEquals(2f, matrix.getDeterminant(), 0.001f);

        // Check cached value.
        assertEquals(2f, matrix.getDeterminant(), 0.001f);
    }


    /**
     * Tests the isIdentity() method.
     */

    @Test
    public void testIsIdentity()
    {
        ImmutableMatrix3f matrix = new ImmutableMatrix3f(
            1, 0, 0,
            0, 1, 0,
            1, 0, 1);
        assertFalse(matrix.isIdentity());
        assertFalse(matrix.isIdentity()); // Cached value

        matrix = new ImmutableMatrix3f(
            1, 0, 0,
            0, 1, 0,
            0, 0, 1);
        assertTrue(matrix.isIdentity());
        assertTrue(matrix.isIdentity()); // Cached value

        assertFalse(new ImmutableMatrix3f(
            0, 0, 0,
            0, 1, 0,
            0, 0, 1).isIdentity());
        assertFalse(new ImmutableMatrix3f(
            1, 1, 0,
            0, 1, 0,
            0, 0, 1).isIdentity());
        assertFalse(new ImmutableMatrix3f(
            1, 0, 1,
            0, 1, 0,
            0, 0, 1).isIdentity());
        assertFalse(new ImmutableMatrix3f(
            1, 0, 0,
            1, 1, 0,
            0, 0, 1).isIdentity());
        assertFalse(new ImmutableMatrix3f(
            1, 0, 0,
            0, 0, 0,
            0, 0, 1).isIdentity());
        assertFalse(new ImmutableMatrix3f(
            1, 0, 0,
            0, 1, 1,
            0, 0, 1).isIdentity());
        assertFalse(new ImmutableMatrix3f(
            1, 0, 0,
            0, 1, 0,
            0, 1, 1).isIdentity());
        assertFalse(new ImmutableMatrix3f(
            1, 0, 0,
            0, 1, 0,
            0, 0, 0).isIdentity());
    }


    /**
     * Tests the getAdjoint() method.
     */

    @Test
    public void testGetCofactor()
    {
        final ImmutableMatrix3f matrix = new ImmutableMatrix3f(
            1, 0, 1,
            2, 4, 0,
            3, 5, 6);

        ImmutableMatrix3f cofactor = matrix.getCofactor();
        assertNotSame(matrix, cofactor);
        assertArrayEquals(new float[] {
            24, -12, -2,
            5, 3, -5,
            -4, 2, 4 }, cofactor.m, 0.001f);

        cofactor = matrix.getCofactor(); // Cached value
        assertNotSame(matrix, cofactor);
        assertArrayEquals(new float[] {
            24, -12, -2,
            5, 3, -5,
            -4, 2, 4 }, cofactor.m, 0.001f);
    }


    /**
     * Tests the getAdjoint() method.
     */

    @Test
    public void testGetAdjoint()
    {
        final ImmutableMatrix3f matrix = new ImmutableMatrix3f(
            1, 0, 1,
            2, 4, 0,
            3, 5, 6);
        ImmutableMatrix3f adjoint = matrix.getAdjoint();
        assertNotSame(matrix, adjoint);
        assertArrayEquals(new float[] {
            24, 5, -4,
            -12, 3, 2,
            -2, -5, 4 }, adjoint.m, 0.001f);

        adjoint = matrix.getAdjoint(); // Cached value
        assertNotSame(matrix, adjoint);
        assertArrayEquals(new float[] {
            24, 5, -4,
            -12, 3, 2,
            -2, -5, 4 }, adjoint.m, 0.001f);
    }


    /**
     * Tests the getInverse() method.
     */

    @Test
    public void testGetInverse()
    {
        final ImmutableMatrix3f matrix = new ImmutableMatrix3f(
            1, 0, 0,
            0, 2, 0,
            2, 3, 1);
        ImmutableMatrix3f inverse = matrix.getInverse();
        assertNotSame(inverse, matrix);
        assertArrayEquals(new float[] {
            1, 0, 0,
            0, 0.5f, 0,
            -2, -1.5f, 1 }, inverse.m, 0.001f);

        inverse = matrix.getInverse(); // Cached value
        assertArrayEquals(new float[] {
            1, 0, 0,
            0, 0.5f, 0,
            -2, -1.5f, 1 }, inverse.m, 0.001f);
    }

    /**
     * Tests the getTranspose() method.
     */

    @Test
    public void testGetTranspose()
    {
        final ImmutableMatrix3f matrix = new ImmutableMatrix3f(
            1, 2, 3,
            5, 6, 7,
            9, 10, 11);

        ImmutableMatrix3f transpose = matrix.getTranspose();
        assertNotSame(matrix, transpose);
        assertArrayEquals(new float[] {
            1, 5, 9,
            2, 6, 10,
            3, 7, 11 }, transpose.m, 0.001f);

        transpose = matrix.getTranspose(); // Cached value
        assertNotSame(matrix, transpose);
        assertArrayEquals(new float[] {
            1, 5, 9,
            2, 6, 10,
            3, 7, 11 }, transpose.m, 0.001f);
    }


    /**
     * Tests the getBuffer() method.
     */

    @Test
    public void testGetBuffer()
    {
        final ImmutableMatrix3f matrix = new ImmutableMatrix3f(
            1, 2, 3,
            5, 6, 7,
            9, 10, 11);

        // Test first creation
        final FloatBuffer buffer = matrix.getBuffer();
        assertEquals(9, buffer.capacity());
        assertEquals(0, buffer.position());
        final float[] array = new float[9];
        buffer.get(array);
        assertArrayEquals(matrix.m, array, 0.001f);
        assertEquals(9, buffer.position());

        // Test second request
        final FloatBuffer buffer2 = matrix.getBuffer();
        assertSame(buffer, buffer2);
        assertEquals(9, buffer2.capacity());
        assertEquals(0, buffer2.position());
    }


    /**
     * Tests serialization.
     *
     * @throws IOException
     *             When object could not be written or read
     * @throws ClassNotFoundException
     *             When class was not found
     */

    @Test
    public void testSerialization() throws IOException, ClassNotFoundException
    {
        final ImmutableMatrix3f matrix = new ImmutableMatrix3f(
            1, 2, 3,
            5, 6, 7,
            9, 10, 11);

        // Trigger creation of NIO buffer
        final FloatBuffer before = matrix.getBuffer();

        // Serialize the matrix
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        final ObjectOutputStream oos = new ObjectOutputStream(baos);
        try
        {
            oos.writeObject(matrix);
        }
        finally
        {
            oos.close();
        }
        final byte[] ser = baos.toByteArray();

        // Deserialize the matrix
        final ByteArrayInputStream bais = new ByteArrayInputStream(ser);
        final ObjectInputStream ois = new ObjectInputStream(bais);
        try
        {
            final ImmutableMatrix3f matrix2 = (ImmutableMatrix3f) ois
                .readObject();
            assertNotSame(matrix, matrix2);
            assertEquals(matrix, matrix2);

            // Trigger creation of NIO buffer
            final FloatBuffer after = matrix2.getBuffer();
            assertNotSame(before, after);
            final float[] beforeArray = new float[9];
            final float[] afterArray = new float[9];
            before.get(beforeArray);
            after.get(afterArray);
            assertArrayEquals(beforeArray, afterArray, 0.001f);
        }
        finally
        {
            oos.close();
        }
    }
}
