/*******************************************************************************
 * Copyright (c) 2015-2018 Skymind, Inc.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Apache License, Version 2.0 which is available at
 * https://www.apache.org/licenses/LICENSE-2.0.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 ******************************************************************************/

//
// Created by agibsonccc on 1/6/17.
//
#include <helpers/data_gen.h>
#include "testinclude.h"
#include <helpers/TAD.h>
#include <ShapeBuilders.h>

class OnesTest : public testing::Test {
public:
    Nd4jLong shapeBuffer[12] = {4,4,3,1,1,3,1,1,1,0,1,99};
    int dimension[3] = {0,2,3};
    Nd4jLong tadAssertionShape[10] = {3,1,1,4,1,1,3,0,3,99};
    int dimensionLength = 3;
};

class LabelTest : public testing::Test {
public:
    float labels[450] = {1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0};
    Nd4jLong shapeInfo[8] = {2,150,3,1,150,16384,1,102};
    int dimension[1] = {1};
    int dimensionLength = 1;
    Nd4jLong tadShapeInfoAssert[8] = {2,1,3,1,150,16384,150,102};
};
class ThreeDTest : public testing::Test {
public:
    Nd4jLong shape[3] = {3,4,5};
    Nd4jLong *shapeBuffer;
    ThreeDTest() {
        shapeBuffer = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 3, shape);        
    }
    ~ThreeDTest() {
        delete[] shapeBuffer;
    }
};

class VectorTest : public testing::Test {

};

class NumTadTests : public testing::Test {
public:
    Nd4jLong shape[3] = {3,4,5};
    int dimension = 0;
};

class ShapeTest :  public testing::Test {
public:
    Nd4jLong vectorShape[2] = {1,2};
};

class MatrixTest : public testing::Test {
public:
    int rows = 3;
    int cols = 4;
    int rank = 2;
    int dims[2] = {0,1};
    Nd4jLong expectedShapes[2][2] = {
            {1,3},
            {1,4}
    };
    Nd4jLong expectedStrides[2][2] = {
            {1,4},
            {1,1}
    };
};

class TADStall : public testing::Test {
public:
    Nd4jLong shape[4] = {3,3,4,5};
    int dimensions[3]  = {1,2,3};
};

class TensorOneDimTest : public testing::Test {
public:
    int rows = 3;
    int cols = 4;
    int dim2 = 5;
    int rank = 3;
    int dims[3] = {0,1,2};
    Nd4jLong expectedShapes[3][2] = {
            {1,3},
            {1,4},
            {1,5}
    };
    Nd4jLong expectedStrides[3][2] = {
            {1,20},
            {1,5},
            {1,1}
    };
};

class TensorTwoDimTest : public testing::Test {
public:
    //From a 3d array:
    int rows = 3;
    int cols = 4;
    int dim2 = 5;
    int dimensionLength = 2;
    int dims[3][2] = {
            {0,1},{0,2},{1,2}
    };

    Nd4jLong shape[3] {rows,cols,dim2};

    //Along dimension 0,1: expect matrix with shape [rows,cols]
    //Along dimension 0,2: expect matrix with shape [rows,dim2]
    //Along dimension 1,2: expect matrix with shape [cols,dim2]
    Nd4jLong expectedShapes[3][2] = {
            {rows,cols},
            {rows,dim2},
            {cols,dim2}
    };

    Nd4jLong expectedStrides[3][2] = {
            {20,5},
            {20,1},
            {5,1}
    };

};

class TensorTwoFromFourDDimTest : public testing::Test {
public:
    //From a 3d array:
    int rows = 3;
    int cols = 4;
    int dim2 = 5;
    int dim3 = 6;
    Nd4jLong shape[4] = {rows,cols,dim2,dim3};
    int dimensionLength = 2;
    //Along dimension 0,1: expect matrix with shape [rows,cols]
    //Along dimension 0,2: expect matrix with shape [rows,dim2]
    //Along dimension 0,3: expect matrix with shape [rows,dim3]
    //Along dimension 1,2: expect matrix with shape [cols,dim2]
    //Along dimension 1,3: expect matrix with shape [cols,dim3]
    //Along dimension 2,3: expect matrix with shape [dim2,dim3]

    int dims[6][2] = {
            {0,1},
            {0,2},
            {0,3},
            {1,2},
            {1,3},
            {2,3}
    };

    Nd4jLong expectedShapes[6][2] = {
             {rows,cols},
             {rows,dim2},
             {rows,dim3},
             {cols,dim2},
             {cols,dim3}
            ,{dim2,dim3}
    };

    Nd4jLong expectedStrides[6][2] = {
            {120,30},
            {120,6},
            {120,1},
            {30,6},
            {30,1},
            {6,1}
    };
};


class OrderTest : public testing::Test {
public:
    Nd4jLong expected[8] = {2,3,4,1,3,0,0,102};
    Nd4jLong test[8] = {2,3,4,1,3,0,0,102};

};


class LeadingOnes : public testing::Test {
public:
    Nd4jLong shapeBufferF[16] = {4,1,1,4,4,1,1,1,4,16384,1,102}; // shapes with data type DOUBLE
    Nd4jLong shapeBufferC[16] = {4,1,1,4,4,16,16,4,1,16384,1,99};
    int dimensionLength = 2;
    int dimension[2] = {2,3};
    Nd4jLong tadAssertionC[10] = {3,4,4,1,4,1,16,16384,1,99};
    Nd4jLong tadCAssertionF[10] = {3,4,4,1,1,4,1,16384,1,102};
};


TEST_F(LeadingOnes,OnesTest) {        

    shape::TAD *cTad = new shape::TAD;
    cTad->init(shapeBufferC,dimension,dimensionLength);
    cTad->createTadOnlyShapeInfo();
    cTad->createOffsets();
    shape::TAD *fTad = new shape::TAD;
    fTad->init(shapeBufferF,dimension,dimensionLength);
    fTad->createTadOnlyShapeInfo();
    fTad->createOffsets();
    // shape::printShapeInfoLinear(cTad->tadOnlyShapeInfo);
    // shape::printShapeInfoLinear(fTad->tadOnlyShapeInfo);
    ASSERT_TRUE(arrsEquals(10, tadCAssertionF, fTad->tadOnlyShapeInfo));
    ASSERT_TRUE(arrsEquals(10, tadAssertionC, cTad->tadOnlyShapeInfo));

    delete cTad;
    delete fTad;
}


class NormalThreeFourFive : public testing::Test {
public:
    Nd4jLong assertionBuffer[8] = {2, 3, 4, 20, 5, 16384, 0, 102};
    Nd4jLong inputShapeBuffer[10] = {3,3,4,5,20,5,1,16384,1,99};
    int dimensionLength = 2;
    int dimension[2] = {0,1};
};


TEST_F(NormalThreeFourFive,DimensionTest) {
    shape::TAD *tad = new shape::TAD;
    tad->init(inputShapeBuffer,dimension,dimensionLength);
    tad->createTadOnlyShapeInfo();
    tad->createOffsets();
    shape::printShapeInfoLinear(tad->tadOnlyShapeInfo);
    ASSERT_TRUE(arrsEquals(8,assertionBuffer,tad->tadOnlyShapeInfo));

    delete tad;
}

class DimensionWarning : public testing::Test {
public:
    int dimensionLength = 2;
    int dimensions[2] = {0,1};
    Nd4jLong shape[3] = {1,5,1};    
    Nd4jLong *shapeBuffer = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 3, shape);

    ~DimensionWarning() {
        delete[] shapeBuffer;
    }
};


TEST_F(DimensionWarning,ShapeWarning) {
    shape::TAD *tad = new shape::TAD;
    tad->init(shapeBuffer,dimensions,dimensionLength);
    tad->createTadOnlyShapeInfo();
    tad->createOffsets();
    delete tad;
}


class TadRank : public testing::Test {
    Nd4jLong shapeBuffer[12] = {4,2,1,3,3,9,9,3,1,0,1,99};
    int dimensionLength = 2;
    int dimension[2] = {2,3};

};

class TestRemoveIndex : public testing::Test {};

class TestReverseCopy : public testing::Test {};

class TestConcat : public testing::Test {};

class SliceVectorTest : public testing::Test {};

class SliceMatrixTest : public testing::Test {};

class SliceTensorTest : public testing::Test {};

class ElementWiseStrideTest : public testing::Test {
public:
    Nd4jLong shape[3] = {3,4,5};
    Nd4jLong stride[2] = {20,5};
    int elementWiseStrideAssertion = -1;
};

class PermuteTest : public testing::Test{};

class LengthPerSliceTest : public testing::Test{};

class ExpectedValuesTest : public testing::Test {
public:
    Nd4jLong mainShape[4] = {9,7,5,3};
    int testDimensions[3] = {0,2,3};

};

class BeginOneTadTest : public testing::Test {
public:
    Nd4jLong assertionShapeBuffer[8] = {2,3,5,1,3,16384,1,102};
    Nd4jLong inputShapeBuffer[10] = {3,1,3,5,1,1,3,16384,0,102};
    int dimensionLength = 2;
    int dimension[2] = {1,2};
    //error: [2,1,1,1,1,0,1,97]
};

class FourDTest : public testing::Test {
    /**
     * INDArray array3d = Nd4j.ones(1, 10, 10);
array3d.sum(1);

INDArray array4d = Nd4j.ones(1, 10, 10, 10);
INDArray sum40 = array4d.sum(0);
     */
public:
    Nd4jLong threeDShape[3] = {1,10,10};
    Nd4jLong fourDShape[4] = {1,10,10,10};
    Nd4jLong *threeDShapeBuffer = nullptr,*fourDShapeBuffer = nullptr;
    int dimensionThree = 1;
    int dimensionThreeTwo = 0;
    int dimensionFour = 0;
    int dimensionLength = 1;
    FourDTest() {
        threeDShapeBuffer = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'f', 3, threeDShape);        
        fourDShapeBuffer  = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'f', 4, fourDShape);
    }
    ~FourDTest() {
        if(threeDShapeBuffer != nullptr)
            delete[] threeDShapeBuffer;
        if(fourDShapeBuffer != nullptr)
            delete[] fourDShapeBuffer;
    }



};



TEST_F(FourDTest,ThreeDFourDTest) {
    shape::TAD *threeTadTwo = new shape::TAD;
    threeTadTwo->init(threeDShapeBuffer,&dimensionThreeTwo,dimensionLength);
    threeTadTwo->createTadOnlyShapeInfo();
    threeTadTwo->createOffsets();

    shape::TAD *threeTad = new shape::TAD;
    threeTad->init(threeDShapeBuffer,&dimensionThree,dimensionLength);
    threeTad->createTadOnlyShapeInfo();
    threeTad->createOffsets();

    shape::TAD *fourTad = new shape::TAD;
    fourTad->init(fourDShapeBuffer,&dimensionFour,dimensionLength);
    fourTad->createTadOnlyShapeInfo();
    fourTad->createOffsets();

    delete threeTadTwo;
    delete threeTad;
    delete fourTad;
}



class RowVectorOnesTest : public testing::Test {
public:
    Nd4jLong shapeBuffer[12] = {4,4,3,1,1,3,1,1,1,8192,1,99}; // float32 type of shape
    float data[12] = {1,2,3,4,5,6,7,8,9,10,11,12};
    Nd4jLong assertionBuffer[10] = {3,4,1,1,3,1,1,8192,0,99};
    int dimensionLength = 3;
    int dimension[3] = {0,2,3};
};

// TEST_F(RowVectorOnesTest,TadShape) {
//     shape::TAD *tad = new shape::TAD(shapeBuffer,dimension,dimensionLength);
//     tad->createTadOnlyShapeInfo();
//     tad ->createOffsets();
//     ASSERT_TRUE(arrsEquals(10,assertionBuffer,tad->tadOnlyShapeInfo));
//     delete tad;
// }



class SixDTest : public testing::Test {
public:
    Nd4jLong inputShapeBuffer[16] = {6,1,1,4,4,4,4,1,1,1,4,16,64,16384,1,102}; // shape with double data type
    int dimensionLength = 2;
    int dimension[2] = {2,3};
    Nd4jLong assertionShapeBuffer[8] = {2,4,4,1,4,16384,1,102}; // also double typed shape
};

TEST_F(SixDTest, SixDWithOnes) {
    shape::TAD *tad = new shape::TAD;
    tad->init(inputShapeBuffer,dimension,dimensionLength);
    tad->createTadOnlyShapeInfo();
    tad->createOffsets();
    // shape::printShapeInfoLinear(inputShapeBuffer);
    // shape::printShapeInfoLinear(tad->tadOnlyShapeInfo);
    //[2,1,1,1,1,0,1,97]
    ASSERT_TRUE(arrsEquals(8,assertionShapeBuffer,tad->tadOnlyShapeInfo));
    delete tad;
}

class TrailingTest : public testing::Test {
public:
    Nd4jLong inputShapeBuffer[12] = {4,5,5,5,1,1,5,25,125,16384,1,102};
    int dimensionLength = 1;
    int dimension[1] = {0};
    Nd4jLong assertionShapeBuffer[8] = {2,1,5,125,1,16384,1,102};
};

TEST_F(TrailingTest,TrailingTest2) {
    shape::TAD *tad = new shape::TAD;
    tad->init(inputShapeBuffer,dimension,dimensionLength);
    tad->createTadOnlyShapeInfo();
    tad->createOffsets();
    //[2,1,1,1,1,0,1,97]
    ASSERT_TRUE(arrsEquals(8,assertionShapeBuffer,tad->tadOnlyShapeInfo));
    delete tad;
}


class ScalarTest : public testing::Test {
public:
    Nd4jLong inputShapeBuffer[12] = {3,2,3,4,12,4,1,16384,1,99};
    int dimensionLength = 1;
    int dimension[1] = {1};
    Nd4jLong assertionShapeBuffer[8] = {2,1,1,1,1,16384,1,99};
};
/*
TEST_F(ScalarTest,ScalarTest2) {
    shape::TAD *tad = new shape::TAD(inputShapeBuffer,dimension,dimensionLength);
    tad->createTadOnlyShapeInfo();
    tad ->createOffsets();
    //[2,1,1,1,1,0,1,97]
    shape::printShapeInfoLinear(tad->tadOnlyShapeInfo);
    ASSERT_TRUE(arrsEquals(8,assertionShapeBuffer,tad->tadOnlyShapeInfo));
}
*/



class ThreeTest : public testing::Test {
public:
    Nd4jLong inputShapeBuffer[10] = {3,4,3,2,6,2,1,16384,1,99};
    int dimensionLength = 1;
    int dimension[1] = {0};
    Nd4jLong assertionShapeBuffer[8] = {2,1,4,1,6,16384,6,99};
};

TEST_F(ThreeTest,ThreeTest ) {
    shape::TAD *tad = new shape::TAD;
    tad->init(inputShapeBuffer,dimension,dimensionLength);
    tad->createTadOnlyShapeInfo();
    tad->createOffsets();
    //[2,1,1,1,1,0,1,97]
    ASSERT_TRUE(arrsEquals(8,assertionShapeBuffer,tad->tadOnlyShapeInfo));
    delete tad;
}


TEST_F(BeginOneTadTest, TadTest) {
    shape::TAD *tad = new shape::TAD;
    tad->init(inputShapeBuffer,dimension,dimensionLength);
    tad->createTadOnlyShapeInfo();
    auto tadShapeBuffer = tad->tadOnlyShapeInfo;
    // shape::printShapeInfoLinear(tadShapeBuffer);
    //[2,1,1,1,1,0,1,97]
    ASSERT_TRUE(arrsEquals(8,assertionShapeBuffer,tadShapeBuffer));

    delete tad;
}

/*
TEST_F(OnesTest,OnesTadTest) {
    shape::TAD *tad = new shape::TAD(shapeBuffer,dimension,dimensionLength);
    int *tadShapeBuffer = tad->shapeInfoOnlyShapeAndStride();
    ASSERT_TRUE(arrsEquals(10,tadAssertionShape,tadShapeBuffer));
    delete[] tadShapeBuffer;
}
*/

TEST_F(LabelTest,LabelTad) {
    shape::TAD *tad = new shape::TAD;
    tad->init(shapeInfo,dimension,dimensionLength);
    tad->createTadOnlyShapeInfo();
    auto tadShapeInfo = tad->tadOnlyShapeInfo;
    ASSERT_TRUE(arrsEquals(8,tadShapeInfoAssert,tadShapeInfo));

    delete tad;
}

TEST_F(ExpectedValuesTest,TadTest) {    
    auto shapeBuffer = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 4, mainShape);
    shape::TAD *tad = new shape::TAD;
    tad->init(shapeBuffer,testDimensions,3);
    tad->createTadOnlyShapeInfo();
    auto shapeInfo = tad->tadOnlyShapeInfo;

    delete tad;
    delete[] shapeBuffer;
}

TEST_F(OrderTest,testOrder) {
    int rank = shape::rank(expected);
    auto expectedShape = shape::shapeOf(expected);
    auto expectedStride = shape::stride(expected);
    int realOrder = shape::getOrder(rank,expectedShape,expectedStride,1);
    int expectedOrder = 102;
    ASSERT_EQ(expectedOrder,realOrder);
}


TEST_F(ThreeDTest,TensorAlongDimensionTest) {
    int dimension[2] = {0,2};
    Nd4jLong tadShapeAssertion[2] = {3,5};
    Nd4jLong strideAssertion[2] = {20,1};
    shape::TAD *tad = new shape::TAD;
    tad->init(0,this->shapeBuffer,dimension,2);
    tad->createTadOnlyShapeInfo();
    auto shapeBufferTest = tad->tadOnlyShapeInfo;
    auto shapeTest = shape::shapeOf(shapeBufferTest);
    auto strideTest = shape::stride(shapeBufferTest);
    ASSERT_TRUE(arrsEquals(2,tadShapeAssertion,shapeTest));
    ASSERT_TRUE(arrsEquals(2,strideAssertion,strideTest));
    delete tad;
}


TEST_F(NumTadTests,TadTest) {    
    auto shape = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 3, this->shape);
    shape::TAD *tad = new shape::TAD;
    tad->init(shape,&dimension,1);
    int numTads = shape::tensorsAlongDimension(shape,&dimension,1);
    ASSERT_EQ(20,numTads);
    delete[] shape;
    delete tad;
}

TEST_F(TADStall,TestStall) {
    auto shapeInfo = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 4, shape);    
    shape::TAD *tad = new shape::TAD;
    tad->init(0,shapeInfo,this->dimensions,3);
    tad->createTadOnlyShapeInfo();
    Nd4jLong *test = tad->tadOnlyShapeInfo;

    delete[] shapeInfo;
    delete tad;
}


TEST_F(LengthPerSliceTest,TestLengthPerSlice) {
    Nd4jLong firstShape[2] = {5,3};
    int lengthPerSliceAssertionFirst = 3;
    int firstDimension = 0;
    int lengthPerSliceTest = shape::lengthPerSlice(2,firstShape,&firstDimension,1);
    ASSERT_EQ(lengthPerSliceAssertionFirst,lengthPerSliceTest);
}

TEST_F(PermuteTest,PermuteShapeBufferTest) {
    int permuteOrder[4] = {3,2,1,0};
    int normalOrder[4] = {0,1,2,3};
    Nd4jLong shapeToPermute[4] = {5,3,2,6};
    Nd4jLong permutedOrder[4] = {6,2,3,5};
    auto shapeBufferOriginal  = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 4, shapeToPermute);
    auto assertionShapeBuffer = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 4, shapeToPermute);    
    shape::permuteShapeBufferInPlace(shapeBufferOriginal,normalOrder,shapeBufferOriginal);
    EXPECT_TRUE(arrsEquals(4,assertionShapeBuffer,shapeBufferOriginal));

    auto backwardsAssertion  = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 4, permutedOrder);
    auto permuted = shape::permuteShapeBuffer(assertionShapeBuffer, permuteOrder);
    EXPECT_TRUE(arrsEquals(4, backwardsAssertion, permuted));


    delete[] permuted;
    delete[] backwardsAssertion;
    delete[] shapeBufferOriginal;
    delete[] assertionShapeBuffer;
}

TEST_F(ElementWiseStrideTest,ElementWiseStrideTest) {

}

TEST_F(SliceVectorTest,RowColumnVectorTest) {
    Nd4jLong rowVectorShape[2] = {1,5};
    auto rowVectorShapeInfo  = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 2, rowVectorShape);    
    Nd4jLong colVectorShape[2] = {5,1};
    auto colVectorShapeInfo  = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 2, colVectorShape);    
    Nd4jLong *sliceRow = shape::sliceOfShapeBuffer(0,rowVectorShapeInfo);
    EXPECT_TRUE(arrsEquals(2,rowVectorShapeInfo,sliceRow));
    Nd4jLong *scalarSliceInfo = shape::createScalarShapeInfo();
    Nd4jLong *scalarColumnAssertion = shape::createScalarShapeInfo();
    scalarColumnAssertion[shape::shapeInfoLength(2) - 3] = 1;
    Nd4jLong *scalarColumnTest = shape::sliceOfShapeBuffer(1L,colVectorShapeInfo);
    EXPECT_TRUE(arrsEquals(2,scalarColumnAssertion,scalarColumnTest));

    delete[] scalarColumnTest;
    delete[] scalarColumnAssertion;
    delete[] scalarSliceInfo;
    delete[] sliceRow;
    delete[] rowVectorShapeInfo;
    delete[] colVectorShapeInfo;
}

TEST_F(SliceTensorTest,TestSlice) {
    Nd4jLong shape[3] = {3,3,2};
    auto shapeBuffer  = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 3, shape);
    Nd4jLong sliceShape[2] = {3,2};
    auto sliceShapeBuffer  = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 2, sliceShape);    
    Nd4jLong *testSlice = shape::sliceOfShapeBuffer(0,shapeBuffer);
    EXPECT_TRUE(arrsEquals(2,sliceShapeBuffer,testSlice));
    delete[] testSlice;
    delete[] shapeBuffer;
    delete[] sliceShapeBuffer;

}

TEST_F(SliceMatrixTest,TestSlice) {
    Nd4jLong shape[2] = {3,2};
    auto shapeBuffer = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 2, shape);    
    Nd4jLong sliceShape[2] = {1,2};
    auto sliceShapeBuffer = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 2, sliceShape);    
    Nd4jLong *testSlice = shape::sliceOfShapeBuffer(0,shapeBuffer);
    EXPECT_TRUE(arrsEquals(2,sliceShapeBuffer,testSlice));
    delete[] testSlice;
    delete[] shapeBuffer;
    delete[] sliceShapeBuffer;

}


TEST_F(TestConcat,ConcatTest) {
    Nd4jLong firstArr[2] = {1,2};
    Nd4jLong secondConcat[2] = {3,4};
    Nd4jLong concatAssertion[4] = {1,2,3,4};
    Nd4jLong *concatTest = shape::concat(firstArr,2,secondConcat,2);
    EXPECT_TRUE(arrsEquals(4,concatAssertion,concatTest));
    delete[] concatTest;
}

TEST_F(TestReverseCopy,ReverseCopyTest) {
    Nd4jLong toCopy[5] = {0,1,2,3,4};
    Nd4jLong reverseAssertion[5] = {4,3,2,1,0};
    Nd4jLong *reverseCopyTest = shape::reverseCopy(toCopy,5);
    EXPECT_TRUE(arrsEquals(5,reverseAssertion,reverseCopyTest));
    delete[] reverseCopyTest;
}

TEST_F(TestRemoveIndex,Remove) {
    Nd4jLong input[5] = {0,1,2,3,4};
    Nd4jLong indexesToRemove[3] = {0,1,2};
    Nd4jLong indexesToRemoveAssertion[2] = {3,4};
    Nd4jLong *indexesToRemoveTest = shape::removeIndex<Nd4jLong>(input,indexesToRemove, (Nd4jLong) 5, (Nd4jLong) 3);
    EXPECT_TRUE(arrsEquals(2,indexesToRemoveAssertion,indexesToRemoveTest));
    delete[] indexesToRemoveTest;
}

TEST_F(TensorTwoFromFourDDimTest,TadTwoFromFourDimTest) {
    //Along dimension 0,1: expect matrix with shape [rows,cols]
    //Along dimension 0,2: expect matrix with shape [rows,dim2]
    //Along dimension 0,3: expect matrix with shape [rows,dim3]
    //Along dimension 1,2: expect matrix with shape [cols,dim2]
    //Along dimension 1,3: expect matrix with shape [cols,dim3]
    //Along dimension 2,3: expect matrix with shape [dim2,dim3]
    auto baseShapeBuffer = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 4, shape);    
    for(int i = 0; i <  3; i++) {
        int *dimArr = dims[i];
        Nd4jLong *expectedShape = expectedShapes[i];
        shape::TAD *tad = new shape::TAD;
        tad->init(baseShapeBuffer,dimArr,dimensionLength);
        auto expectedShapeBuffer = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', dimensionLength, expectedShape);        
        tad->createTadOnlyShapeInfo();
        Nd4jLong *testShapeBuffer = tad->tadOnlyShapeInfo;
        EXPECT_TRUE(arrsEquals(shape::rank(expectedShapeBuffer),expectedShape,shape::shapeOf(testShapeBuffer)));
        EXPECT_TRUE(arrsEquals(shape::rank(expectedShapeBuffer),expectedStrides[i],shape::stride(testShapeBuffer)));

        delete[] expectedShapeBuffer;
        delete tad;
    }

    delete[] baseShapeBuffer;
}

TEST_F(TensorTwoDimTest,TadTwoDimTest) {
    //Along dimension 0,1: expect matrix with shape [rows,cols]
    //Along dimension 0,2: expect matrix with shape [rows,dim2]
    //Along dimension 1,2: expect matrix with shape [cols,dim2]
    auto baseShapeBuffer = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 3, shape);    

    for(int i = 0; i <  3; i++) {
        int *dimArr = dims[i];
        Nd4jLong *expectedShape = expectedShapes[i];
        shape::TAD *tad = new shape::TAD;
        tad->init(baseShapeBuffer,dimArr,dimensionLength);
        auto expectedShapeBuffer = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', dimensionLength, expectedShape);        
        tad->createTadOnlyShapeInfo();
        Nd4jLong *testShapeBuffer = tad->tadOnlyShapeInfo;
        Nd4jLong *expectedStride = expectedStrides[i];
        Nd4jLong *testShape = shape::shapeOf(testShapeBuffer);
        Nd4jLong *testStride = shape::stride(testShapeBuffer);
        EXPECT_TRUE(arrsEquals(shape::rank(expectedShapeBuffer),expectedShape,testShape));
        EXPECT_TRUE(arrsEquals(shape::rank(testShapeBuffer),expectedStride,testStride));

        delete[] expectedShapeBuffer;
        delete tad;

    }

    delete[] baseShapeBuffer;


}

TEST_F(TensorOneDimTest,TadDimensionsForTensor) {
    Nd4jLong shape[3] = {rows,cols,dim2};
    auto shapeBuffer = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', rank, shape);    

    for(int i = 0; i < rank; i++) {
        //Along dimension 0: expect row vector with length 'dims[i]'
        shape::TAD *zero = new shape::TAD;
        zero->init(shapeBuffer,&dims[i],1);
        zero->createTadOnlyShapeInfo();
        Nd4jLong *testDimZeroShapeBuffer = zero->tadOnlyShapeInfo;
        Nd4jLong *testShape = shape::shapeOf(testDimZeroShapeBuffer);
        Nd4jLong *testStride = shape::stride(testDimZeroShapeBuffer);
        EXPECT_TRUE(arrsEquals(2,expectedShapes[i],testShape));
        EXPECT_TRUE(arrsEquals(2,expectedStrides[i],testStride));

        delete zero;
    }

    delete[] shapeBuffer;
}


TEST_F(MatrixTest,TadDimensionsForMatrix) {
    Nd4jLong shape[2] = {rows,cols};
    auto shapeBuffer = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', rank, shape);    

    shape::TAD *dimZero = new shape::TAD;
    dimZero->init(shapeBuffer,&dims[0],1);
    shape::TAD *dimOne = new shape::TAD;
    dimOne->init(shapeBuffer,&dims[1],1);
    //Along dimension 0: expect row vector with length 'rows'
    Nd4jLong rowVectorShape[2] = {1,rows};    
    auto expectedDimZeroShape = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 2, rowVectorShape);
    dimZero->createTadOnlyShapeInfo();
    Nd4jLong *testDimZero = dimZero->tadOnlyShapeInfo;
    EXPECT_TRUE(arrsEquals(2,expectedShapes[0],shape::shapeOf(testDimZero)));
    EXPECT_TRUE(arrsEquals(2,expectedStrides[0],shape::stride(testDimZero)));

    delete[] expectedDimZeroShape;
    //Along dimension 1: expect row vector with length 'cols'
    Nd4jLong rowVectorColShape[2] {1,cols};    
    auto expectedDimOneShape = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 2, rowVectorColShape);
    dimOne->createTadOnlyShapeInfo();
    Nd4jLong *testDimOneShape = dimOne->tadOnlyShapeInfo;
    EXPECT_TRUE(arrsEquals(2,expectedShapes[1],shape::shapeOf(testDimOneShape)));
    EXPECT_TRUE(arrsEquals(2,expectedStrides[1],shape::stride(testDimOneShape)));

    delete[] expectedDimOneShape;
    delete dimOne;
    delete dimZero;
    delete[] shapeBuffer;
}

TEST_F(VectorTest,VectorTadShape) {
    Nd4jLong rowVector[2] = {2,2};    
    auto rowBuffer = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 2, rowVector);
    int rowDimension = 1;

    Nd4jLong columnVector[2] = {2,2};
    auto colShapeBuffer = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 2, columnVector);   
    int colDimension = 0;


    shape::TAD *rowTad = new shape::TAD;
    rowTad->init(rowBuffer,&rowDimension,1);
    rowTad->createTadOnlyShapeInfo();
    Nd4jLong *rowTadShapeBuffer = rowTad->tadOnlyShapeInfo;
    Nd4jLong *rowTadShape = shape::shapeOf(rowTadShapeBuffer);
    shape::TAD *colTad = new shape::TAD;
    colTad->init(colShapeBuffer,&colDimension,1);
    colTad->createTadOnlyShapeInfo();
    Nd4jLong *colTadShapeBuffer = colTad->tadOnlyShapeInfo;
    Nd4jLong *colTadShape = shape::shapeOf(colTadShapeBuffer);
    Nd4jLong assertionShape[2] = {1,2};
    Nd4jLong assertionStride[2] = {1,1};
    EXPECT_TRUE(arrsEquals(2,assertionShape,rowTadShape));
    EXPECT_TRUE(arrsEquals(2,assertionStride,shape::stride(rowTadShapeBuffer)));
    EXPECT_TRUE(arrsEquals(2,assertionShape,colTadShape));

    delete[] rowBuffer;
    delete[] colShapeBuffer;
    delete rowTad;
    delete colTad;
}




TEST_F(ShapeTest,IsVector) {
    ASSERT_TRUE(shape::isVector(vectorShape,2));
}

TEST_F(VectorTest,LinspaceCombinationTest) {
    int rows = 3;
    int cols = 4;
    int len = rows * cols;
    double *linspaced = linspace<double>(1,rows * cols,len);
    Nd4jLong shape[2] = {rows,cols};
    auto shapeBuffer = nd4j::ShapeBuilders::createShapeInfo(nd4j::DataType::FLOAT32, 'c', 2, shape);    

    delete[] shapeBuffer;
    delete[] linspaced;
}