// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#include "testPch.h"

#include "APITest.h"
#include "LearningModelBindingAPITest.h"
#include "SqueezeNetValidator.h"

#include <sstream>

using namespace winrt;
using namespace winml;
using namespace wfc;
using namespace wgi;
using namespace wm;
using namespace ws;

static void LearningModelBindingAPITestsClassSetup() {
  init_apartment();
#ifdef BUILD_INBOX
  winrt_activation_handler = WINRT_RoGetActivationFactory;
#endif
}

static void CpuSqueezeNet()
{
    std::string cpuInstance("CPU");
    WINML_EXPECT_NO_THROW(WinML::Engine::Test::ModelValidator::SqueezeNet(cpuInstance, LearningModelDeviceKind::Cpu, /*dataTolerance*/ 0.00001f, false));
}

static void CpuSqueezeNetEmptyOutputs()
{
    std::string cpuInstance("CPU");
    WINML_EXPECT_NO_THROW(
        WinML::Engine::Test::ModelValidator::SqueezeNet(
            cpuInstance,
            LearningModelDeviceKind::Cpu,
            /*dataTolerance*/ 0.00001f,
            false,
            OutputBindingStrategy::Empty);
    );
}

static void CpuSqueezeNetUnboundOutputs()
{
    std::string cpuInstance("CPU");
    WINML_EXPECT_NO_THROW(
        WinML::Engine::Test::ModelValidator::SqueezeNet(
            cpuInstance,
            LearningModelDeviceKind::Cpu,
            /*dataTolerance*/ 0.00001f,
            false,
            OutputBindingStrategy::Unbound);
    );
}

static void CpuSqueezeNetBindInputTensorAsInspectable()
{
    std::string cpuInstance("CPU");
    WINML_EXPECT_NO_THROW(
        WinML::Engine::Test::ModelValidator::SqueezeNet(
            cpuInstance,
            LearningModelDeviceKind::Cpu,
            /*dataTolerance*/ 0.00001f,
            false,
            OutputBindingStrategy::Bound /* empty outputs */,
            true /* bind inputs as inspectables */);
    );
}

static void CastMapInt64()
{
    WINML_EXPECT_NO_THROW(LearningModel::LoadFromFilePath(FileHelpers::GetModulePath() + L"castmap-int64.onnx"));
    // TODO: Check Descriptor
}

static void DictionaryVectorizerMapInt64()
{
    LearningModel learningModel = nullptr;
    WINML_EXPECT_NO_THROW(APITest::LoadModel(L"dictvectorizer-int64.onnx", learningModel));

    auto inputDescriptor = learningModel.InputFeatures().First().Current();
    WINML_EXPECT_TRUE(inputDescriptor.Kind() == LearningModelFeatureKind::Map);
    auto mapDescriptor = inputDescriptor.as<MapFeatureDescriptor>();
    WINML_EXPECT_TRUE(mapDescriptor.KeyKind() == TensorKind::Int64);
    WINML_EXPECT_TRUE(mapDescriptor.ValueDescriptor().Kind() == LearningModelFeatureKind::Tensor);
    auto tensorDescriptor = mapDescriptor.ValueDescriptor().as<TensorFeatureDescriptor>();
    // empty size means tensor of scalar value
    WINML_EXPECT_TRUE(tensorDescriptor.Shape().Size() == 0);
    WINML_EXPECT_TRUE(tensorDescriptor.TensorKind() == TensorKind::Float);

    LearningModelSession modelSession(learningModel);
    LearningModelBinding binding(modelSession);
    std::unordered_map<int64_t, float> map;
    map[1] = 1.f;
    map[10] = 10.f;
    map[3] = 3.f;


    auto mapInputName = inputDescriptor.Name();

    // Bind as IMap
    auto abiMap = winrt::single_threaded_map(std::move(map));
    binding.Bind(mapInputName, abiMap);
    auto mapInputInspectable = abiMap.as<wf::IInspectable>();
    auto first = binding.First();
    WINML_EXPECT_TRUE(first.Current().Key() == mapInputName);
    WINML_EXPECT_TRUE(first.Current().Value() == mapInputInspectable);
    WINML_EXPECT_TRUE(binding.Lookup(mapInputName) == mapInputInspectable);

    // Bind as IMapView
    auto mapView = abiMap.GetView();
    binding.Bind(mapInputName, mapView);
    mapInputInspectable = mapView.as<wf::IInspectable>();
    first = binding.First();
    WINML_EXPECT_TRUE(first.Current().Key() == mapInputName);
    WINML_EXPECT_TRUE(first.Current().Value() == mapView);
    WINML_EXPECT_TRUE(binding.Lookup(mapInputName) == mapView);

}

static void DictionaryVectorizerMapString()
{
    LearningModel learningModel = nullptr;
    WINML_EXPECT_NO_THROW(APITest::LoadModel(L"dictvectorizer-string.onnx", learningModel));

    auto inputDescriptor = learningModel.InputFeatures().First().Current();
    WINML_EXPECT_TRUE(inputDescriptor.Kind() == LearningModelFeatureKind::Map);

    auto mapDescriptor = inputDescriptor.as<MapFeatureDescriptor>();
    WINML_EXPECT_TRUE(mapDescriptor.KeyKind() == TensorKind::String);
    WINML_EXPECT_TRUE(mapDescriptor.ValueDescriptor().Kind() == LearningModelFeatureKind::Tensor);

    auto tensorDescriptor = mapDescriptor.ValueDescriptor().as<TensorFeatureDescriptor>();
    // empty size means tensor of scalar value
    WINML_EXPECT_TRUE(tensorDescriptor.Shape().Size() == 0);
    WINML_EXPECT_TRUE(tensorDescriptor.TensorKind() == TensorKind::Float);

    LearningModelSession modelSession(learningModel);
    LearningModelBinding binding(modelSession);
    std::unordered_map<winrt::hstring, float> map;
    map[L"1"] = 1.f;
    map[L"10"] = 10.f;
    map[L"2"] = 2.f;

    auto mapInputName = inputDescriptor.Name();
    auto abiMap = winrt::single_threaded_map(std::move(map));
    binding.Bind(mapInputName, abiMap);

    auto mapInputInspectable = abiMap.as<wf::IInspectable>();
    auto first = binding.First();
    WINML_EXPECT_TRUE(first.Current().Key() == mapInputName);
    WINML_EXPECT_TRUE(first.Current().Value() == mapInputInspectable);
    WINML_EXPECT_TRUE(binding.Lookup(mapInputName) == mapInputInspectable);

	modelSession.Evaluate(binding, L"");
}

static void RunZipMapInt64(
    winml::LearningModel model,
    OutputBindingStrategy bindingStrategy)
{
    auto outputFeatures = model.OutputFeatures();
    auto outputDescriptor = outputFeatures.First().Current();
    WINML_EXPECT_TRUE(outputDescriptor.Kind() == LearningModelFeatureKind::Sequence);

    auto seqDescriptor = outputDescriptor.as<SequenceFeatureDescriptor>();
    auto mapDescriptor = seqDescriptor.ElementDescriptor().as<MapFeatureDescriptor>();
    WINML_EXPECT_TRUE(mapDescriptor.KeyKind() == TensorKind::Int64);

    WINML_EXPECT_TRUE(mapDescriptor.ValueDescriptor().Kind() == LearningModelFeatureKind::Tensor);
    auto tensorDescriptor = mapDescriptor.ValueDescriptor().as<TensorFeatureDescriptor>();
    WINML_EXPECT_TRUE(tensorDescriptor.TensorKind() == TensorKind::Float);

    LearningModelSession session(model);
    LearningModelBinding binding(session);

    std::vector<float> inputs = { 0.5f, 0.25f, 0.125f };
    std::vector<int64_t> shape = { 1, 3 };

    // Bind inputs
    auto inputTensor =
        TensorFloat::CreateFromArray(
            shape,
            winrt::array_view<const float>(std::move(inputs)));
    binding.Bind(winrt::hstring(L"X"), inputTensor);

    typedef IMap<int64_t, float> ABIMap;
    typedef IVector<ABIMap> ABISequeneceOfMap;

    ABISequeneceOfMap abiOutput = nullptr;
    // Bind outputs
    if (bindingStrategy == OutputBindingStrategy::Bound)
    {
        abiOutput = winrt::single_threaded_vector<ABIMap>();
        binding.Bind(winrt::hstring(L"Y"), abiOutput);
    }

    // Evaluate
    auto result = session.Evaluate(binding, L"0").Outputs();

    if (bindingStrategy == OutputBindingStrategy::Bound)
    {
        // from output binding
        const auto &out1 = abiOutput.GetAt(0);
        const auto &out2 = result.Lookup(L"Y").as<IVectorView<ABIMap>>().GetAt(0);
        WINML_LOG_COMMENT((std::ostringstream() << "size: " << out1.Size()).str());
        // check outputs
        auto iter1 = out1.First();
        auto iter2 = out2.First();
        for (uint32_t i = 0, size = (uint32_t)inputs.size(); i < size; ++i)
        {
            WINML_EXPECT_TRUE(iter1.HasCurrent());
            WINML_EXPECT_TRUE(iter2.HasCurrent());
            const auto &pair1 = iter1.Current();
            const auto &pair2 = iter2.Current();
            WINML_LOG_COMMENT((std::ostringstream() << "key: " << pair1.Key() << ", value: " << pair2.Value()).str());
            WINML_EXPECT_TRUE(pair1.Key() == i && pair2.Key() == i);
            WINML_EXPECT_TRUE(pair1.Value() == inputs[i] && pair2.Value() == inputs[i]);
            iter1.MoveNext();
            iter2.MoveNext();
        }
        WINML_EXPECT_TRUE(!iter1.HasCurrent());
        WINML_EXPECT_TRUE(!iter2.HasCurrent());
    }
    else
    {
        abiOutput = result.Lookup(L"Y").as<ABISequeneceOfMap>();
        WINML_EXPECT_TRUE(abiOutput.Size() == 1);
        ABIMap map = abiOutput.GetAt(0);
        WINML_EXPECT_TRUE(map.Size() == 3);
        WINML_EXPECT_TRUE(map.Lookup(0) == 0.5);
        WINML_EXPECT_TRUE(map.Lookup(1) == .25);
        WINML_EXPECT_TRUE(map.Lookup(2) == .125);
    }
}

static void ZipMapInt64()
{
    LearningModel learningModel= nullptr;
    WINML_EXPECT_NO_THROW(APITest::LoadModel(L"zipmap-int64.onnx", learningModel));
    RunZipMapInt64(learningModel, OutputBindingStrategy::Bound);
}

static void ZipMapInt64Unbound()
{
    LearningModel learningModel = nullptr;
    WINML_EXPECT_NO_THROW(APITest::LoadModel(L"zipmap-int64.onnx", learningModel));
    RunZipMapInt64(learningModel, OutputBindingStrategy::Unbound);
}

static void ZipMapString()
{
    // output constraint: "seq(map(string, float))" or "seq(map(int64, float))"
    LearningModel learningModel = nullptr;
    WINML_EXPECT_NO_THROW(APITest::LoadModel(L"zipmap-string.onnx", learningModel));
    auto outputs = learningModel.OutputFeatures();
    auto outputDescriptor = outputs.First().Current();
    WINML_EXPECT_TRUE(outputDescriptor.Kind() == LearningModelFeatureKind::Sequence);
    auto mapDescriptor = outputDescriptor.as<SequenceFeatureDescriptor>().ElementDescriptor().as<MapFeatureDescriptor>();
    WINML_EXPECT_TRUE(mapDescriptor.KeyKind() == TensorKind::String);
    WINML_EXPECT_TRUE(mapDescriptor.ValueDescriptor().Kind() == LearningModelFeatureKind::Tensor);
    auto tensorDescriptor = mapDescriptor.ValueDescriptor().as<TensorFeatureDescriptor>();
    WINML_EXPECT_TRUE(tensorDescriptor.TensorKind() == TensorKind::Float);

    LearningModelSession session(learningModel);
    LearningModelBinding binding(session);

    std::vector<float> inputs = { 0.5f, 0.25f, 0.125f };
    std::vector<int64_t> shape = { 1, 3 };
    std::vector<winrt::hstring> labels = { L"cat", L"dog", L"lion" };
    std::map<winrt::hstring, float> mapData = { { L"cat", 0.0f }, { L"dog", 0.0f }, { L"lion", 0.0f } };
    typedef IMap<winrt::hstring, float> ABIMap;
    ABIMap abiMap = winrt::single_threaded_map<winrt::hstring, float>(std::move(mapData));
    std::vector<ABIMap> seqOutput = { abiMap };
    IVector<ABIMap> ABIOutput = winrt::single_threaded_vector<ABIMap>(std::move(seqOutput));

    TensorFloat inputTensor = TensorFloat::CreateFromArray(shape, winrt::array_view<const float>(std::move(inputs)));
    binding.Bind(winrt::hstring(L"X"), inputTensor);
    binding.Bind(winrt::hstring(L"Y"), ABIOutput);
    auto result = session.Evaluate(binding, L"0").Outputs();
    // from output binding
    const auto &out1 = ABIOutput.GetAt(0);
    const auto &out2 = result.Lookup(L"Y").as<IVectorView<ABIMap>>().GetAt(0);
    WINML_LOG_COMMENT((std::ostringstream() << "size: " << out1.Size()).str());
    // single key,value pair for each map
    auto iter1 = out1.First();
    auto iter2 = out2.First();
    for (uint32_t i = 0, size = (uint32_t)inputs.size(); i < size; ++i)
    {
        WINML_EXPECT_TRUE(iter2.HasCurrent());
        const auto &pair1 = iter1.Current();
        const auto &pair2 = iter2.Current();
        WINML_LOG_COMMENT((std::ostringstream() << "key: " << pair1.Key().c_str() << ", value " << pair2.Value()).str());
        WINML_EXPECT_TRUE(std::wstring(pair1.Key().c_str()).compare(labels[i]) == 0);
        WINML_EXPECT_TRUE(std::wstring(pair2.Key().c_str()).compare(labels[i]) == 0);
        WINML_EXPECT_TRUE(pair1.Value() == inputs[i] && pair2.Value() == inputs[i]);
        iter1.MoveNext();
        iter2.MoveNext();
    }
    WINML_EXPECT_TRUE(!iter1.HasCurrent());
    WINML_EXPECT_TRUE(!iter2.HasCurrent());
}

static void GpuSqueezeNet()
{
    std::string gpuInstance("GPU");
    WINML_EXPECT_NO_THROW(
        WinML::Engine::Test::ModelValidator::SqueezeNet(
            gpuInstance,
            LearningModelDeviceKind::DirectX,
            /*dataTolerance*/ 0.00001f);
    );
}

static void GpuSqueezeNetEmptyOutputs()
{
    std::string gpuInstance("GPU");
    WINML_EXPECT_NO_THROW(
        WinML::Engine::Test::ModelValidator::SqueezeNet(
            gpuInstance,
            LearningModelDeviceKind::DirectX,
            /*dataTolerance*/ 0.00001f,
            false,
            OutputBindingStrategy::Empty);
    );
}

static void GpuSqueezeNetUnboundOutputs()
{
    std::string gpuInstance("GPU");
    WINML_EXPECT_NO_THROW(
        WinML::Engine::Test::ModelValidator::SqueezeNet(
            gpuInstance,
            LearningModelDeviceKind::DirectX,
            /*dataTolerance*/ 0.00001f,
            false,
            OutputBindingStrategy::Unbound);
    );
}

// Validates that when the input image is the same as the model expects, the binding step is executed correctly.
static void ImageBindingDimensions()
{
    LearningModelBinding learningModelBinding = nullptr;
    LearningModel learningModel = nullptr;
    LearningModelSession learningModelSession = nullptr;
    LearningModelDevice leraningModelDevice = nullptr;
    std::wstring filePath = FileHelpers::GetModulePath() + L"model.onnx";
    // load a model with expected input size: 224 x 224
    WINML_EXPECT_NO_THROW(leraningModelDevice = LearningModelDevice(LearningModelDeviceKind::Default));
    WINML_EXPECT_NO_THROW(learningModel = LearningModel::LoadFromFilePath(filePath));
    WINML_EXPECT_TRUE(learningModel != nullptr);
    WINML_EXPECT_NO_THROW(learningModelSession = LearningModelSession(learningModel, leraningModelDevice));
    WINML_EXPECT_NO_THROW(learningModelBinding = LearningModelBinding(learningModelSession));

    // Create input images and execute bind
    // Test Case 1: both width and height are larger than model expects
    VideoFrame inputImage1(BitmapPixelFormat::Rgba8, 1000, 1000);
    ImageFeatureValue inputTensor = ImageFeatureValue::CreateFromVideoFrame(inputImage1);
    WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"data_0", inputTensor));

    // Test Case 2: only height is larger, while width is smaller
    VideoFrame inputImage2(BitmapPixelFormat::Rgba8, 20, 1000);
    inputTensor = ImageFeatureValue::CreateFromVideoFrame(inputImage2);
    WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"data_0", inputTensor));

    // Test Case 3: only width is larger, while height is smaller
    VideoFrame inputImage3(BitmapPixelFormat::Rgba8, 1000, 20);
    inputTensor = ImageFeatureValue::CreateFromVideoFrame(inputImage3);
    WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"data_0", inputTensor));

    // Test Case 4: both width and height are smaller than model expects
    VideoFrame inputImage4(BitmapPixelFormat::Rgba8, 20, 20);
    inputTensor = ImageFeatureValue::CreateFromVideoFrame(inputImage4);
    WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"data_0", inputTensor));
}

static void VerifyInvalidBindExceptions()
{
    LearningModel learningModel = nullptr;
  WINML_EXPECT_NO_THROW(APITest::LoadModel(L"zipmap-int64.onnx", learningModel));

    LearningModelSession session(learningModel);
    LearningModelBinding binding(session);

    std::vector<float> inputs = { 0.5f, 0.25f, 0.125f };
    std::vector<int64_t> shape = { 1, 3 };

    auto matchException =
        [](const winrt::hresult_error& e, HRESULT hr) -> bool
    {
        return e.code() == hr;
    };

    auto ensureWinmlSizeMismatch = std::bind(matchException, std::placeholders::_1, WINML_ERR_SIZE_MISMATCH);
    auto ensureWinmlInvalidBinding = std::bind(matchException, std::placeholders::_1, WINML_ERR_INVALID_BINDING);

    /*
        Verify tensor bindings throw correct bind exceptions
    */

    // Bind invalid image as tensorfloat input
    auto image = FileHelpers::LoadImageFeatureValue(L"227x227.png");
    WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"X", image), winrt::hresult_error, ensureWinmlSizeMismatch);

    // Bind invalid map as tensorfloat input
    std::unordered_map<float, float> map;
    auto abiMap = winrt::single_threaded_map(std::move(map));
    WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"X", abiMap), winrt::hresult_error, ensureWinmlInvalidBinding);

    // Bind invalid sequence as tensorfloat input
    std::vector<uint32_t> sequence;
    auto abiSequence = winrt::single_threaded_vector(std::move(sequence));
    WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"X", abiSequence), winrt::hresult_error, ensureWinmlInvalidBinding);

    // Bind invalid tensor size as tensorfloat input
    auto tensorBoolean = TensorBoolean::Create();
    WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"X", tensorBoolean), winrt::hresult_error, ensureWinmlInvalidBinding);

    // Bind invalid tensor shape as tensorfloat input
    auto tensorInvalidShape = TensorFloat::Create(std::vector<int64_t> { 2, 3, 4 });
    WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"X", tensorInvalidShape), winrt::hresult_error, ensureWinmlInvalidBinding);

    /*
        Verify sequence bindings throw correct bind exceptions
    */

    // Bind invalid image as sequence<map<int, float> output
    WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"Y", image), winrt::hresult_error, ensureWinmlInvalidBinding);

    // Bind invalid map as sequence<map<int, float> output
    WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"Y", abiMap), winrt::hresult_error, ensureWinmlInvalidBinding);

    // Bind invalid sequence<int> as sequence<map<int, float> output
    WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"Y", abiSequence), winrt::hresult_error, ensureWinmlInvalidBinding);

    // Bind invalid tensor as sequence<map<int, float> output
    WINML_EXPECT_THROW_SPECIFIC(binding.Bind(L"Y", tensorBoolean), winrt::hresult_error, ensureWinmlInvalidBinding);

    /*
        Verify image bindings throw correct bind exceptions
    */

    // WINML_EXPECT_NO_THROW(LoadModel(L"fns-candy.onnx"));

    // LearningModelSession imageSession(m_model);
    // LearningModelBinding imageBinding(imageSession);

    // auto inputName = m_model.InputFeatures().First().Current().Name();

    // // Bind invalid map as image input
    // WINML_EXPECT_THROW_SPECIFIC(imageBinding.Bind(inputName, abiMap), winrt::hresult_error, ensureWinmlInvalidBinding);

    // // Bind invalid sequence as image input
    // WINML_EXPECT_THROW_SPECIFIC(imageBinding.Bind(inputName, abiSequence), winrt::hresult_error, ensureWinmlInvalidBinding);

    // // Bind invalid tensor type as image input
    // WINML_EXPECT_THROW_SPECIFIC(imageBinding.Bind(inputName, tensorBoolean), winrt::hresult_error, ensureWinmlInvalidBinding);

    // // Bind invalid tensor size as image input
    // auto tensorFloat = TensorFloat::Create(std::vector<int64_t> { 1, 1, 100, 100 });
    // WINML_EXPECT_THROW_SPECIFIC(imageBinding.Bind(inputName, tensorFloat), winrt::hresult_error, ensureWinmlInvalidBinding);

    // // Bind invalid tensor shape as image input
    // WINML_EXPECT_THROW_SPECIFIC(imageBinding.Bind(inputName, tensorInvalidShape), winrt::hresult_error, ensureWinmlInvalidBinding);

    /*
        Verify map bindings throw correct bind exceptions
    */
    WINML_EXPECT_NO_THROW(APITest::LoadModel(L"dictvectorizer-int64.onnx", learningModel));

    LearningModelSession mapSession(learningModel);
    LearningModelBinding mapBinding(mapSession);

    auto inputName = learningModel.InputFeatures().First().Current().Name();

    // Bind invalid image as image input
    auto smallImage = FileHelpers::LoadImageFeatureValue(L"100x100.png");
    WINML_EXPECT_THROW_SPECIFIC(mapBinding.Bind(inputName, smallImage), winrt::hresult_error, ensureWinmlInvalidBinding);

    // Bind invalid map as image input
    WINML_EXPECT_THROW_SPECIFIC(mapBinding.Bind(inputName, abiMap), winrt::hresult_error, ensureWinmlInvalidBinding);

    // Bind invalid sequence as image input
    WINML_EXPECT_THROW_SPECIFIC(mapBinding.Bind(inputName, abiSequence), winrt::hresult_error, ensureWinmlInvalidBinding);

    // Bind invalid tensor type as image input
    WINML_EXPECT_THROW_SPECIFIC(mapBinding.Bind(inputName, tensorBoolean), winrt::hresult_error, ensureWinmlInvalidBinding);
}

// Verify that it throws an error when binding an invalid name.
static void BindInvalidInputName()
{
    LearningModel learningModel = nullptr;
    LearningModelBinding learningModelBinding = nullptr;
    LearningModelDevice learningModelDevice = nullptr;
    LearningModelSession learningModelSession = nullptr;
    std::wstring modelPath = FileHelpers::GetModulePath() + L"Add_ImageNet1920.onnx";
    WINML_EXPECT_NO_THROW(learningModel = LearningModel::LoadFromFilePath(modelPath));
    WINML_EXPECT_TRUE(learningModel != nullptr);
    WINML_EXPECT_NO_THROW(learningModelDevice = LearningModelDevice(LearningModelDeviceKind::Default));
    WINML_EXPECT_NO_THROW(learningModelSession = LearningModelSession(learningModel, learningModelDevice));
    WINML_EXPECT_NO_THROW(learningModelBinding = LearningModelBinding(learningModelSession));

    VideoFrame iuputImage(BitmapPixelFormat::Rgba8, 1920, 1080);
    ImageFeatureValue inputTensor = ImageFeatureValue::CreateFromVideoFrame(iuputImage);

    auto first = learningModel.InputFeatures().First();
    std::wstring testInvalidName = L"0";

    // Verify that testInvalidName is not in model's InputFeatures
    while (first.HasCurrent())
    {
        WINML_EXPECT_NOT_EQUAL(testInvalidName, first.Current().Name());
        first.MoveNext();
    }

    // Bind inputTensor to a valid input name
    WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"input_39:0", inputTensor));

    // Bind inputTensor to an invalid input name
    WINML_EXPECT_THROW_SPECIFIC(learningModelBinding.Bind(testInvalidName, inputTensor),
        winrt::hresult_error,
        [](const winrt::hresult_error& e) -> bool
    {
        return e.code() == WINML_ERR_INVALID_BINDING;
    });
}

static void VerifyOutputAfterEvaluateAsyncCalledTwice()
{
    LearningModel learningModel = nullptr;
    LearningModelBinding learningModelBinding = nullptr;
    LearningModelDevice learningModelDevice = nullptr;
    LearningModelSession learningModelSession = nullptr;
    std::wstring filePath = FileHelpers::GetModulePath() + L"relu.onnx";
    WINML_EXPECT_NO_THROW(learningModelDevice = LearningModelDevice(LearningModelDeviceKind::Default));
    WINML_EXPECT_NO_THROW(learningModel = LearningModel::LoadFromFilePath(filePath));
    WINML_EXPECT_TRUE(learningModel != nullptr);
    WINML_EXPECT_NO_THROW(learningModelSession = LearningModelSession(learningModel, learningModelDevice));
    WINML_EXPECT_NO_THROW(learningModelBinding = LearningModelBinding(learningModelSession));

    auto inputShape = std::vector<int64_t>{ 5 };
    auto inputData1 = std::vector<float>{ -50.f, -25.f, 0.f, 25.f, 50.f };
    auto inputValue1 =
        TensorFloat::CreateFromIterable(
            inputShape,
            single_threaded_vector<float>(std::move(inputData1)).GetView());

    auto inputData2 = std::vector<float>{ 50.f, 25.f, 0.f, -25.f, -50.f };
    auto inputValue2 =
        TensorFloat::CreateFromIterable(
            inputShape,
            single_threaded_vector<float>(std::move(inputData2)).GetView());

    WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"X", inputValue1));

    auto outputValue = TensorFloat::Create();
    WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"Y", outputValue));

    WINML_EXPECT_NO_THROW(learningModelSession.Evaluate(learningModelBinding, L""));

    auto buffer1 = outputValue.GetAsVectorView();
    WINML_EXPECT_TRUE(buffer1 != nullptr);

    // The second evaluation
    // If we don't bind output again, the output value will not change
    WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"X", inputValue2));
    WINML_EXPECT_NO_THROW(learningModelSession.Evaluate(learningModelBinding, L""));
    auto buffer2 = outputValue.GetAsVectorView();
    WINML_EXPECT_EQUAL(buffer1.Size(), buffer2.Size());
    bool isSame = true;
    for (uint32_t i = 0; i < buffer1.Size(); ++i)
    {
        if (buffer1.GetAt(i) != buffer2.GetAt(i))
        {
            isSame = false;
            break;
        }
    }
    WINML_EXPECT_FALSE(isSame);
}

static VideoFrame CreateVideoFrame(const wchar_t* path)
{
    auto imagefile = StorageFile::GetFileFromPathAsync(path).get();
    auto stream = imagefile.OpenAsync(FileAccessMode::Read).get();
    auto decoder = BitmapDecoder::CreateAsync(stream).get();
    auto softwareBitmap = decoder.GetSoftwareBitmapAsync().get();
    return VideoFrame::CreateWithSoftwareBitmap(softwareBitmap);
}

static void VerifyOutputAfterImageBindCalledTwice()
{
    std::wstring fullModelPath = FileHelpers::GetModulePath() + L"model.onnx";
    std::wstring fullImagePath1 = FileHelpers::GetModulePath() + L"kitten_224.png";
    std::wstring fullImagePath2 = FileHelpers::GetModulePath() + L"fish.png";

    // winml model creation
    LearningModel model = nullptr;
    WINML_EXPECT_NO_THROW(model = LearningModel::LoadFromFilePath(fullModelPath));
    LearningModelSession modelSession = nullptr;
    WINML_EXPECT_NO_THROW(modelSession = LearningModelSession(model, LearningModelDevice(LearningModelDeviceKind::Default)));
    LearningModelBinding modelBinding(modelSession);

    // create the tensor for the actual output
    auto output = TensorFloat::Create();
    modelBinding.Bind(L"softmaxout_1", output);

    // Bind image 1 and evaluate
    auto frame = CreateVideoFrame(fullImagePath1.c_str());
    auto imageTensor = ImageFeatureValue::CreateFromVideoFrame(frame);
    WINML_EXPECT_NO_THROW(modelBinding.Bind(L"data_0", imageTensor));
    WINML_EXPECT_NO_THROW(modelSession.Evaluate(modelBinding, L""));

    // Store 1st result
    auto outputVectorView1 = output.GetAsVectorView();

    // Bind image 2 and evaluate
    // In this scenario, the backing videoframe is updated, and the imagefeaturevalue is rebound.
    // The expected result is that the videoframe will be re-tensorized at bind
    auto frame2 = CreateVideoFrame(fullImagePath2.c_str());
    frame2.CopyToAsync(frame).get();
    WINML_EXPECT_NO_THROW(modelBinding.Bind(L"data_0", imageTensor));
    WINML_EXPECT_NO_THROW(modelSession.Evaluate(modelBinding, L""));

    // Store 2nd result
    auto outputVectorView2 = output.GetAsVectorView();

    WINML_EXPECT_EQUAL(outputVectorView1.Size(), outputVectorView2.Size());
    bool isSame = true;
    for (uint32_t i = 0; i < outputVectorView1.Size(); ++i)
    {
        if (outputVectorView1.GetAt(i) != outputVectorView2.GetAt(i))
        {
            isSame = false;
            break;
        }
    }
    WINML_EXPECT_FALSE(isSame);
}

static void SequenceLengthTensorFloat()
{
  // Tests sequence of tensor float as an input
  LearningModel learningModel = nullptr;
  LearningModelBinding learningModelBinding = nullptr;
  LearningModelDevice learningModelDevice = nullptr;
  LearningModelSession learningModelSession = nullptr;
  std::wstring filePath = FileHelpers::GetModulePath() + L"sequence_length.onnx";
  WINML_EXPECT_NO_THROW(learningModelDevice = LearningModelDevice(LearningModelDeviceKind::Default));
  WINML_EXPECT_NO_THROW(learningModel = LearningModel::LoadFromFilePath(filePath));
  WINML_EXPECT_TRUE(learningModel != nullptr);
  WINML_EXPECT_NO_THROW(learningModelSession = LearningModelSession(learningModel, learningModelDevice));
  WINML_EXPECT_NO_THROW(learningModelBinding = LearningModelBinding(learningModelSession));

  auto input = winrt::single_threaded_vector<TensorFloat>();
  for (int i = 0; i < 3; i++){
    std::vector<int64_t> shape = {5, 3*i + 1};
    std::vector<float> data(static_cast<size_t>(std::accumulate(shape.begin(), shape.end(), static_cast<int64_t>(1), std::multiplies())));
    input.Append(TensorFloat::CreateFromShapeArrayAndDataArray(shape, data));
  }

  WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"X", input));
  auto results = learningModelSession.Evaluate(learningModelBinding, L"");

  WINML_EXPECT_EQUAL(3, results.Outputs().Lookup(L"Y").as<TensorInt64Bit>().GetAsVectorView().GetAt(0));
}

static void SequenceConstructTensorString()
{
  LearningModel learningModel = nullptr;
  LearningModelBinding learningModelBinding = nullptr;
  LearningModelDevice learningModelDevice = nullptr;
  LearningModelSession learningModelSession = nullptr;
  std::wstring filePath = FileHelpers::GetModulePath() + L"sequence_construct.onnx";
  WINML_EXPECT_NO_THROW(learningModelDevice = LearningModelDevice(LearningModelDeviceKind::Default));
  WINML_EXPECT_NO_THROW(learningModel = LearningModel::LoadFromFilePath(filePath));
  WINML_EXPECT_TRUE(learningModel != nullptr);
  WINML_EXPECT_NO_THROW(learningModelSession = LearningModelSession(learningModel, learningModelDevice));
  WINML_EXPECT_NO_THROW(learningModelBinding = LearningModelBinding(learningModelSession));

  std::vector<int64_t> shape1 = {2, 3};
  std::vector<int64_t> data1(static_cast<size_t>(std::accumulate(shape1.begin(), shape1.end(), static_cast<int64_t>(1), std::multiplies())));
  auto input1 = TensorInt64Bit::CreateFromShapeArrayAndDataArray(shape1, data1);
  std::vector<int64_t> shape2 = {2, 3};
  std::vector<int64_t> data2(static_cast<size_t>(std::accumulate(shape2.begin(), shape2.end(), static_cast<int64_t>(1), std::multiplies())));
  auto input2 = TensorInt64Bit::CreateFromShapeArrayAndDataArray(shape2, data2);

  WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"tensor1", input1));
  WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"tensor2", input2));
  auto results = learningModelSession.Evaluate(learningModelBinding, L"");

  auto output_sequence = results.Outputs().Lookup(L"output_sequence").as<wfc::IVectorView<TensorInt64Bit>>();
  WINML_EXPECT_EQUAL(static_cast<uint32_t>(2), output_sequence.Size());
  WINML_EXPECT_EQUAL(2, output_sequence.GetAt(0).Shape().GetAt(0));
  WINML_EXPECT_EQUAL(3, output_sequence.GetAt(0).Shape().GetAt(1));
  WINML_EXPECT_EQUAL(2, output_sequence.GetAt(1).Shape().GetAt(0));
  WINML_EXPECT_EQUAL(3, output_sequence.GetAt(1).Shape().GetAt(1));

  auto bound_output_sequence = winrt::single_threaded_vector<TensorInt64Bit> ();
  WINML_EXPECT_NO_THROW(learningModelBinding.Bind(L"output_sequence", bound_output_sequence));
  WINML_EXPECT_NO_THROW(learningModelSession.Evaluate(learningModelBinding, L""));
  WINML_EXPECT_EQUAL(static_cast<uint32_t>(2), bound_output_sequence.Size());
  WINML_EXPECT_EQUAL(2, bound_output_sequence.GetAt(0).Shape().GetAt(0));
  WINML_EXPECT_EQUAL(3, bound_output_sequence.GetAt(0).Shape().GetAt(1));
  WINML_EXPECT_EQUAL(2, bound_output_sequence.GetAt(1).Shape().GetAt(0));
  WINML_EXPECT_EQUAL(3, bound_output_sequence.GetAt(1).Shape().GetAt(1));
}

const LearningModelBindingAPITestsApi& getapi() {
  static LearningModelBindingAPITestsApi api =
  {
    LearningModelBindingAPITestsClassSetup,
    CpuSqueezeNet,
    CpuSqueezeNetEmptyOutputs,
    CpuSqueezeNetUnboundOutputs,
    CpuSqueezeNetBindInputTensorAsInspectable,
    CastMapInt64,
    DictionaryVectorizerMapInt64,
    DictionaryVectorizerMapString,
    ZipMapInt64,
    ZipMapInt64Unbound,
    ZipMapString,
    GpuSqueezeNet,
    GpuSqueezeNetEmptyOutputs,
    GpuSqueezeNetUnboundOutputs,
    ImageBindingDimensions,
    VerifyInvalidBindExceptions,
    BindInvalidInputName,
    VerifyOutputAfterEvaluateAsyncCalledTwice,
    VerifyOutputAfterImageBindCalledTwice,
    SequenceLengthTensorFloat,
    SequenceConstructTensorString
  };

  if (SkipGpuTests()) {
    api.GpuSqueezeNet = SkipTest;
    api.GpuSqueezeNetEmptyOutputs = SkipTest;
    api.GpuSqueezeNetUnboundOutputs = SkipTest;
  }
  if (RuntimeParameterExists(L"noVideoFrameTests")) {
    api.ImageBindingDimensions = SkipTest;
    api.BindInvalidInputName = SkipTest;
    api.VerifyOutputAfterImageBindCalledTwice = SkipTest;
    api.VerifyInvalidBindExceptions = SkipTest;
  }
  return api;
}
