/* Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *  * Neither the name of NVIDIA CORPORATION nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// These are helper functions for the SDK samples (image,bitmap)
#ifndef COMMON_HELPER_IMAGE_H_
#define COMMON_HELPER_IMAGE_H_

#include <algorithm>
#include <assert.h>
#include "exception.h"
#include <fstream>
#include <iostream>
#include <math.h>
#include <stdint.h>
#include <string>
#include <vector>

#ifndef MIN
#	define MIN(a, b) ((a < b) ? a : b)
#endif
#ifndef MAX
#	define MAX(a, b) ((a > b) ? a : b)
#endif

#ifndef EXIT_WAIVED
#	define EXIT_WAIVED 2
#endif

#include "helper_string.h"

// namespace unnamed (internal)
namespace helper_image_internal {
	//! size of PGM file header
	const unsigned int PGMHeaderSize = 0x40;

	// types

	//! Data converter from unsigned char / unsigned byte to type T
	template<class T>
	struct ConverterFromUByte;

	//! Data converter from unsigned char / unsigned byte
	template<>
	struct ConverterFromUByte<unsigned char> {
		//! Conversion operator
		//! @return converted value
		//! @param  val  value to convert
		float operator()(const unsigned char &val) { return static_cast<unsigned char>(val); }
	};

	//! Data converter from unsigned char / unsigned byte to float
	template<>
	struct ConverterFromUByte<float> {
		//! Conversion operator
		//! @return converted value
		//! @param  val  value to convert
		float operator()(const unsigned char &val) { return static_cast<float>(val) / 255.0f; }
	};

	//! Data converter from unsigned char / unsigned byte to type T
	template<class T>
	struct ConverterToUByte;

	//! Data converter from unsigned char / unsigned byte to unsigned int
	template<>
	struct ConverterToUByte<unsigned char> {
		//! Conversion operator (essentially a passthru
		//! @return converted value
		//! @param  val  value to convert
		unsigned char operator()(const unsigned char &val) { return val; }
	};

	//! Data converter from unsigned char / unsigned byte to unsigned int
	template<>
	struct ConverterToUByte<float> {
		//! Conversion operator
		//! @return converted value
		//! @param  val  value to convert
		unsigned char operator()(const float &val) {
			return static_cast<unsigned char>(val * 255.0f);
		}
	};
} // namespace helper_image_internal

#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
#	ifndef FOPEN
#		define FOPEN(fHandle, filename, mode) fopen_s(&fHandle, filename, mode)
#	endif
#	ifndef FOPEN_FAIL
#		define FOPEN_FAIL(result) (result != 0)
#	endif
#	ifndef SSCANF
#		define SSCANF sscanf_s
#	endif
#else
#	ifndef FOPEN
#		define FOPEN(fHandle, filename, mode) (fHandle = fopen(filename, mode))
#	endif
#	ifndef FOPEN_FAIL
#		define FOPEN_FAIL(result) (result == NULL)
#	endif
#	ifndef SSCANF
#		define SSCANF sscanf
#	endif
#endif

inline bool __loadPPM(const char *file, unsigned char **data, unsigned int *w, unsigned int *h,
					  unsigned int *channels) {
	FILE *fp = NULL;

	if (FOPEN_FAIL(FOPEN(fp, file, "rb"))) {
		std::cerr << "__LoadPPM() : Failed to open file: " << file << std::endl;
		return false;
	}

	// check header
	char header[helper_image_internal::PGMHeaderSize];

	if (fgets(header, helper_image_internal::PGMHeaderSize, fp) == NULL) {
		std::cerr << "__LoadPPM() : reading PGM header returned NULL" << std::endl;
		return false;
	}

	if (strncmp(header, "P5", 2) == 0) {
		*channels = 1;
	} else if (strncmp(header, "P6", 2) == 0) {
		*channels = 3;
	} else {
		std::cerr << "__LoadPPM() : File is not a PPM or PGM image" << std::endl;
		*channels = 0;
		return false;
	}

	// parse header, read maxval, width and height
	unsigned int width	= 0;
	unsigned int height = 0;
	unsigned int maxval = 0;
	unsigned int i		= 0;

	while (i < 3) {
		if (fgets(header, helper_image_internal::PGMHeaderSize, fp) == NULL) {
			std::cerr << "__LoadPPM() : reading PGM header returned NULL" << std::endl;
			return false;
		}

		if (header[0] == '#') { continue; }

		if (i == 0) {
			i += SSCANF(header, "%u %u %u", &width, &height, &maxval);
		} else if (i == 1) {
			i += SSCANF(header, "%u %u", &height, &maxval);
		} else if (i == 2) {
			i += SSCANF(header, "%u", &maxval);
		}
	}

	// check if given handle for the data is initialized
	if (NULL != *data) {
		if (*w != width || *h != height) {
			std::cerr << "__LoadPPM() : Invalid image dimensions." << std::endl;
		}
	} else {
		*data = (unsigned char *)malloc(sizeof(unsigned char) * width * height * *channels);
		*w	  = width;
		*h	  = height;
	}

	// read and close file
	if (fread(*data, sizeof(unsigned char), width * height * *channels, fp) == 0) {
		std::cerr << "__LoadPPM() read data returned error." << std::endl;
	}

	fclose(fp);

	return true;
}

template<class T>
inline bool sdkLoadPGM(const char *file, T **data, unsigned int *w, unsigned int *h) {
	unsigned char *idata = NULL;
	unsigned int channels;

	if (true != __loadPPM(file, &idata, w, h, &channels)) { return false; }

	unsigned int size = *w * *h * channels;

	// initialize mem if necessary
	// the correct size is checked / set in loadPGMc()
	if (NULL == *data) { *data = reinterpret_cast<T *>(malloc(sizeof(T) * size)); }

	// copy and cast data
	std::transform(idata, idata + size, *data, helper_image_internal::ConverterFromUByte<T>());

	free(idata);

	return true;
}

template<class T>
inline bool sdkLoadPPM4(const char *file, T **data, unsigned int *w, unsigned int *h) {
	unsigned char *idata = 0;
	unsigned int channels;

	if (__loadPPM(file, &idata, w, h, &channels)) {
		// pad 4th component
		int size = *w * *h;
		// keep the original pointer
		unsigned char *idata_orig = idata;
		*data					  = reinterpret_cast<T *>(malloc(sizeof(T) * size * 4));
		unsigned char *ptr		  = *data;

		for (int i = 0; i < size; i++) {
			*ptr++ = *idata++;
			*ptr++ = *idata++;
			*ptr++ = *idata++;
			*ptr++ = 0;
		}

		free(idata_orig);
		return true;
	} else {
		free(idata);
		return false;
	}
}

inline bool __savePPM(const char *file, unsigned char *data, unsigned int w, unsigned int h,
					  unsigned int channels) {
	assert(NULL != data);
	assert(w > 0);
	assert(h > 0);

	std::fstream fh(file, std::fstream::out | std::fstream::binary);

	if (fh.bad()) {
		std::cerr << "__savePPM() : Opening file failed." << std::endl;
		return false;
	}

	if (channels == 1) {
		fh << "P5\n";
	} else if (channels == 3) {
		fh << "P6\n";
	} else {
		std::cerr << "__savePPM() : Invalid number of channels." << std::endl;
		return false;
	}

	fh << w << "\n" << h << "\n" << 0xff << std::endl;

	for (unsigned int i = 0; (i < (w * h * channels)) && fh.good(); ++i) { fh << data[i]; }

	fh.flush();

	if (fh.bad()) {
		std::cerr << "__savePPM() : Writing data failed." << std::endl;
		return false;
	}

	fh.close();

	return true;
}

template<class T>
inline bool sdkSavePGM(const char *file, T *data, unsigned int w, unsigned int h) {
	unsigned int size	 = w * h;
	unsigned char *idata = (unsigned char *)malloc(sizeof(unsigned char) * size);

	std::transform(data, data + size, idata, helper_image_internal::ConverterToUByte<T>());

	// write file
	bool result = __savePPM(file, idata, w, h, 1);

	// cleanup
	free(idata);

	return result;
}

inline bool sdkSavePPM4ub(const char *file, unsigned char *data, unsigned int w, unsigned int h) {
	// strip 4th component
	int size			 = w * h;
	unsigned char *ndata = (unsigned char *)malloc(sizeof(unsigned char) * size * 3);
	unsigned char *ptr	 = ndata;

	for (int i = 0; i < size; i++) {
		*ptr++ = *data++;
		*ptr++ = *data++;
		*ptr++ = *data++;
		data++;
	}

	bool result = __savePPM(file, ndata, w, h, 3);
	free(ndata);
	return result;
}

//////////////////////////////////////////////////////////////////////////////
//! Read file \filename and return the data
//! @return bool if reading the file succeeded, otherwise false
//! @param filename name of the source file
//! @param data  uninitialized pointer, returned initialized and pointing to
//!        the data read
//! @param len  number of data elements in data, -1 on error
//////////////////////////////////////////////////////////////////////////////
template<class T>
inline bool sdkReadFile(const char *filename, T **data, unsigned int *len, bool verbose) {
	// check input arguments
	assert(NULL != filename);
	assert(NULL != len);

	// intermediate storage for the data read
	std::vector<T> data_read;

	// open file for reading
	FILE *fh = NULL;

	// check if filestream is valid
	if (FOPEN_FAIL(FOPEN(fh, filename, "r"))) {
		printf("Unable to open input file: %s\n", filename);
		return false;
	}

	// read all data elements
	T token;

	while (!feof(fh)) {
		fscanf(fh, "%f", &token);
		data_read.push_back(token);
	}

	// the last element is read twice
	data_read.pop_back();
	fclose(fh);

	// check if the given handle is already initialized
	if (NULL != *data) {
		if (*len != data_read.size()) {
			std::cerr << "sdkReadFile() : Initialized memory given but "
					  << "size  mismatch with signal read "
					  << "(data read / data init = " << (unsigned int)data_read.size() << " / "
					  << *len << ")" << std::endl;

			return false;
		}
	} else {
		// allocate storage for the data read
		*data = reinterpret_cast<T *>(malloc(sizeof(T) * data_read.size()));
		// store signal size
		*len = static_cast<unsigned int>(data_read.size());
	}

	// copy data
	memcpy(*data, &data_read.front(), sizeof(T) * data_read.size());

	return true;
}

//////////////////////////////////////////////////////////////////////////////
//! Read file \filename and return the data
//! @return bool if reading the file succeeded, otherwise false
//! @param filename name of the source file
//! @param data  uninitialized pointer, returned initialized and pointing to
//!        the data read
//! @param len  number of data elements in data, -1 on error
//////////////////////////////////////////////////////////////////////////////
template<class T>
inline bool sdkReadFileBlocks(const char *filename, T **data, unsigned int *len,
							  unsigned int block_num, unsigned int block_size, bool verbose) {
	// check input arguments
	assert(NULL != filename);
	assert(NULL != len);

	// open file for reading
	FILE *fh = fopen(filename, "rb");

	if (fh == NULL && verbose) {
		std::cerr << "sdkReadFile() : Opening file failed." << std::endl;
		return false;
	}

	// check if the given handle is already initialized
	// allocate storage for the data read
	data[block_num] = reinterpret_cast<T *>(malloc(block_size));

	// read all data elements
	fseek(fh, block_num * block_size, SEEK_SET);
	*len = fread(data[block_num], sizeof(T), block_size / sizeof(T), fh);

	fclose(fh);

	return true;
}

//////////////////////////////////////////////////////////////////////////////
//! Write a data file \filename
//! @return true if writing the file succeeded, otherwise false
//! @param filename name of the source file
//! @param data  data to write
//! @param len  number of data elements in data, -1 on error
//! @param epsilon  epsilon for comparison
//////////////////////////////////////////////////////////////////////////////
template<class T, class S>
inline bool sdkWriteFile(const char *filename, const T *data, unsigned int len, const S epsilon,
						 bool verbose, bool append = false) {
	assert(NULL != filename);
	assert(NULL != data);

	// open file for writing
	//    if (append) {
	std::fstream fh(filename, std::fstream::out | std::fstream::ate);

	if (verbose) {
		std::cerr << "sdkWriteFile() : Open file " << filename << " for write/append." << std::endl;
	}

	/*    } else {
			std::fstream fh(filename, std::fstream::out);
			if (verbose) {
				std::cerr << "sdkWriteFile() : Open file " << filename << " for
	   write." << std::endl;
			}
		}
	*/

	// check if filestream is valid
	if (!fh.good()) {
		if (verbose) { std::cerr << "sdkWriteFile() : Opening file failed." << std::endl; }

		return false;
	}

	// first write epsilon
	fh << "# " << epsilon << "\n";

	// write data
	for (unsigned int i = 0; (i < len) && (fh.good()); ++i) { fh << data[i] << ' '; }

	// Check if writing succeeded
	if (!fh.good()) {
		if (verbose) { std::cerr << "sdkWriteFile() : Writing file failed." << std::endl; }

		return false;
	}

	// file ends with nl
	fh << std::endl;

	return true;
}

//////////////////////////////////////////////////////////////////////////////
//! Compare two arrays of arbitrary type
//! @return  true if \a reference and \a data are identical, otherwise false
//! @param reference  timer_interface to the reference data / gold image
//! @param data       handle to the computed data
//! @param len        number of elements in reference and data
//! @param epsilon    epsilon to use for the comparison
//////////////////////////////////////////////////////////////////////////////
template<class T, class S>
inline bool compareData(const T *reference, const T *data, const unsigned int len, const S epsilon,
						const float threshold) {
	assert(epsilon >= 0);

	bool result				 = true;
	unsigned int error_count = 0;

	for (unsigned int i = 0; i < len; ++i) {
		float diff = static_cast<float>(reference[i]) - static_cast<float>(data[i]);
		bool comp  = (diff <= epsilon) && (diff >= -epsilon);
		result &= comp;

		error_count += !comp;

#if 0

		if (!comp) {
		  std::cerr << "ERROR, i = " << i << ",\t "
					<< reference[i] << " / "
					<< data[i]
					<< " (reference / data)\n";
		}

#endif
	}

	if (threshold == 0.0f) {
		return (result) ? true : false;
	} else {
		if (error_count) {
			printf("%4.2f(%%) of bytes mismatched (count=%d)\n",
				   static_cast<float>(error_count) * 100 / static_cast<float>(len),
				   error_count);
		}

		return (len * threshold > error_count) ? true : false;
	}
}

#ifndef __MIN_EPSILON_ERROR
#	define __MIN_EPSILON_ERROR 1e-3f
#endif

//////////////////////////////////////////////////////////////////////////////
//! Compare two arrays of arbitrary type
//! @return  true if \a reference and \a data are identical, otherwise false
//! @param reference  handle to the reference data / gold image
//! @param data       handle to the computed data
//! @param len        number of elements in reference and data
//! @param epsilon    epsilon to use for the comparison
//! @param epsilon    threshold % of (# of bytes) for pass/fail
//////////////////////////////////////////////////////////////////////////////
template<class T, class S>
inline bool compareDataAsFloatThreshold(const T *reference, const T *data, const unsigned int len,
										const S epsilon, const float threshold) {
	assert(epsilon >= 0);

	// If we set epsilon to be 0, let's set a minimum threshold
	float max_error = MAX((float)epsilon, __MIN_EPSILON_ERROR);
	int error_count = 0;
	bool result		= true;

	for (unsigned int i = 0; i < len; ++i) {
		float diff = fabs(static_cast<float>(reference[i]) - static_cast<float>(data[i]));
		bool comp  = (diff < max_error);
		result &= comp;

		if (!comp) { error_count++; }
	}

	if (threshold == 0.0f) {
		if (error_count) { printf("total # of errors = %d\n", error_count); }

		return (error_count == 0) ? true : false;
	} else {
		if (error_count) {
			printf("%4.2f(%%) of bytes mismatched (count=%d)\n",
				   static_cast<float>(error_count) * 100 / static_cast<float>(len),
				   error_count);
		}

		return ((len * threshold > error_count) ? true : false);
	}
}

inline void sdkDumpBin(void *data, unsigned int bytes, const char *filename) {
	printf("sdkDumpBin: <%s>\n", filename);
	FILE *fp;
	FOPEN(fp, filename, "wb");
	fwrite(data, bytes, 1, fp);
	fflush(fp);
	fclose(fp);
}

inline bool sdkCompareBin2BinUint(const char *src_file, const char *ref_file,
								  unsigned int nelements, const float epsilon,
								  const float threshold, char *exec_path) {
	unsigned int *src_buffer, *ref_buffer;
	FILE *src_fp = NULL, *ref_fp = NULL;

	uint64_t error_count = 0;
	size_t fsize		 = 0;

	if (FOPEN_FAIL(FOPEN(src_fp, src_file, "rb"))) {
		printf("compareBin2Bin <unsigned int> unable to open src_file: %s\n", src_file);
		error_count++;
	}

	char *ref_file_path = sdkFindFilePath(ref_file, exec_path);

	if (ref_file_path == NULL) {
		printf("compareBin2Bin <unsigned int>  unable to find <%s> in <%s>\n", ref_file, exec_path);
		printf(">>> Check info.xml and [project//data] folder <%s> <<<\n", ref_file);
		printf("Aborting comparison!\n");
		printf("  FAILED\n");
		error_count++;

		if (src_fp) { fclose(src_fp); }

		if (ref_fp) { fclose(ref_fp); }
	} else {
		if (FOPEN_FAIL(FOPEN(ref_fp, ref_file_path, "rb"))) {
			printf(
			  "compareBin2Bin <unsigned int>"
			  " unable to open ref_file: %s\n",
			  ref_file_path);
			error_count++;
		}

		if (src_fp && ref_fp) {
			src_buffer = (unsigned int *)malloc(nelements * sizeof(unsigned int));
			ref_buffer = (unsigned int *)malloc(nelements * sizeof(unsigned int));

			fsize = fread(src_buffer, nelements, sizeof(unsigned int), src_fp);
			fsize = fread(ref_buffer, nelements, sizeof(unsigned int), ref_fp);

			printf(
			  "> compareBin2Bin <unsigned int> nelements=%d,"
			  " epsilon=%4.2f, threshold=%4.2f\n",
			  nelements,
			  epsilon,
			  threshold);
			printf("   src_file <%s>, size=%d bytes\n", src_file, static_cast<int>(fsize));
			printf("   ref_file <%s>, size=%d bytes\n", ref_file_path, static_cast<int>(fsize));

			if (!compareData<unsigned int, float>(
				  ref_buffer, src_buffer, nelements, epsilon, threshold)) {
				error_count++;
			}

			fclose(src_fp);
			fclose(ref_fp);

			free(src_buffer);
			free(ref_buffer);
		} else {
			if (src_fp) { fclose(src_fp); }

			if (ref_fp) { fclose(ref_fp); }
		}
	}

	if (error_count == 0) {
		printf("  OK\n");
	} else {
		printf("  FAILURE: %d errors...\n", (unsigned int)error_count);
	}

	return (error_count == 0); // returns true if all pixels pass
}

inline bool sdkCompareBin2BinFloat(const char *src_file, const char *ref_file,
								   unsigned int nelements, const float epsilon,
								   const float threshold, char *exec_path) {
	float *src_buffer = NULL, *ref_buffer = NULL;
	FILE *src_fp = NULL, *ref_fp = NULL;
	size_t fsize = 0;

	uint64_t error_count = 0;

	if (FOPEN_FAIL(FOPEN(src_fp, src_file, "rb"))) {
		printf("compareBin2Bin <float> unable to open src_file: %s\n", src_file);
		error_count = 1;
	}

	char *ref_file_path = sdkFindFilePath(ref_file, exec_path);

	if (ref_file_path == NULL) {
		printf("compareBin2Bin <float> unable to find <%s> in <%s>\n", ref_file, exec_path);
		printf(">>> Check info.xml and [project//data] folder <%s> <<<\n", exec_path);
		printf("Aborting comparison!\n");
		printf("  FAILED\n");
		error_count++;

		if (src_fp) { fclose(src_fp); }

		if (ref_fp) { fclose(ref_fp); }
	} else {
		if (FOPEN_FAIL(FOPEN(ref_fp, ref_file_path, "rb"))) {
			printf("compareBin2Bin <float> unable to open ref_file: %s\n", ref_file_path);
			error_count = 1;
		}

		if (src_fp && ref_fp) {
			src_buffer = reinterpret_cast<float *>(malloc(nelements * sizeof(float)));
			ref_buffer = reinterpret_cast<float *>(malloc(nelements * sizeof(float)));

			printf(
			  "> compareBin2Bin <float> nelements=%d, epsilon=%4.2f,"
			  " threshold=%4.2f\n",
			  nelements,
			  epsilon,
			  threshold);
			fsize = fread(src_buffer, sizeof(float), nelements, src_fp);
			printf("   src_file <%s>, size=%d bytes\n",
				   src_file,
				   static_cast<int>(fsize * sizeof(float)));
			fsize = fread(ref_buffer, sizeof(float), nelements, ref_fp);
			printf("   ref_file <%s>, size=%d bytes\n",
				   ref_file_path,
				   static_cast<int>(fsize * sizeof(float)));

			if (!compareDataAsFloatThreshold<float, float>(
				  ref_buffer, src_buffer, nelements, epsilon, threshold)) {
				error_count++;
			}

			fclose(src_fp);
			fclose(ref_fp);

			free(src_buffer);
			free(ref_buffer);
		} else {
			if (src_fp) { fclose(src_fp); }

			if (ref_fp) { fclose(ref_fp); }
		}
	}

	if (error_count == 0) {
		printf("  OK\n");
	} else {
		printf("  FAILURE: %d errors...\n", (unsigned int)error_count);
	}

	return (error_count == 0); // returns true if all pixels pass
}

inline bool sdkCompareL2fe(const float *reference, const float *data, const unsigned int len,
						   const float epsilon) {
	assert(epsilon >= 0);

	float error = 0;
	float ref	= 0;

	for (unsigned int i = 0; i < len; ++i) {
		float diff = reference[i] - data[i];
		error += diff * diff;
		ref += reference[i] * reference[i];
	}

	float normRef = sqrtf(ref);

	if (fabs(ref) < 1e-7) {
#ifdef _DEBUG
		std::cerr << "ERROR, reference l2-norm is 0\n";
#endif
		return false;
	}

	float normError = sqrtf(error);
	error			= normError / normRef;
	bool result		= error < epsilon;
#ifdef _DEBUG

	if (!result) {
		std::cerr << "ERROR, l2-norm error " << error << " is greater than epsilon " << epsilon
				  << "\n";
	}

#endif

	return result;
}

inline bool sdkLoadPPMub(const char *file, unsigned char **data, unsigned int *w, unsigned int *h) {
	unsigned int channels;
	return __loadPPM(file, data, w, h, &channels);
}

inline bool sdkLoadPPM4ub(const char *file, unsigned char **data, unsigned int *w,
						  unsigned int *h) {
	unsigned char *idata = 0;
	unsigned int channels;

	if (__loadPPM(file, &idata, w, h, &channels)) {
		// pad 4th component
		int size = *w * *h;
		// keep the original pointer
		unsigned char *idata_orig = idata;
		*data					  = (unsigned char *)malloc(sizeof(unsigned char) * size * 4);
		unsigned char *ptr		  = *data;

		for (int i = 0; i < size; i++) {
			*ptr++ = *idata++;
			*ptr++ = *idata++;
			*ptr++ = *idata++;
			*ptr++ = 0;
		}

		free(idata_orig);
		return true;
	} else {
		free(idata);
		return false;
	}
}

inline bool sdkComparePPM(const char *src_file, const char *ref_file, const float epsilon,
						  const float threshold, bool verboseErrors) {
	unsigned char *src_data, *ref_data;
	uint64_t error_count = 0;
	unsigned int ref_width, ref_height;
	unsigned int src_width, src_height;

	if (src_file == NULL || ref_file == NULL) {
		if (verboseErrors) {
			std::cerr << "PPMvsPPM: src_file or ref_file is NULL."
						 "  Aborting comparison\n";
		}

		return false;
	}

	if (verboseErrors) {
		std::cerr << "> Compare (a)rendered:  <" << src_file << ">\n";
		std::cerr << ">         (b)reference: <" << ref_file << ">\n";
	}

	if (sdkLoadPPM4ub(ref_file, &ref_data, &ref_width, &ref_height) != true) {
		if (verboseErrors) {
			std::cerr << "PPMvsPPM: unable to load ref image file: " << ref_file << "\n";
		}

		return false;
	}

	if (sdkLoadPPM4ub(src_file, &src_data, &src_width, &src_height) != true) {
		std::cerr << "PPMvsPPM: unable to load src image file: " << src_file << "\n";
		return false;
	}

	if (src_height != ref_height || src_width != ref_width) {
		if (verboseErrors) {
			std::cerr << "PPMvsPPM: source and ref size mismatch (" << src_width << ","
					  << src_height << ")vs(" << ref_width << "," << ref_height << ")\n";
		}
	}

	if (verboseErrors) {
		std::cerr << "PPMvsPPM: comparing images size (" << src_width << "," << src_height
				  << ") epsilon(" << epsilon << "), threshold(" << threshold * 100 << "%)\n";
	}

	if (compareData(ref_data, src_data, src_width * src_height * 4, epsilon, threshold) == false) {
		error_count = 1;
	}

	if (error_count == 0) {
		if (verboseErrors) { std::cerr << "    OK\n\n"; }
	} else {
		if (verboseErrors) { std::cerr << "    FAILURE!  " << error_count << " errors...\n\n"; }
	}

	// returns true if all pixels pass
	return (error_count == 0) ? true : false;
}

inline bool sdkComparePGM(const char *src_file, const char *ref_file, const float epsilon,
						  const float threshold, bool verboseErrors) {
	unsigned char *src_data = 0, *ref_data = 0;
	uint64_t error_count = 0;
	unsigned int ref_width, ref_height;
	unsigned int src_width, src_height;

	if (src_file == NULL || ref_file == NULL) {
		if (verboseErrors) {
			std::cerr << "PGMvsPGM: src_file or ref_file is NULL."
						 "  Aborting comparison\n";
		}

		return false;
	}

	if (verboseErrors) {
		std::cerr << "> Compare (a)rendered:  <" << src_file << ">\n";
		std::cerr << ">         (b)reference: <" << ref_file << ">\n";
	}

	if (sdkLoadPPMub(ref_file, &ref_data, &ref_width, &ref_height) != true) {
		if (verboseErrors) {
			std::cerr << "PGMvsPGM: unable to load ref image file: " << ref_file << "\n";
		}

		return false;
	}

	if (sdkLoadPPMub(src_file, &src_data, &src_width, &src_height) != true) {
		std::cerr << "PGMvsPGM: unable to load src image file: " << src_file << "\n";
		return false;
	}

	if (src_height != ref_height || src_width != ref_width) {
		if (verboseErrors) {
			std::cerr << "PGMvsPGM: source and ref size mismatch (" << src_width << ","
					  << src_height << ")vs(" << ref_width << "," << ref_height << ")\n";
		}
	}

	if (verboseErrors)
		std::cerr << "PGMvsPGM: comparing images size (" << src_width << "," << src_height
				  << ") epsilon(" << epsilon << "), threshold(" << threshold * 100 << "%)\n";

	if (compareData(ref_data, src_data, src_width * src_height, epsilon, threshold) == false) {
		error_count = 1;
	}

	if (error_count == 0) {
		if (verboseErrors) { std::cerr << "    OK\n\n"; }
	} else {
		if (verboseErrors) { std::cerr << "    FAILURE!  " << error_count << " errors...\n\n"; }
	}

	// returns true if all pixels pass
	return (error_count == 0) ? true : false;
}

#endif // COMMON_HELPER_IMAGE_H_
