package org.nd4j.linalg.shape;

import lombok.val;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.nd4j.linalg.BaseNd4jTest;
import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.api.shape.Shape;
import org.nd4j.linalg.factory.Nd4j;
import org.nd4j.linalg.factory.Nd4jBackend;
import org.nd4j.linalg.indexing.NDArrayIndex;
import org.nd4j.linalg.primitives.Triple;

import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.*;

/**
 * @author Adam Gibson
 */
@RunWith(Parameterized.class)
public class ShapeTests extends BaseNd4jTest {
    public ShapeTests(Nd4jBackend backend) {
        super(backend);
    }


    @Test
    public void testRowColVectorVsScalar() {
        INDArray arr = Nd4j.create(2);
        assertTrue(arr.isRowVector());
        INDArray colVector = arr.reshape(2,1);
        assertTrue(colVector.isColumnVector());
        assertFalse(arr.isScalar());
        assertFalse(colVector.isScalar());

        INDArray arr3 = Nd4j.scalar(1.0);
        assertFalse(arr3.isColumnVector());
        assertFalse(arr3.isRowVector());
   }

    @Test
    public void testSixteenZeroOne() {
        INDArray baseArr = Nd4j.linspace(1, 16, 16).reshape(2, 2, 2, 2);
        assertEquals(4, baseArr.tensorssAlongDimension(0, 1));
        INDArray columnVectorFirst = Nd4j.create(new double[][] {{1, 3}, {2, 4}});
        INDArray columnVectorSecond = Nd4j.create(new double[][] {{9, 11}, {10, 12}});
        INDArray columnVectorThird = Nd4j.create(new double[][] {{5, 7}, {6, 8}});
        INDArray columnVectorFourth = Nd4j.create(new double[][] {{13, 15}, {14, 16}});
        INDArray[] assertions =
                        new INDArray[] {columnVectorFirst, columnVectorSecond, columnVectorThird, columnVectorFourth};

        for (int i = 0; i < baseArr.tensorssAlongDimension(0, 1); i++) {
            INDArray test = baseArr.tensorAlongDimension(i, 0, 1);
            assertEquals("Wrong at index " + i, assertions[i], test);
        }

    }

    @Test
    public void testVectorAlongDimension1() {
        INDArray arr = Nd4j.create(1, 5, 5);
        assertEquals(arr.vectorsAlongDimension(0), 5);
        assertEquals(arr.vectorsAlongDimension(1), 5);
        for (int i = 0; i < arr.vectorsAlongDimension(0); i++) {
            if (i < arr.vectorsAlongDimension(0) - 1 && i > 0)
                assertEquals(25, arr.vectorAlongDimension(i, 0).length());
        }

    }

    @Test
    public void testSixteenSecondDim() {
        INDArray baseArr = Nd4j.linspace(1, 16, 16).reshape(2, 2, 2, 2);
        INDArray[] assertions = new INDArray[] {Nd4j.create(new double[] {1, 5}), Nd4j.create(new double[] {9, 13}),
                        Nd4j.create(new double[] {3, 7}), Nd4j.create(new double[] {11, 15}),
                        Nd4j.create(new double[] {2, 6}), Nd4j.create(new double[] {10, 14}),
                        Nd4j.create(new double[] {4, 8}), Nd4j.create(new double[] {12, 16}),


        };

        for (int i = 0; i < baseArr.tensorssAlongDimension(2); i++) {
            INDArray arr = baseArr.tensorAlongDimension(i, 2);
            assertEquals("Failed at index " + i, assertions[i], arr);
        }

    }



    @Test
    public void testVectorAlongDimension() {
        INDArray arr = Nd4j.linspace(1, 24, 24).reshape(4, 3, 2);
        INDArray assertion = Nd4j.create(new float[] {5, 17}, new long[] {1, 2});
        INDArray vectorDimensionTest = arr.vectorAlongDimension(1, 2);
        assertEquals(assertion, vectorDimensionTest);
        INDArray zeroOne = arr.vectorAlongDimension(0, 1);
        assertEquals(zeroOne, Nd4j.create(new float[] {1, 5, 9}));

        INDArray testColumn2Assertion = Nd4j.create(new float[] {13, 17, 21});
        INDArray testColumn2 = arr.vectorAlongDimension(1, 1);

        assertEquals(testColumn2Assertion, testColumn2);


        INDArray testColumn3Assertion = Nd4j.create(new float[] {2, 6, 10});
        INDArray testColumn3 = arr.vectorAlongDimension(2, 1);
        assertEquals(testColumn3Assertion, testColumn3);


        INDArray v1 = Nd4j.linspace(1, 4, 4).reshape(new long[] {2, 2});
        INDArray testColumnV1 = v1.vectorAlongDimension(0, 0);
        INDArray testColumnV1Assertion = Nd4j.create(new float[] {1, 2});
        assertEquals(testColumnV1Assertion, testColumnV1);

        INDArray testRowV1 = v1.vectorAlongDimension(1, 0);
        INDArray testRowV1Assertion = Nd4j.create(new float[] {3, 4});
        assertEquals(testRowV1Assertion, testRowV1);

    }

    @Test
    public void testThreeTwoTwo() {
        INDArray threeTwoTwo = Nd4j.linspace(1, 12, 12).reshape(3, 2, 2);
        INDArray[] assertions = new INDArray[] {Nd4j.create(new double[] {1, 4}), Nd4j.create(new double[] {7, 10}),
                        Nd4j.create(new double[] {2, 5}), Nd4j.create(new double[] {8, 11}),
                        Nd4j.create(new double[] {3, 6}), Nd4j.create(new double[] {9, 12}),

        };

        assertEquals(assertions.length, threeTwoTwo.tensorssAlongDimension(1));
        for (int i = 0; i < assertions.length; i++) {
            INDArray test = threeTwoTwo.tensorAlongDimension(i, 1);
            assertEquals(assertions[i], test);
        }

    }

    @Test
    public void testNoCopy() {
        INDArray threeTwoTwo = Nd4j.linspace(1, 12, 12);
        INDArray arr = Shape.newShapeNoCopy(threeTwoTwo, new long[] {3, 2, 2}, true);
        assertArrayEquals(arr.shape(), new long[] {3, 2, 2});
    }

    @Test
    public void testThreeTwoTwoTwo() {
        INDArray threeTwoTwo = Nd4j.linspace(1, 12, 12).reshape(3, 2, 2);
        INDArray[] assertions = new INDArray[] {Nd4j.create(new double[] {1, 7}), Nd4j.create(new double[] {4, 10}),
                        Nd4j.create(new double[] {2, 8}), Nd4j.create(new double[] {5, 11}),
                        Nd4j.create(new double[] {3, 9}), Nd4j.create(new double[] {6, 12}),

        };

        assertEquals(assertions.length, threeTwoTwo.tensorssAlongDimension(2));
        for (int i = 0; i < assertions.length; i++) {
            INDArray test = threeTwoTwo.tensorAlongDimension(i, 2);
            assertEquals(assertions[i], test);
        }

    }

    @Test
    public void testNewAxis() {
        INDArray tensor = Nd4j.linspace(1, 12, 12).reshape(3, 2, 2);
        INDArray assertion = Nd4j.create(new double[][] {{1, 7}, {4, 10}}).reshape(1, 2, 2);
        INDArray tensorGet = tensor.get(NDArrayIndex.point(0), NDArrayIndex.newAxis());
        assertEquals(assertion, tensorGet);

    }


    @Test
    public void testSixteenFirstDim() {
        INDArray baseArr = Nd4j.linspace(1, 16, 16).reshape(2, 2, 2, 2);
        INDArray[] assertions = new INDArray[] {Nd4j.create(new double[] {1, 3}), Nd4j.create(new double[] {9, 11}),
                        Nd4j.create(new double[] {5, 7}), Nd4j.create(new double[] {13, 15}),
                        Nd4j.create(new double[] {2, 4}), Nd4j.create(new double[] {10, 12}),
                        Nd4j.create(new double[] {6, 8}), Nd4j.create(new double[] {14, 16}),


        };

        for (int i = 0; i < baseArr.tensorssAlongDimension(1); i++) {
            INDArray arr = baseArr.tensorAlongDimension(i, 1);
            assertEquals("Failed at index " + i, assertions[i], arr);
        }

    }


    @Test
    public void testDimShuffle() {
        INDArray scalarTest = Nd4j.scalar(0.0);
        INDArray broadcast = scalarTest.dimShuffle(new Object[] {'x'}, new long[] {0, 1}, new boolean[] {true, true});
        assertTrue(broadcast.rank() == 3);
        INDArray rowVector = Nd4j.linspace(1, 4, 4);
        assertEquals(rowVector,
                        rowVector.dimShuffle(new Object[] {0, 1}, new int[] {0, 1}, new boolean[] {false, false}));
        //add extra dimension to row vector in middle
        INDArray rearrangedRowVector =
                        rowVector.dimShuffle(new Object[] {0, 'x', 1}, new int[] {0, 1}, new boolean[] {true, true});
        assertArrayEquals(new long[] {1, 1, 4}, rearrangedRowVector.shape());

        INDArray dimshuffed = rowVector.dimShuffle(new Object[] {'x', 0, 'x', 'x'}, new long[] {0, 1},
                        new boolean[] {true, true});
        assertArrayEquals(new long[] {1, 1, 1, 1, 4}, dimshuffed.shape());
    }



    @Test
    public void testEight() {
        INDArray baseArr = Nd4j.linspace(1, 8, 8).reshape(2, 2, 2);
        assertEquals(2, baseArr.tensorssAlongDimension(0, 1));
        INDArray columnVectorFirst = Nd4j.create(new double[][] {{1, 3}, {2, 4}});
        INDArray columnVectorSecond = Nd4j.create(new double[][] {{5, 7}, {6, 8}});
        assertEquals(columnVectorFirst, baseArr.tensorAlongDimension(0, 0, 1));
        assertEquals(columnVectorSecond, baseArr.tensorAlongDimension(1, 0, 1));
    }

    @Test
    public void testBroadcastShapes(){
        //Test cases: in1Shape, in2Shape, shapeOf(op(in1,in2))
        List<Triple<long[], long[], long[]>> testCases = new ArrayList<>();
        testCases.add(new Triple<>(new long[]{3,1}, new long[]{1,4}, new long[]{3,4}));
        testCases.add(new Triple<>(new long[]{3,1}, new long[]{3,4}, new long[]{3,4}));
        testCases.add(new Triple<>(new long[]{3,4}, new long[]{1,4}, new long[]{3,4}));
        testCases.add(new Triple<>(new long[]{3,4,1}, new long[]{1,1,5}, new long[]{3,4,5}));
        testCases.add(new Triple<>(new long[]{3,4,1}, new long[]{3,1,5}, new long[]{3,4,5}));
        testCases.add(new Triple<>(new long[]{3,1,5}, new long[]{1,4,1}, new long[]{3,4,5}));
        testCases.add(new Triple<>(new long[]{3,1,5}, new long[]{1,4,5}, new long[]{3,4,5}));
        testCases.add(new Triple<>(new long[]{3,1,5}, new long[]{3,4,5}, new long[]{3,4,5}));
        testCases.add(new Triple<>(new long[]{3,1,1,1}, new long[]{1,4,5,6}, new long[]{3,4,5,6}));
        testCases.add(new Triple<>(new long[]{1,1,1,6}, new long[]{3,4,5,6}, new long[]{3,4,5,6}));
        testCases.add(new Triple<>(new long[]{1,4,5,1}, new long[]{3,1,1,6}, new long[]{3,4,5,6}));
        testCases.add(new Triple<>(new long[]{1,6}, new long[]{3,4,5,1}, new long[]{3,4,5,6}));

        for(Triple<long[], long[], long[]> t : testCases){
            val x = t.getFirst();
            val y = t.getSecond();
            val exp = t.getThird();

            val act = Shape.broadcastOutputShape(x,y);
            assertArrayEquals(exp,act);
        }
    }


    @Override
    public char ordering() {
        return 'f';
    }
}
