# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
#
# 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.

import unittest

import numpy as np
from test_case_base import TestCaseBase

import paddle
from paddle.jit.sot.utils.paddle_api_config import add_break_graph_apis


def ifelse_func(x, y):
    if x > 0:
        y = y + 1
    else:
        y = y + 2
    return y


class TestIfElse(TestCaseBase):
    def test_simple(self):
        x = paddle.to_tensor([1.0])
        y = paddle.to_tensor([2.0])
        self.assert_results(ifelse_func, x, y)


def multi_output(x: paddle.Tensor):
    m = x + 1
    if x > 0:
        return m
    else:
        return 2 * m


class TestExecutor(TestCaseBase):
    def test_simple(self):
        x = paddle.to_tensor(2)
        self.assert_results(multi_output, x)
        x = paddle.to_tensor(-2)
        self.assert_results(multi_output, x)


def print_break_graph(x, y):
    z = x + y
    print(x, z)
    out = y * z * 2
    return out


class TestPrint(TestCaseBase):
    def test_simple(self):
        x = paddle.to_tensor(2)
        y = paddle.to_tensor(3)
        self.assert_results(print_break_graph, x, y)


def to_tensor_break_graph(x, y):
    z = x + y
    out = y * paddle.to_tensor(2) * z
    return out


class TestToTensor(TestCaseBase):
    def test_simple(self):
        add_break_graph_apis([paddle.to_tensor])
        x = paddle.to_tensor(2)
        y = paddle.to_tensor(3)
        self.assert_results(to_tensor_break_graph, x, y)


def tensor_clear_gradient(x):
    x = paddle.to_tensor(x)
    x.clear_gradient()
    return x


class TestBreakGraphInResumeFn(TestCaseBase):
    def test_simple(self):
        x = paddle.to_tensor(2)
        self.assert_results(tensor_clear_gradient, x)


def inner_fn(a, b, c, d):
    return a + b * c - d


def multi_stack_args(a, b, c):
    out = inner_fn(a, b, c, paddle.to_tensor(4))
    return out


class TestMultiStackArgs(TestCaseBase):
    def test_simple(self):
        a = paddle.to_tensor(1)
        b = paddle.to_tensor(2)
        c = paddle.to_tensor(3)
        self.assert_results(multi_stack_args, a, b, c)


def break_graph_in_call_method(x):
    out = paddle.nn.functional.relu(paddle.to_tensor([4.0]))
    return x + out


def numpy_break_graph():
    a = paddle.to_tensor([1, 2])
    b = np.sum(a.numpy())
    print(b)
    return b


class TestBreakGraphInCallMethod(TestCaseBase):
    def test_simple(self):
        x = paddle.to_tensor([1.0])
        break_graph_in_call_method(x)
        x = paddle.to_tensor([2.0])
        break_graph_in_call_method(x)

        x = paddle.to_tensor([3.0])
        self.assert_results(break_graph_in_call_method, x)

    def test_numpy(self):
        self.assert_results(numpy_break_graph)


def test_break_graph_repeat(x):
    out = paddle.to_tensor(
        paddle.to_tensor(paddle.to_tensor(paddle.to_tensor([1.0])))
    )
    return x + out


class TestBreakGraphRepeat(TestCaseBase):
    def test_simple(self):
        x = paddle.to_tensor([1.0])
        test_break_graph_repeat(x)
        x = paddle.to_tensor([2.0])
        test_break_graph_repeat(x)

        x = paddle.to_tensor([3.0])
        self.assert_results(test_break_graph_repeat, x)


def break_graph_resume_pass_null(x, y):
    return paddle.add(x, y[0:50] if y is not None else None)


class TestBreakGraphResumePassNull(TestCaseBase):
    def test_break_graph_resume_pass_null(self):
        x = paddle.rand([50, 50], dtype=paddle.float32)
        y = paddle.rand([100, 50], dtype=paddle.float32)
        self.assert_results(break_graph_resume_pass_null, x, y)


if __name__ == "__main__":
    unittest.main()
