#
# SPDX-FileCopyrightText: Copyright (c) 1993-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# 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 os
import subprocess as sp

import numpy as np
import onnx
import onnx_graphsurgeon as gs
import onnxruntime
import pytest
from onnx_graphsurgeon.logger import G_LOGGER
from onnx_graphsurgeon.util import misc

ROOT_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), os.path.pardir))
EXAMPLES_ROOT = os.path.join(ROOT_DIR, "examples")


class Artifact(object):
    def __init__(self, name, infer=True):
        self.name = name
        self.infer = infer


EXAMPLES = [
    ("01_creating_a_model", [Artifact("test_globallppool.onnx")]),
    ("02_creating_a_model_with_initializer", [Artifact("test_conv.onnx")]),
    ("03_isolating_a_subgraph", [Artifact("model.onnx"), Artifact("subgraph.onnx")]),
    ("04_modifying_a_model", [Artifact("model.onnx"), Artifact("modified.onnx")]),
    ("05_folding_constants", [Artifact("model.onnx"), Artifact("folded.onnx")]),
    (
        "06_removing_nodes",
        [Artifact("model.onnx", infer=False), Artifact("removed.onnx")],
    ),
    ("07_creating_a_model_with_the_layer_api", [Artifact("model.onnx")]),
    ("08_replacing_a_subgraph", [Artifact("model.onnx"), Artifact("replaced.onnx")]),
    ("09_shape_operations_with_the_layer_api", [Artifact("model.onnx")]),
    ("10_dynamic_batch_size", [Artifact("model.onnx"), Artifact("dynamic.onnx")]),
    ("11_creating_a_local_function", [Artifact("model.onnx")]),
    # Skipping inference test as bf16 is not supported in ORT yet.
    (
        "12_using_numpy_unsupported_dtypes",
        [Artifact("test_conv_bf16.onnx", infer=False)],
    ),
]


# Extract any ``` blocks from the README
def load_commands_from_readme(readme):
    def ignore_command(cmd):
        return "pip" in cmd

    commands = []
    with open(readme, "r") as f:
        in_command_block = False
        for line in f.readlines():
            if not in_command_block and "```bash" in line:
                in_command_block = True
            elif in_command_block:
                if "```" in line:
                    in_command_block = False
                elif not ignore_command(line):
                    commands.append(line.strip())
    return commands


def infer_model(path):
    model = onnx.load(path)
    onnx.checker.check_model(model)

    graph = gs.import_onnx(model)

    feed_dict = {}
    for tensor in graph.inputs:
        shape = tuple(
            dim if not misc.is_dynamic_dimension(dim) else 1 for dim in tensor.shape
        )
        feed_dict[tensor.name] = np.random.random_sample(size=shape).astype(
            tensor.dtype
        )

    output_names = [out.name for out in graph.outputs]

    sess = onnxruntime.InferenceSession(
        model.SerializeToString(), providers=["CPUExecutionProvider"]
    )
    outputs = sess.run(output_names, feed_dict)
    G_LOGGER.info("Inference outputs: {:}".format(outputs))
    return outputs


@pytest.mark.parametrize("example_dir,artifacts", EXAMPLES)
def test_examples(example_dir, artifacts):
    example_dir = os.path.join(EXAMPLES_ROOT, example_dir)
    readme = os.path.join(example_dir, "README.md")
    commands = load_commands_from_readme(readme)
    for command in commands:
        G_LOGGER.info(command)
        assert (
            sp.run(
                ["bash", "-c", command], cwd=example_dir, env={"PYTHONPATH": ROOT_DIR}
            ).returncode
            == 0
        )

    for artifact in artifacts:
        artifact_path = os.path.join(example_dir, artifact.name)
        assert os.path.exists(artifact_path)
        if artifact.infer:
            assert infer_model(artifact_path)
        os.remove(artifact_path)
