from functools import partial

import torch

from colossalai.fx.tracer.meta_patch import patched_function
from colossalai.testing import clear_cache_before_run


def _run(data, patch_fn):
    try:
        output = patch_fn(data)
        return output
    except Exception as e:
        return e


def _assert_output_shape(data, patch_fn, expect_exception, output_shape):
    output = _run(data, patch_fn)

    if expect_exception:
        assert isinstance(output, AssertionError)
    else:
        assert not isinstance(output, Exception)
        assert output.is_meta
        assert output.shape == output_shape


@clear_cache_before_run()
def test_repeat_interleave():
    patch_fn = patched_function.torch_repeat_interleave

    # examples from https://pytorch.org/docs/stable/generated/torch.repeat_interleave.html
    data = torch.tensor([1, 2, 3])
    materialized_output = torch.repeat_interleave(data, repeats=2)
    repeat_interleave = partial(patch_fn, repeats=2)
    meta_data = data.to('meta')
    _assert_output_shape(data=meta_data,
                         patch_fn=repeat_interleave,
                         expect_exception=False,
                         output_shape=materialized_output.shape)

    data = torch.tensor([[1, 2], [3, 4]])
    materialized_output = torch.repeat_interleave(data, repeats=3, dim=1)
    repeat_interleave = partial(patch_fn, repeats=3, dim=1)
    meta_data = data.to('meta')
    _assert_output_shape(data=meta_data,
                         patch_fn=repeat_interleave,
                         expect_exception=False,
                         output_shape=materialized_output.shape)

    data = torch.tensor([[1, 2], [3, 4]])
    materialized_output = torch.repeat_interleave(data, repeats=torch.tensor([1, 2]), dim=-1)
    repeat_interleave = partial(patch_fn, repeats=torch.tensor([1, 2]), dim=-1)
    meta_data = data.to('meta')
    _assert_output_shape(data=meta_data,
                         patch_fn=repeat_interleave,
                         expect_exception=False,
                         output_shape=materialized_output.shape)

    data = torch.tensor([[1, 2], [3, 4]])
    materialized_output = torch.repeat_interleave(data, repeats=torch.tensor([1, 2]), dim=0)
    repeat_interleave = partial(patch_fn, repeats=[1, 2], dim=0)
    meta_data = data.to('meta')
    _assert_output_shape(data=meta_data,
                         patch_fn=repeat_interleave,
                         expect_exception=True,
                         output_shape=materialized_output.shape)


@clear_cache_before_run()
def test_torch_max():
    data = torch.rand(4, 3)
    out = torch.max(data)
    patched_out = patched_function.torch_max(data)
    assert out.shape == patched_out.shape

    data = torch.rand(4, 3, 2)
    out, idx = torch.max(data, dim=1)
    patched_out, patched_idx = patched_function.torch_max(data, dim=1)
    assert out.shape == patched_out.shape
    assert idx.shape == patched_idx.shape

    data = torch.rand(4, 3, 2)
    out, idx = torch.max(data, dim=1, keepdim=True)
    patched_out, patched_idx = patched_function.torch_max(data, dim=1, keepdim=True)
    assert out.shape == patched_out.shape
    assert idx.shape == patched_idx.shape
