//*****************************************************************************
// Copyright 2017-2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://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.
//*****************************************************************************

#include "gtest/gtest.h"

#include "ngraph/ngraph.hpp"

using namespace std;
using namespace ngraph;

std::shared_ptr<ngraph::op::Parameter> getParamFromShape(const ngraph::Shape& shape)
{
    return std::make_shared<ngraph::op::Parameter>(ngraph::element::f32, shape);
}

inline const ngraph::Shape& getShapeFromParam(const shared_ptr<ngraph::Node>& node)
{
    return node->get_shape();
}

// input shapes are equal so AutoBroadcast does nothing
TEST(autobroadcast, no_broadcast_equal)
{
    ngraph::Shape s2345{2, 3, 4, 5};
    auto lhs = getParamFromShape(s2345);
    auto rhs = getParamFromShape(s2345);

    auto shaped = ngraph::builder::numpy_broadcast({lhs, rhs});
    const shared_ptr<Node>& ab_lhs = shaped.first;
    const shared_ptr<Node>& ab_rhs = shaped.second;

    EXPECT_EQ(ab_lhs, lhs); // no change
    EXPECT_EQ(getShapeFromParam(ab_lhs), s2345);

    EXPECT_EQ(ab_rhs, rhs); // no change
    EXPECT_EQ(getShapeFromParam(ab_rhs), s2345);
}

// input shapes are incompatable
TEST(autobroadcast, no_broadcast_incompatable)
{
    ngraph::Shape s2345{2, 3, 4, 5};
    ngraph::Shape s6789{6, 7, 8, 9};
    auto lhs = getParamFromShape(s2345);
    auto rhs = getParamFromShape(s6789);

    EXPECT_THROW(ngraph::builder::numpy_broadcast({lhs, rhs}),
                 ngraph::builder::autobroadcast_incompatible_shapes);
}

// basic broadcast test
// 1D to 2D
// lhs broadcast to 2,3
TEST(autobroadcast, normal_broadcast_2d)
{
    ngraph::Shape s3{3};
    ngraph::Shape s23{2, 3};
    auto lhs = getParamFromShape(s3);
    auto rhs = getParamFromShape(s23);

    auto shaped = ngraph::builder::numpy_broadcast({lhs, rhs});
    const shared_ptr<Node>& ab_lhs = shaped.first;
    const shared_ptr<Node>& ab_rhs = shaped.second;

    EXPECT_NE(ab_lhs, lhs);
    EXPECT_EQ(getShapeFromParam(ab_lhs), s23);

    EXPECT_EQ(ab_rhs, rhs); // no change
    EXPECT_EQ(getShapeFromParam(ab_rhs), s23);
}

// basic broadcast test
// 2D to 3D
// lhs broadcast to 2,3,4
TEST(autobroadcast, normal_broadcast_3d)
{
    ngraph::Shape s34{3, 4};
    ngraph::Shape s234{2, 3, 4};
    auto lhs = getParamFromShape(s34);
    auto rhs = getParamFromShape(s234);

    auto shaped = ngraph::builder::numpy_broadcast({lhs, rhs});
    const shared_ptr<Node>& ab_lhs = shaped.first;
    const shared_ptr<Node>& ab_rhs = shaped.second;

    EXPECT_NE(ab_lhs, lhs);
    EXPECT_EQ(getShapeFromParam(ab_lhs), s234);

    EXPECT_EQ(ab_rhs, rhs); // no change
    EXPECT_EQ(getShapeFromParam(ab_rhs), s234);
}

// basic broadcast test
// 3D to 4D
// lhs broadcast to 2,3,4,5
TEST(autobroadcast, normal_broadcast_4d)
{
    ngraph::Shape s345{3, 4, 5};
    ngraph::Shape s2345{2, 3, 4, 5};
    auto lhs = getParamFromShape(s345);
    auto rhs = getParamFromShape(s2345);

    auto shaped = ngraph::builder::numpy_broadcast({lhs, rhs});
    const shared_ptr<Node>& ab_lhs = shaped.first;
    const shared_ptr<Node>& ab_rhs = shaped.second;

    EXPECT_NE(ab_lhs, lhs);
    EXPECT_EQ(getShapeFromParam(ab_lhs), s2345);

    EXPECT_EQ(ab_rhs, rhs); // no change
    EXPECT_EQ(getShapeFromParam(ab_rhs), s2345);
}

// basic reshape and broadcast test
// rhs reshape to 2,3,4 then
// rhs broadcast to 2,3,4,5
TEST(autobroadcast, reshape_1x_broadcast)
{
    ngraph::Shape s2345{2, 3, 4, 5};
    ngraph::Shape s2341{2, 3, 4, 1};
    auto lhs = getParamFromShape(s2345);
    auto rhs = getParamFromShape(s2341);

    auto shaped = ngraph::builder::numpy_broadcast({lhs, rhs});
    const shared_ptr<Node>& ab_lhs = shaped.first;
    const shared_ptr<Node>& ab_rhs = shaped.second;

    EXPECT_EQ(ab_lhs, lhs); // no change
    EXPECT_EQ(getShapeFromParam(ab_lhs), s2345);

    EXPECT_NE(ab_rhs, rhs);
    EXPECT_EQ(getShapeFromParam(ab_rhs), s2345);
}

// same as above, but additionally
// lhs reshape to 2,4,5 then
// lhs broadcast to 2,3,4,5
TEST(autobroadcast, reshape_2x_broadcast)
{
    ngraph::Shape s2145{2, 1, 4, 5};
    ngraph::Shape s2341{2, 3, 4, 1};
    auto lhs = getParamFromShape(s2145);
    auto rhs = getParamFromShape(s2341);

    auto shaped = ngraph::builder::numpy_broadcast({lhs, rhs});
    const shared_ptr<Node>& ab_lhs = shaped.first;
    const shared_ptr<Node>& ab_rhs = shaped.second;

    ngraph::Shape s2345{2, 3, 4, 5};

    EXPECT_NE(ab_lhs, lhs);
    EXPECT_EQ(getShapeFromParam(ab_lhs), s2345);

    EXPECT_NE(ab_rhs, rhs);
    EXPECT_EQ(getShapeFromParam(ab_rhs), s2345);
}

// matching singular dimension on axis 2
// should not require reshape of either lhs or rhs
// i.e. this should be the same as normal broadcast casse
// rhs broadcast to 2,3,1,5
TEST(autobroadcast, broadcast_with_dim1)
{
    ngraph::Shape s2315{2, 3, 1, 5};
    ngraph::Shape s315{3, 1, 5};
    auto lhs = getParamFromShape(s2315);
    auto rhs = getParamFromShape(s315);

    auto shaped = ngraph::builder::numpy_broadcast({lhs, rhs});
    const shared_ptr<Node>& ab_lhs = shaped.first;
    const shared_ptr<Node>& ab_rhs = shaped.second;

    EXPECT_EQ(ab_lhs, lhs); // no change
    EXPECT_EQ(getShapeFromParam(ab_lhs), s2315);

    EXPECT_NE(ab_rhs, rhs);
    EXPECT_EQ(getShapeFromParam(ab_rhs), s2315);
}

// reshape only test
// rhs reshape to 1,3,4,5 with no broadcast
TEST(autobroadcast, broadcast_with_leading_dim1)
{
    ngraph::Shape s1345{1, 3, 4, 5};
    ngraph::Shape s345{3, 4, 5};
    auto lhs = getParamFromShape(s1345);
    auto rhs = getParamFromShape(s345);

    auto shaped = ngraph::builder::numpy_broadcast({lhs, rhs});
    const shared_ptr<Node>& ab_lhs = shaped.first;
    const shared_ptr<Node>& ab_rhs = shaped.second;

    EXPECT_EQ(ab_lhs, lhs); // no change
    EXPECT_EQ(getShapeFromParam(ab_lhs), s1345);

    EXPECT_NE(ab_rhs, rhs);
    EXPECT_EQ(getShapeFromParam(ab_rhs), s1345);
}

TEST(autobroadcast, make_node_2_args)
{
    ngraph::Shape s21{2, 1};
    ngraph::Shape s23{2, 3};
    auto lhs = getParamFromShape(s21);
    auto rhs = getParamFromShape(s23);

    shared_ptr<Node> op = ngraph::builder::make_with_numpy_broadcast<ngraph::op::Add>(lhs, rhs);
    EXPECT_NE(op, nullptr);
}

TEST(autobroadcast, make_node_3_args)
{
    ngraph::Shape s21{2, 1};
    ngraph::Shape s23{2, 3};

    auto predicates = std::make_shared<ngraph::op::Parameter>(ngraph::element::boolean, s23);
    auto lhs = getParamFromShape(s21);
    auto rhs = getParamFromShape(s23);

    shared_ptr<Node> op =
        ngraph::builder::make_with_numpy_broadcast<ngraph::op::Select>(predicates, lhs, rhs);
    EXPECT_NE(op, nullptr);
}
