/* -*- mia-c++  -*-
 *
 * This file is part of MIA - a toolbox for medical image analysis 
 * Copyright (c) Leipzig, Madrid 1999-2017 Gert Wollny
 *
 * MIA is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MIA; if not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <iostream>
#include <string>
#include <stdexcept>
#include <dlfcn.h>

#include <mia/core/errormacro.hh>
#include <mia/core/msgstream.hh>
#include <mia/core/cmdlineparser.hh>
#include <mia/3d/imageio.hh>


using namespace std;
NS_MIA_USE

enum EBinops  {
	bin_or, 
	bin_nor, 
	bin_and, 
	bin_nand, 
	bin_xor, 
	bin_nxor, 
	bin_unknown
}; 

struct logical_xor : public binary_function<bool,bool,bool> {
	bool operator() (bool a, bool b) const {
		return (a ^ b);
	}
};

P3DImage binary_op(const C3DBitImage& a, const C3DBitImage& b, EBinops op)
{
	assert(a.get_size() == b.get_size()); 
	C3DBitImage *result = new C3DBitImage(a.get_size()); 
	
	switch (op) {
	case bin_or  : 	transform(a.begin(), a.end(), b.begin(), result->begin(), logical_or<bool>()); 
		break; 
	case bin_nor : 
		transform(a.begin(), a.end(), b.begin(), result->begin(), not2(logical_or<bool>())); 
		break; 
	case bin_and : 
		transform(a.begin(), a.end(), b.begin(), result->begin(), logical_and<bool>()); 
		break; 
	case bin_nand:  
		transform(a.begin(), a.end(), b.begin(), result->begin(), not2(logical_and<bool>())); 
		break; 
	case bin_xor : 
		transform(a.begin(), a.end(), b.begin(), result->begin(), logical_xor());
		break; 
	case bin_nxor:  
		transform(a.begin(), a.end(), b.begin(), result->begin(), not2(logical_xor())); 
		break; 
	default: 
		throw invalid_argument("Unknown binary operation requested"); 
	}
	return P3DImage(result); 
}


const TDictMap<EBinops>::Table g_binops_table[] = {
	{"or", bin_or, "logocal or"}, 
	{"nor", bin_nor, "logocal nor"}, 
	{"and", bin_and, "logocal and"}, 
	{"nand", bin_nand, "logocal nand"}, 
	{"xor", bin_xor, "logocal xor"}, 
	{"nxor", bin_nxor, "logocal nxor"}, 
	{NULL,bin_unknown, ""}, 
}; 

const TDictMap<EBinops> g_binops_dict(g_binops_table);

const SProgramDescription g_description = {
        {pdi_group, "Analysis, filtering, combining, and segmentation of 3D images"}, 
	{pdi_short, "Combine two binary images."}, 
	{pdi_description, "This program is used to combine two binary images by some kind of operation."}, 
	{pdi_example_descr, "Combine the binary images b1.png and b2.png by using the 'nor' operation " 
	 "and store the result in b1nor2.png."}, 
	{pdi_example_code, "-1 b1.png -2 b2.png -p nor -o b1nor2.png"}
}; 

int do_main( int argc, char *argv[] )
{

	string filename1;
	string filename2;
	string out_filename;
	EBinops op = bin_nor; 

	const auto& imageio = C3DImageIOPluginHandler::instance();
	CCmdOptionList options(g_description);
	options.add(make_opt( filename1, "file1", '1', "input mask image 1", CCmdOptionFlags::required_input, &imageio)); 
	options.add(make_opt( filename2, "file2", '2', "input mask image 2", CCmdOptionFlags::required_input, &imageio)); 
	options.add(make_opt(op, g_binops_dict, "operation", 'p', "Operation to be applied")); 
	options.add(make_opt( out_filename, "out-file", 'o', "output mask image", CCmdOptionFlags::required_output, &imageio)); 

	if (options.parse(argc, argv) != CCmdOptionList::hr_no) 
		return EXIT_SUCCESS; 

	
	// read images
	P3DImage image1 = load_image3d(filename1); 
	P3DImage image2 = load_image3d(filename2); 

	try {
		const C3DBitImage& img1 = dynamic_cast<const C3DBitImage&>(*image1); 
		const C3DBitImage& img2 = dynamic_cast<const C3DBitImage&>(*image2); 
		P3DImage result = binary_op(img1, img2, op); 
			
		if ( !save_image(out_filename, result) ){
			throw create_exception<runtime_error>( "cannot save result to ", out_filename); 
		}
			
	}catch (bad_cast& x) {
		throw invalid_argument("Input images are not binary masks"); 
	}
		
	return EXIT_SUCCESS; 
}

#include <mia/internal/main.hh>
MIA_MAIN(do_main); 

