// CPPMatrixMultiply.cpp : Defines the exported functions for the DLL application.

/*
Copyright 2015 Amazon.com, Inc.or its affiliates. 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.
A copy of the License is located at http ://aws.amazon.com/apache2.0/
or in the "license" file accompanying this file.
This file 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 "stdafx.h"
#include <array>
#include "assert.h"
#include "amp.h"
#include "ppl.h"
#include <vector>

using namespace concurrency;

enum Algorithms 			// this must exactly duplicate enum in C#
{
	CSharp_Basic = 0,
	CSharp_ImprovedSerial = 1,
	CSharp_TPL = 2,
	CPP_Basic = 3,
	CPP_PPL = 4,
	CPP_AMP = 5,
	CPP_AMPTiling = 6
};

void MatrixMultiplyBasic(float A[], float B[], float C[], int N)
{
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < N; j++)
		{
			float sum = 0.0;
			for (int k = 0; k < N; k++)
			{
				sum += A[i*N + k] * B[k*N + j];
			}
			C[i*N + j] = sum;
		}
	}
}

// This function was developed by Microsoft Corporation and is licensed under Microsoft MSPL, 
// available here : http ://opensource.org/licenses/ms-pl

void MatrixMultiplyPPL(float A[], float B[], float C[], int N)
{
	parallel_for(0, N, [&](int i)
	{
		for (int j = 0; j < N; j++)
		{
			float sum = 0.0;
			for (int k = 0; k < N; k++)
			{
				sum += A[i*N + k] * B[k*N + j];
			}
			C[i*N + j] = sum;
		}
	});
}

// This function was developed by Microsoft Corporation and is licensed under Microsoft MSPL, 
// available here : http ://opensource.org/licenses/ms-pl

void MatrixMultiplyAMP(float A[], float B[], float C[], int N)
{
	extent<2> e_a(N, N), e_b(N, N), e_c(N, N);

	array_view<float, 2> a(e_a, A);
	array_view<float, 2> b(e_b, B);
	array_view<float, 2> c(e_c, C);
	c.discard_data();		// avoid copying memory to GPU

	parallel_for_each(c.extent, [=](index<2> idx) restrict(amp)
	{
		int row = idx[0];
		int col = idx[1];
		float sum = 0;
		for (int inner = 0; inner < N; inner++)
		{
			index<2> idx_a(idx[0], inner);
			index<2> idx_b(inner, idx[1]);
			sum += a[idx_a] * b[idx_b];
		}
		c[idx] = sum;
	});

	c.synchronize();
}

// This function was developed by Microsoft Corporation and is licensed under Microsoft MSPL, 
// available here : http ://opensource.org/licenses/ms-pl

const int TILESIZE = 8;  // array size passed in must be a multiple of TILESIZE

void MatrixMultiplyTiling(float A[], float B[], float C[], int N)
{
	assert((N % TILESIZE) == 0);
	array_view<const float, 2> a(N, N, A);
	array_view<const float, 2> b(N, N, B);
	array_view<float, 2> c(N, N, C);
	c.discard_data();

	parallel_for_each(c.extent.tile<TILESIZE, TILESIZE>(), 
		[=](tiled_index<TILESIZE, TILESIZE> t_idx) restrict(amp)
	{
		int row = t_idx.local[0];
		int col = t_idx.local[1];
		tile_static float locA[TILESIZE][TILESIZE];
		tile_static float locB[TILESIZE][TILESIZE];
		float sum = 0;
		for (int i = 0; i < a.extent[1]; i += TILESIZE)
		{
			locA[row][col] = a(t_idx.global[0], col + i);
			locB[row][col] = b(row + i, t_idx.global[1]);
			t_idx.barrier.wait();

			for (int k = 0; k < TILESIZE; k++)
				sum += locA[row][k] * locB[k][col];
			t_idx.barrier.wait();

		}
		c[t_idx.global] = sum;
	});
	c.synchronize();
}

// convert std::string to wstring
std::wstring stows(std::string input)
{
	std::wstring result;
	for (char x : input)
		result += x;
	return result;
}

extern "C" __declspec (dllexport) bool _stdcall
CallCPPMatrixMultiply(int algorithm, float A[], float B[], float C[], 
						int N, wchar_t* error, size_t errsize)
{
	try
	{
		switch (algorithm)
		{
		case Algorithms::CPP_Basic:
			MatrixMultiplyBasic(A, B, C, N); break;
		case Algorithms::CPP_PPL:
			MatrixMultiplyPPL(A, B, C, N); break;
		case Algorithms::CPP_AMP:
			MatrixMultiplyAMP(A, B, C, N); break;
		case Algorithms::CPP_AMPTiling:
			MatrixMultiplyTiling(A, B, C, N); break;
		default:
			wcscpy_s(error, errsize, L"Invalid C++ algorithm index.");
			return false;
		}
	}
	catch (concurrency::runtime_exception& ex)
	{
		std::wstring result = stows(ex.what());
		wcscpy_s(error, errsize, result.c_str());
		return false;
	}

	return true;
}

// Return the name of the default GPU device (or the emulator if no GPU exists).
// AMP will enumerate devices to initialize itself outside of the timing code.
extern "C" __declspec (dllexport) void _stdcall WarmUpAMP(wchar_t* buffer, size_t bufsize)
{
	accelerator default_device;
	wcscpy_s(buffer, bufsize, default_device.get_description().c_str());
}