// Copyright Maarten L. Hekkelman, Radboud University 2008-2011.
//   Distributed under the Boost Software License, Version 1.0.
//       (See accompanying file LICENSE_1_0.txt or copy at
//             http://www.boost.org/LICENSE_1_0.txt)

#include "mas.h"

#if defined(_MSC_VER)
#include <conio.h>
#include <ctype.h>
#endif

#include <iostream>

#include <boost/format.hpp>
#include <boost/foreach.hpp>
#define foreach BOOST_FOREACH
#include <boost/bind.hpp>
#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/date_time/date_clock_device.hpp>

#include "dssp.h"
#include "structure.h"

using namespace std;

string ResidueToDSSPLine(const MResidue& residue)
{
/*
	This is the header line for the residue lines in a DSSP file:

  #  RESIDUE AA STRUCTURE BP1 BP2  ACC     N-H-->O    O-->H-N    N-H-->O    O-->H-N    TCO  KAPPA ALPHA  PHI   PSI    X-CA   Y-CA   Z-CA

 */
	boost::format kDSSPResidueLine(
	"%5.5d%5.5d%1.1s%1.1s %c  %c %c%c%c%c%c%c%c%4.4d%4.4d%c%4.4d %11s%11s%11s%11s  %6.3f%6.1f%6.1f%6.1f%6.1f %6.1f %6.1f %6.1f");

	const MAtom& ca = residue.GetCAlpha();

	char code = kResidueInfo[residue.GetType()].code;
	if (residue.GetType() == kCysteine and residue.GetSSBridgeNr() != 0)
		code = 'a' + ((residue.GetSSBridgeNr() - 1) % 26);

	char ss;
	switch (residue.GetSecondaryStructure())
	{
		case alphahelix:	ss = 'H'; break;
		case betabridge:	ss = 'B'; break;
		case strand:		ss = 'E'; break;
		case helix_3:		ss = 'G'; break;
		case helix_5:		ss = 'I'; break;
		case turn:			ss = 'T'; break;
		case bend:			ss = 'S'; break;
		case loop:			ss = ' '; break;
	}

	char helix[3];
	for (uint32 stride = 3; stride <= 5; ++stride)
	{
		switch (residue.GetHelixFlag(stride))
		{
			case helixNone:			helix[stride - 3] = ' '; break;
			case helixStart:		helix[stride - 3] = '>'; break;
			case helixEnd:			helix[stride - 3] = '<'; break;
			case helixStartAndEnd:	helix[stride - 3] = 'X'; break;
			case helixMiddle:		helix[stride - 3] = '0' + stride; break;
		}
	}

	char bend = ' ';
	if (residue.IsBend())
		bend = 'S';

	double alpha;
	char chirality;
	tr1::tie(alpha,chirality) = residue.Alpha();

	uint32 bp[2] = {};
	char bridgelabel[2] = { ' ', ' ' };
	for (uint32 i = 0; i < 2; ++i)
	{
		MBridgeParner p = residue.GetBetaPartner(i);
		if (p.residue != nullptr)
		{
			bp[i] = p.residue->GetNumber();
			bp[i] %= 10000;	// won't fit otherwise...
			bridgelabel[i] = 'A' + p.ladder % 26;
			if (p.parallel)
				bridgelabel[i] = tolower(bridgelabel[i]);
		}
	}

	char sheet = ' ';
	if (residue.GetSheet() != 0)
		sheet = 'A' + (residue.GetSheet() - 1) % 26;

	string NHO[2], ONH[2];
	const HBond* acceptors = residue.Acceptor();
	const HBond* donors = residue.Donor();
	for (uint32 i = 0; i < 2; ++i)
	{
		NHO[i] = ONH[i] = "0, 0.0";

		if (acceptors[i].residue != nullptr)
		{
			int32 d = acceptors[i].residue->GetNumber() - residue.GetNumber();
			NHO[i] = (boost::format("%d,%3.1f") % d % acceptors[i].energy).str();
		}

		if (donors[i].residue != nullptr)
		{
			int32 d = donors[i].residue->GetNumber() - residue.GetNumber();
			ONH[i] = (boost::format("%d,%3.1f") % d % donors[i].energy).str();
		}
	}

	return (kDSSPResidueLine % residue.GetNumber() % ca.mResSeq % ca.mICode % ca.mChainID % code %
		ss % helix[0] % helix[1] % helix[2] % bend % chirality % bridgelabel[0] % bridgelabel[1] %
		bp[0] % bp[1] % sheet % floor(residue.Accessibility() + 0.5) %
		NHO[0] % ONH[0] % NHO[1] % ONH[1] %
		residue.TCO() % residue.Kappa() % alpha % residue.Phi() % residue.Psi() %
		ca.mLoc.mX % ca.mLoc.mY % ca.mLoc.mZ).str();
}

void WriteDSSP(MProtein& protein, ostream& os)
{
	const string kFirstLine("==== Secondary Structure Definition by the program DSSP, CMBI version by M.L. Hekkelman/2010-10-21 ==== ");
	boost::format kHeaderLine("%1% %|127t|%2%");

	using namespace boost::gregorian;

	uint32 nrOfResidues, nrOfChains, nrOfSSBridges, nrOfIntraChainSSBridges, nrOfHBonds;
	uint32 nrOfHBondsPerDistance[11] = {};

	protein.GetStatistics(nrOfResidues, nrOfChains, nrOfSSBridges, nrOfIntraChainSSBridges, nrOfHBonds, nrOfHBondsPerDistance);

	date today = day_clock::local_day();

	os << kHeaderLine % (kFirstLine + "DATE=" + to_iso_extended_string(today)) % '.' << endl;
	os << kHeaderLine % "REFERENCE W. KABSCH AND C.SANDER, BIOPOLYMERS 22 (1983) 2577-2637" % '.' << endl;
	os << kHeaderLine % protein.GetHeader() % '.' << endl;
	if (not protein.GetCompound().empty())
		os << kHeaderLine % protein.GetCompound() % '.' << endl;
	if (not protein.GetSource().empty())
		os << kHeaderLine % protein.GetSource() % '.' << endl;
	if (not protein.GetAuthor().empty())
		os << kHeaderLine % protein.GetAuthor() % '.' << endl;

	double accessibleSurface = 0;	// calculate accessibility as
	foreach (const MChain* chain, protein.GetChains())
	{
		foreach (const MResidue* residue, chain->GetResidues())
			accessibleSurface += residue->Accessibility();
	}

	os << boost::format("%5.5d%3.3d%3.3d%3.3d%3.3d TOTAL NUMBER OF RESIDUES, NUMBER OF CHAINS, NUMBER OF SS-BRIDGES(TOTAL,INTRACHAIN,INTERCHAIN) %|127t|%c") %
   		nrOfResidues % nrOfChains % nrOfSSBridges % nrOfIntraChainSSBridges % (nrOfSSBridges - nrOfIntraChainSSBridges) % '.' << endl;
   	os << kHeaderLine % (boost::format("%8.1f   ACCESSIBLE SURFACE OF PROTEIN (ANGSTROM**2)") % accessibleSurface) % '.' << endl;

	// hydrogenbond summary

	os << kHeaderLine % (
		boost::format("%5.5d%5.1f   TOTAL NUMBER OF HYDROGEN BONDS OF TYPE O(I)-->H-N(J)  , SAME NUMBER PER 100 RESIDUES")
			% nrOfHBonds % (nrOfHBonds * 100.0 / nrOfResidues)) % '.' << endl;

	uint32 nrOfHBondsInParallelBridges = protein.GetNrOfHBondsInParallelBridges();
	os << kHeaderLine % (
		boost::format("%5.5d%5.1f   TOTAL NUMBER OF HYDROGEN BONDS IN     PARALLEL BRIDGES, SAME NUMBER PER 100 RESIDUES")
			% nrOfHBondsInParallelBridges % (nrOfHBondsInParallelBridges * 100.0 / nrOfResidues)) % '.' << endl;

	uint32 nrOfHBondsInAntiparallelBridges = protein.GetNrOfHBondsInAntiparallelBridges();
	os << kHeaderLine % (
		boost::format("%5.5d%5.1f   TOTAL NUMBER OF HYDROGEN BONDS IN ANTIPARALLEL BRIDGES, SAME NUMBER PER 100 RESIDUES")
			% nrOfHBondsInAntiparallelBridges % (nrOfHBondsInAntiparallelBridges * 100.0 / nrOfResidues)) % '.' << endl;

	boost::format kHBondsLine("%5.5d%5.1f   TOTAL NUMBER OF HYDROGEN BONDS OF TYPE O(I)-->H-N(I%c%1.1d), SAME NUMBER PER 100 RESIDUES");
   	for (int32 k = 0; k < 11; ++k)
	{
		os << kHeaderLine % (kHBondsLine % nrOfHBondsPerDistance[k] % (nrOfHBondsPerDistance[k] * 100.0 / nrOfResidues) % (k - 5 < 0 ? '-' : '+') % abs(k - 5)) % '.' << endl;
	}

	// histograms...

	uint32 histogram[kHistogramSize];
	os << "  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30     *** HISTOGRAMS OF ***           ." << endl;

	protein.GetResiduesPerAlphaHelixHistogram(histogram);
	for (uint32 i = 0; i < kHistogramSize; ++i)
		os << boost::format("%3.3d") % histogram[i];
	os << "    RESIDUES PER ALPHA HELIX         ." << endl;

	protein.GetParallelBridgesPerLadderHistogram(histogram);
	for (uint32 i = 0; i < kHistogramSize; ++i)
		os << boost::format("%3.3d") % histogram[i];
	os << "    PARALLEL BRIDGES PER LADDER      ." << endl;

	protein.GetAntiparallelBridgesPerLadderHistogram(histogram);
	for (uint32 i = 0; i < kHistogramSize; ++i)
		os << boost::format("%3.3d") % histogram[i];
	os << "    ANTIPARALLEL BRIDGES PER LADDER  ." << endl;

	protein.GetLaddersPerSheetHistogram(histogram);
	for (uint32 i = 0; i < kHistogramSize; ++i)
		os << boost::format("%3.3d") % histogram[i];
	os << "    LADDERS PER SHEET                ." << endl;

	// per residue information

	os << "  #  RESIDUE AA STRUCTURE BP1 BP2  ACC     N-H-->O    O-->H-N    N-H-->O    O-->H-N    TCO  KAPPA ALPHA  PHI   PSI    X-CA   Y-CA   Z-CA " << endl;
	boost::format kDSSPResidueLine(
		"%5.5d        !%c             0   0    0      0, 0.0     0, 0.0     0, 0.0     0, 0.0   0.000 360.0 360.0 360.0 360.0    0.0    0.0    0.0");

	vector<const MResidue*> residues;

	foreach (const MChain* chain, protein.GetChains())
	{
		foreach (const MResidue* residue, chain->GetResidues())
			residues.push_back(residue);
	}

	// keep residues sorted by residue number as assigned during reading the PDB file
	sort(residues.begin(), residues.end(), boost::bind(&MResidue::GetNumber, _1) < boost::bind(&MResidue::GetNumber, _2));

	const MResidue* last = nullptr;
	foreach (const MResidue* residue, residues)
	{
		// insert a break line whenever we detect missing residues
		// can be the transition to a different chain, or missing residues in the current chain
		if (last != nullptr and last->GetNumber() + 1 != residue->GetNumber())
		{
			char breaktype = ' ';
			if (last->GetChainID() != residue->GetChainID())
				breaktype = '*';
			os << (kDSSPResidueLine % (last->GetNumber() + 1) % breaktype) << endl;
		}
		os << ResidueToDSSPLine(*residue) << endl;
		last = residue;
	}
}


// __BEGIN_LICENSE__
// Copyright (C) 2006-2011 United States Government as represented by
// the Administrator of the National Aeronautics and Space Administration.
// All Rights Reserved.
// __END_LICENSE__

#include <photk/Common.h>
#include <photk/config.h>
#include <vw/Math/BBox.h>
#include <vw/config.h>
#include <boost/filesystem/path.hpp>
using namespace vw;

namespace photk {

  BaseOptions::BaseOptions() {
#if defined(VW_HAS_BIGTIFF) && VW_HAS_BIGTIFF == 1
    gdal_options["COMPRESS"] = "LZW";
#else
    gdal_options["COMPRESS"] = "NONE";
    gdal_options["BIGTIFF"] = "NO";
#endif
    raster_tile_size =
      vw::Vector2i(vw::vw_settings().default_tile_size(),
                   vw::vw_settings().default_tile_size());
  }

  BaseOptionsDescription::BaseOptionsDescription( BaseOptions& opt ) {
    namespace po = boost::program_options;
    (*this).add_options()
      ("threads", po::value(&opt.num_threads)->default_value(0),
       "Select the number of processors (threads) to use.")
      ("no-bigtiff", "Tell GDAL to not create bigtiffs.")
      ("version,v", "Display the version of software.")
      ("help,h", "Display this help message");
  }

  boost::program_options::variables_map
  check_command_line( int argc, char *argv[], BaseOptions& opt,
                      boost::program_options::options_description const& public_options,
                      boost::program_options::options_description const& hidden_options,
                      boost::program_options::positional_options_description const& positional,
                      std::string const& help ) {
    namespace po = boost::program_options;
    po::variables_map vm;
    try {
      po::options_description all_options;
      all_options.add(public_options).add(hidden_options);
      po::store( po::command_line_parser( argc, argv ).options(all_options).positional(positional).run(), vm );
      po::notify( vm );
    } catch (po::error const& e) {
      vw::vw_throw( vw::ArgumentErr() << "Error parsing input:\n"
                    << e.what() << "\n" << help << "\n" << public_options );
    }
    // We really don't want to use BIGTIFF unless we have to. It's
    // hard to find viewers for bigtiff.
    if ( vm.count("no-bigtiff") ) {
      opt.gdal_options["BIGTIFF"] = "NO";
    } else {
      opt.gdal_options["BIGTIFF"] = "IF_SAFER";
    }
    if ( vm.count("help") )
      vw::vw_throw( vw::ArgumentErr() << help << "\n" << public_options );
    if ( vm.count("version") )
      vw::vw_throw( vw::ArgumentErr() << PHOTK_PACKAGE_STRING << "\n\n"
                    << "Built against:\n  " << VW_PACKAGE_STRING << "\n  BOOST "
                    << PHOTK_BOOST_VERSION << "\n");
    if ( opt.num_threads != 0 ) {
      vw::vw_out() << "\t--> Setting number of processing threads to: "
                   << opt.num_threads << std::endl;
      vw::vw_settings().set_default_num_threads(opt.num_threads);
    }

    return vm;
  }

  bool has_cam_extension( std::string input ) {
    boost::filesystem::path ipath( input );
    std::string ext = ipath.extension();
    if ( ext == ".cahvor" || ext == ".cahv" ||
         ext == ".pin" || ext == ".pinhole" ||
         ext == ".tsai" || ext == ".cmod" ||
         ext == ".cahvore" )
      return true;
    return false;
  }

  // This algorithm is lifted from, "Cutting circles and squares into
  // equal pieces" by Bose et. al.
  std::vector<vw::BBox2i>
  split_square_to_equal_area( int32 const& side_length,
                              size_t const& k ) {
    using namespace vw;
    std::vector<BBox2i> result;
    result.reserve( k );

    VW_ASSERT( uint64(side_length)*uint64(side_length) >= uint64(k),
               ArgumentErr() << "Requesting more regions then are pixels in square. K = " << k << ", Side Length = " << side_length );
    VW_ASSERT( side_length >= k,
               ArgumentErr() << "K must be smaller or equal to side_length." );

    int32 a = int32(floor(sqrt(float(k))));
    int32 r = k - a*a;
    int32 r_p_h_num = 0, r_pp_h_num = 0;
    if ( 0 <= r && r <= a ) {
      r_p_h_num = r;
      r_pp_h_num = a - r;
    } else if ( a + 1 <= r && r <= 2*a ) {
      r_p_h_num = r - a;
      r_pp_h_num = 2*a - r + 1;
    } else {
      vw_throw( LogicErr() << "Split square has hit undiscovered error." );
    }
    int32 region_split =
      int32(float(side_length * (a+1) * r_p_h_num)/float(k));
    int32 r_p_h = r_p_h_num == 0 ? 0 : ceil(float(region_split) / float(r_p_h_num));
    int32 r_pp_h = r_pp_h_num == 0 ? 0 : int32(ceil(float(side_length - region_split) / float(r_pp_h_num)));
    int32 r_p_v = int32(ceil(float(side_length)/float(a + 1)));
    int32 r_pp_v = int32(ceil(float(side_length)/float(a)));

    int32 x,y;
    if ( r_p_h != 0 ) {
      // Adding boxes in the R' region
      for (y = 0; y < side_length - r_p_v; y += r_p_v) {
        for (x = 0; x < region_split - r_p_h; x += r_p_h)
          result.push_back(BBox2i(x,y,r_p_h,r_p_v));
        result.push_back(BBox2i(x,y,region_split-x,r_p_v));
      }
      for (x = 0; x < region_split - r_p_h; x += r_p_h )
        result.push_back(BBox2i(x,y,r_p_h,side_length-y));
      result.push_back(BBox2i(x,y,region_split-x,side_length-y));
    }

    // Adding boxes in the R'' region
    if ( r_pp_h != 0 ) {
      for (y = 0; y < side_length - r_pp_v; y += r_pp_v) {
        for (x = region_split; x < side_length-r_pp_h; x += r_pp_h)
          result.push_back(BBox2i(x,y,r_pp_h,r_pp_v));
        result.push_back(BBox2i(x,y,side_length-x,r_pp_v));
      }
      for (x = region_split; x < side_length-r_pp_h; x += r_pp_h)
        result.push_back(BBox2i(x,y,r_pp_h,side_length-y));
      result.push_back(BBox2i(x,y,side_length-x,side_length-y));
    }

    return result;
  }

}


// Distributed under the MIT License.
// See LICENSE.txt for details.

#pragma once

#include <boost/functional/hash.hpp>
#include <cstddef>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>

#include "DataStructures/DataBox/DataBox.hpp"
#include "DataStructures/FixedHashMap.hpp"
#include "DataStructures/Tensor/EagerMath/Magnitude.hpp"
#include "Domain/FaceNormal.hpp"
#include "Domain/Structure/Direction.hpp"
#include "Domain/Structure/DirectionMap.hpp"
#include "Domain/Structure/Element.hpp"
#include "Domain/Structure/MaxNumberOfNeighbors.hpp"
#include "Domain/Tags.hpp"
#include "Domain/Tags/FaceNormal.hpp"
#include "Domain/Tags/Faces.hpp"
#include "Domain/Tags/SurfaceJacobian.hpp"
#include "Elliptic/BoundaryConditions/ApplyBoundaryCondition.hpp"
#include "Elliptic/DiscontinuousGalerkin/DgOperator.hpp"
#include "Elliptic/DiscontinuousGalerkin/Initialization.hpp"
#include "Elliptic/DiscontinuousGalerkin/Tags.hpp"
#include "Elliptic/Systems/GetSourcesComputer.hpp"
#include "Elliptic/Utilities/ApplyAt.hpp"
#include "NumericalAlgorithms/DiscontinuousGalerkin/HasReceivedFromAllMortars.hpp"
#include "NumericalAlgorithms/DiscontinuousGalerkin/MortarHelpers.hpp"
#include "NumericalAlgorithms/DiscontinuousGalerkin/Tags.hpp"
#include "NumericalAlgorithms/Spectral/Mesh.hpp"
#include "Parallel/AlgorithmMetafunctions.hpp"
#include "Parallel/GlobalCache.hpp"
#include "Parallel/InboxInserters.hpp"
#include "Parallel/Invoke.hpp"
#include "Utilities/ErrorHandling/Assert.hpp"
#include "Utilities/GetOutput.hpp"
#include "Utilities/Gsl.hpp"
#include "Utilities/Literals.hpp"
#include "Utilities/TMPL.hpp"
#include "Utilities/TaggedTuple.hpp"

/// Actions related to elliptic discontinuous Galerkin schemes
namespace elliptic::dg::Actions {
// The individual actions in this namespace are not exposed publicly because
// they don't work on their own. Instead, the public interface (defined below)
// exposes them in action lists.
namespace detail {

// This tag is used to communicate mortar data across neighbors
template <size_t Dim, typename TemporalIdTag, typename PrimalFields,
          typename PrimalFluxes>
struct MortarDataInboxTag
    : public Parallel::InboxInserters::Map<
          MortarDataInboxTag<Dim, TemporalIdTag, PrimalFields, PrimalFluxes>> {
  using temporal_id = typename TemporalIdTag::type;
  using type = std::map<
      temporal_id,
      FixedHashMap<maximum_number_of_neighbors(Dim), ::dg::MortarId<Dim>,
                   elliptic::dg::BoundaryData<PrimalFields, PrimalFluxes>,
                   boost::hash<::dg::MortarId<Dim>>>>;
};

// Initializes all quantities the DG operator needs on internal and external
// faces, as well as the mortars between neighboring elements. Also initializes
// the variable-independent background fields in the PDEs.
template <typename System, typename BackgroundTag>
struct InitializeFacesMortarsAndBackground {
 private:
  static constexpr size_t Dim = System::volume_dim;
  static constexpr bool has_background_fields =
      not std::is_same_v<typename System::background_fields, tmpl::list<>>;
  static_assert(
      not(has_background_fields and std::is_same_v<BackgroundTag, void>),
      "The system has background fields that need initialization. Supply a "
      "'BackgroundTag' to 'elliptic::dg::Actions::initialize_operator'.");

  using InitializeFacesAndMortars =
      elliptic::dg::InitializeFacesAndMortars<Dim,
                                              typename System::inv_metric_tag>;
  using InitializeBackground =
      elliptic::dg::InitializeBackground<Dim,
                                         typename System::background_fields>;

 public:
  using simple_tags = tmpl::append<
      typename InitializeFacesAndMortars::return_tags,
      tmpl::conditional_t<has_background_fields,
                          typename InitializeBackground::return_tags,
                          tmpl::list<>>>;
  using compute_tags = tmpl::list<>;

  template <typename DbTagsList, typename... InboxTags, typename Metavariables,
            typename ArrayIndex, typename ActionList,
            typename ParallelComponent>
  static std::tuple<db::DataBox<DbTagsList>&&> apply(
      db::DataBox<DbTagsList>& box,
      const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
      const Parallel::GlobalCache<Metavariables>& /*cache*/,
      const ArrayIndex& /*array_index*/, ActionList /*meta*/,
      const ParallelComponent* const /*meta*/) {
    if constexpr (has_background_fields) {
      const auto& background = db::get<BackgroundTag>(box);
      using background_classes =
          tmpl::at<typename Metavariables::factory_creation::factory_classes,
                   std::decay_t<decltype(background)>>;
      // Initialize faces and mortars
      db::mutate_apply<typename InitializeFacesAndMortars::return_tags,
                       typename InitializeFacesAndMortars::argument_tags>(
          InitializeFacesAndMortars{}, make_not_null(&box),
          db::get<domain::Tags::InitialExtents<Dim>>(box), background,
          background_classes{});
      // Initialize background fields
      db::mutate_apply<typename InitializeBackground::return_tags,
                       typename InitializeBackground::argument_tags>(
          InitializeBackground{}, make_not_null(&box), background,
          background_classes{});
    } else {
      // Initialize faces and mortars
      db::mutate_apply<typename InitializeFacesAndMortars::return_tags,
                       typename InitializeFacesAndMortars::argument_tags>(
          InitializeFacesAndMortars{}, make_not_null(&box),
          db::get<domain::Tags::InitialExtents<Dim>>(box));
    }
    return {std::move(box)};
  }
};

// Compute auxiliary variables and fluxes from the primal variables, prepare the
// local side of all mortars and send the local mortar data to neighbors. Also
// handle boundary conditions by preparing the exterior ("ghost") side of
// external mortars.
template <typename System, bool Linearized, typename TemporalIdTag,
          typename PrimalFieldsTag, typename PrimalFluxesTag,
          typename OperatorAppliedToFieldsTag, typename PrimalMortarFieldsTag,
          typename PrimalMortarFluxesTag,
          typename FluxesArgsTags =
              typename System::fluxes_computer::argument_tags,
          typename SourcesArgsTags = typename elliptic::get_sources_computer<
              System, Linearized>::argument_tags>
struct PrepareAndSendMortarData;

template <typename System, bool Linearized, typename TemporalIdTag,
          typename PrimalFieldsTag, typename PrimalFluxesTag,
          typename OperatorAppliedToFieldsTag, typename PrimalMortarFieldsTag,
          typename PrimalMortarFluxesTag, typename... FluxesArgsTags,
          typename... SourcesArgsTags>
struct PrepareAndSendMortarData<
    System, Linearized, TemporalIdTag, PrimalFieldsTag, PrimalFluxesTag,
    OperatorAppliedToFieldsTag, PrimalMortarFieldsTag, PrimalMortarFluxesTag,
    tmpl::list<FluxesArgsTags...>, tmpl::list<SourcesArgsTags...>> {
 private:
  static constexpr size_t Dim = System::volume_dim;
  using all_mortar_data_tag = ::Tags::Mortars<
      elliptic::dg::Tags::MortarData<typename TemporalIdTag::type,
                                     typename PrimalMortarFieldsTag::tags_list,
                                     typename PrimalMortarFluxesTag::tags_list>,
      Dim>;
  using mortar_data_inbox_tag =
      MortarDataInboxTag<Dim, TemporalIdTag,
                         typename PrimalMortarFieldsTag::tags_list,
                         typename PrimalMortarFluxesTag::tags_list>;
  using BoundaryConditionsBase = typename System::boundary_conditions_base;

 public:
  // Request these tags be added to the DataBox by the `SetupDataBox` action. We
  // don't actually need to initialize them, because the `TemporalIdTag` and the
  // `PrimalFieldsTag` will be set by other actions before applying the operator
  // and the remaining tags hold output of the operator.
  using simple_tags =
      tmpl::list<TemporalIdTag, PrimalFieldsTag, PrimalFluxesTag,
                 OperatorAppliedToFieldsTag, all_mortar_data_tag>;
  using compute_tags = tmpl::list<>;

  template <typename DbTagsList, typename... InboxTags, typename Metavariables,
            typename ActionList, typename ParallelComponent>
  static std::tuple<db::DataBox<DbTagsList>&&> apply(
      db::DataBox<DbTagsList>& box,
      const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
      Parallel::GlobalCache<Metavariables>& cache,
      const ElementId<Dim>& element_id, const ActionList /*meta*/,
      const ParallelComponent* const /*meta*/) {
    // Used to retrieve items out of the DataBox to forward to functions
    const auto get_items = [](const auto&... args) {
      return std::forward_as_tuple(args...);
    };
    const auto& temporal_id = db::get<TemporalIdTag>(box);
    const auto& element = db::get<domain::Tags::Element<Dim>>(box);
    const auto& mesh = db::get<domain::Tags::Mesh<Dim>>(box);
    const size_t num_points = mesh.number_of_grid_points();
    const auto& mortar_meshes =
        db::get<::Tags::Mortars<domain::Tags::Mesh<Dim - 1>, Dim>>(box);
    const auto& domain = db::get<domain::Tags::Domain<Dim>>(box);
    const auto& boundary_conditions = domain.blocks()
                                          .at(element_id.block_id())
                                          .external_boundary_conditions();
    const auto apply_boundary_condition =
        [&box, &boundary_conditions, &element_id](
            const Direction<Dim>& direction, const auto... fields_and_fluxes) {
          ASSERT(
              boundary_conditions.contains(direction),
              "No boundary condition is available in block "
                  << element_id.block_id() << " in direction " << direction
                  << ". Make sure you are setting up boundary conditions when "
                     "creating the domain.");
          ASSERT(dynamic_cast<const BoundaryConditionsBase*>(
                     boundary_conditions.at(direction).get()) != nullptr,
                 "The boundary condition in block "
                     << element_id.block_id() << " in direction " << direction
                     << " has an unexpected type. Make sure it derives off the "
                        "'boundary_conditions_base' class set in the system.");
          const auto& boundary_condition =
              dynamic_cast<const BoundaryConditionsBase&>(
                  *boundary_conditions.at(direction));
          elliptic::apply_boundary_condition<Linearized>(
              boundary_condition, box, direction, fields_and_fluxes...);
        };

    // Can't `db::get` the arguments for the boundary conditions within
    // `db::mutate`, so we extract the data to mutate and move it back in when
    // we're done.
    typename PrimalFluxesTag::type primal_fluxes;
    typename all_mortar_data_tag::type all_mortar_data;
    db::mutate<PrimalFluxesTag, all_mortar_data_tag>(
        make_not_null(&box),
        [&primal_fluxes, &all_mortar_data](const auto local_primal_fluxes,
                                           const auto local_all_mortar_data) {
          primal_fluxes = std::move(*local_primal_fluxes);
          all_mortar_data = std::move(*local_all_mortar_data);
        });

    // Prepare mortar data
    //
    // These memory buffers will be discarded when the action returns so we
    // don't inflate the memory usage of the simulation when the element is
    // inactive.
    Variables<typename System::auxiliary_fields> auxiliary_fields_buffer{
        num_points};
    Variables<typename System::auxiliary_fluxes> auxiliary_fluxes_buffer{
        num_points};
    using fluxes_args_tags = typename System::fluxes_computer::argument_tags;
    using fluxes_args_volume_tags =
        typename System::fluxes_computer::volume_tags;
    DirectionMap<Dim, std::tuple<decltype(db::get<FluxesArgsTags>(box))...>>
        fluxes_args_on_faces{};
    for (const auto& direction : Direction<Dim>::all_directions()) {
      fluxes_args_on_faces.emplace(
          direction, elliptic::util::apply_at<
                         domain::make_faces_tags<Dim, fluxes_args_tags,
                                                 fluxes_args_volume_tags>,
                         fluxes_args_volume_tags>(get_items, box, direction));
    }
    elliptic::dg::prepare_mortar_data<System, Linearized>(
        make_not_null(&auxiliary_fields_buffer),
        make_not_null(&auxiliary_fluxes_buffer), make_not_null(&primal_fluxes),
        make_not_null(&all_mortar_data), db::get<PrimalFieldsTag>(box), element,
        db::get<domain::Tags::Mesh<Dim>>(box),
        db::get<domain::Tags::InverseJacobian<Dim, Frame::ElementLogical,
                                              Frame::Inertial>>(box),
        db::get<domain::Tags::Faces<Dim, domain::Tags::FaceNormal<Dim>>>(box),
        db::get<domain::Tags::Faces<
            Dim, domain::Tags::UnnormalizedFaceNormalMagnitude<Dim>>>(box),
        mortar_meshes,
        db::get<::Tags::Mortars<::Tags::MortarSize<Dim - 1>, Dim>>(box),
        temporal_id, apply_boundary_condition,
        std::forward_as_tuple(db::get<FluxesArgsTags>(box)...),
        std::forward_as_tuple(db::get<SourcesArgsTags>(box)...),
        fluxes_args_on_faces);

    // Move the mutated data back into the DataBox
    db::mutate<PrimalFluxesTag, all_mortar_data_tag>(
        make_not_null(&box),
        [&primal_fluxes, &all_mortar_data](const auto local_primal_fluxes,
                                           const auto local_all_mortar_data) {
          *local_primal_fluxes = std::move(primal_fluxes);
          *local_all_mortar_data = std::move(all_mortar_data);
        });

    // Send mortar data to neighbors
    auto& receiver_proxy =
        Parallel::get_parallel_component<ParallelComponent>(cache);
    for (const auto& [direction, neighbors] : element.neighbors()) {
      const size_t dimension = direction.dimension();
      const auto& orientation = neighbors.orientation();
      const auto direction_from_neighbor = orientation(direction.opposite());
      for (const auto& neighbor_id : neighbors) {
        const ::dg::MortarId<Dim> mortar_id{direction, neighbor_id};
        // Make a copy of the local boundary data on the mortar to send to the
        // neighbor
        auto remote_boundary_data_on_mortar =
            get<all_mortar_data_tag>(box).at(mortar_id).local_data(
                temporal_id);
        // Reorient the data to the neighbor orientation if necessary
        if (not orientation.is_aligned()) {
          remote_boundary_data_on_mortar.orient_on_slice(
              mortar_meshes.at(mortar_id).extents(), dimension, orientation);
        }
        // Send remote data to neighbor
        Parallel::receive_data<mortar_data_inbox_tag>(
            receiver_proxy[neighbor_id], temporal_id,
            std::make_pair(
                ::dg::MortarId<Dim>{direction_from_neighbor, element.id()},
                std::move(remote_boundary_data_on_mortar)));
      }  // loop over neighbors in direction
    }    // loop over directions

    return {std::move(box)};
  }
};

// Wait until all mortar data from neighbors is available. Then add boundary
// corrections to the primal fluxes, compute their derivatives (i.e. the second
// derivatives of the primal variables) and add boundary corrections to the
// result.
template <typename System, bool Linearized, typename TemporalIdTag,
          typename PrimalFieldsTag, typename PrimalFluxesTag,
          typename OperatorAppliedToFieldsTag, typename PrimalMortarFieldsTag,
          typename PrimalMortarFluxesTag,
          typename FluxesArgsTags =
              typename System::fluxes_computer::argument_tags,
          typename SourcesArgsTags = typename elliptic::get_sources_computer<
              System, Linearized>::argument_tags>
struct ReceiveMortarDataAndApplyOperator;

template <typename System, bool Linearized, typename TemporalIdTag,
          typename PrimalFieldsTag, typename PrimalFluxesTag,
          typename OperatorAppliedToFieldsTag, typename PrimalMortarFieldsTag,
          typename PrimalMortarFluxesTag, typename... FluxesArgsTags,
          typename... SourcesArgsTags>
struct ReceiveMortarDataAndApplyOperator<
    System, Linearized, TemporalIdTag, PrimalFieldsTag, PrimalFluxesTag,
    OperatorAppliedToFieldsTag, PrimalMortarFieldsTag, PrimalMortarFluxesTag,
    tmpl::list<FluxesArgsTags...>, tmpl::list<SourcesArgsTags...>> {
 private:
  static constexpr size_t Dim = System::volume_dim;
  using all_mortar_data_tag = ::Tags::Mortars<
      elliptic::dg::Tags::MortarData<typename TemporalIdTag::type,
                                     typename PrimalMortarFieldsTag::tags_list,
                                     typename PrimalMortarFluxesTag::tags_list>,
      Dim>;
  using mortar_data_inbox_tag =
      MortarDataInboxTag<Dim, TemporalIdTag,
                         typename PrimalMortarFieldsTag::tags_list,
                         typename PrimalMortarFluxesTag::tags_list>;

 public:
  using const_global_cache_tags =
      tmpl::list<elliptic::dg::Tags::PenaltyParameter,
                 elliptic::dg::Tags::Massive>;
  using inbox_tags = tmpl::list<mortar_data_inbox_tag>;

  template <typename DbTags, typename... InboxTags, typename Metavariables,
            typename ArrayIndex, typename ActionList,
            typename ParallelComponent>
  static std::tuple<db::DataBox<DbTags>&&, Parallel::AlgorithmExecution> apply(
      db::DataBox<DbTags>& box, tuples::TaggedTuple<InboxTags...>& inboxes,
      const Parallel::GlobalCache<Metavariables>& /*cache*/,
      const ArrayIndex& /*array_index*/, const ActionList /*meta*/,
      const ParallelComponent* const /*meta*/) {
    const auto& temporal_id = get<TemporalIdTag>(box);
    const auto& element = get<domain::Tags::Element<Dim>>(box);

    if (not ::dg::has_received_from_all_mortars<mortar_data_inbox_tag>(
            temporal_id, element, inboxes)) {
      return {std::move(box), Parallel::AlgorithmExecution::Retry};
    }

    // Move received "remote" mortar data into the DataBox
    if (LIKELY(element.number_of_neighbors() > 0)) {
      auto received_mortar_data =
          std::move(tuples::get<mortar_data_inbox_tag>(inboxes)
                        .extract(temporal_id)
                        .mapped());
      db::mutate<all_mortar_data_tag>(
          make_not_null(&box),
          [&received_mortar_data, &temporal_id](const auto all_mortar_data) {
            for (auto& [mortar_id, mortar_data] : received_mortar_data) {
              all_mortar_data->at(mortar_id).remote_insert(
                  temporal_id, std::move(mortar_data));
            }
          });
    }

    // Apply DG operator
    db::mutate<OperatorAppliedToFieldsTag, all_mortar_data_tag>(
        make_not_null(&box),
        [](const auto&... args) {
          elliptic::dg::apply_operator<System, Linearized>(args...);
        },
        db::get<PrimalFieldsTag>(box), db::get<PrimalFluxesTag>(box), element,
        db::get<domain::Tags::Mesh<Dim>>(box),
        db::get<domain::Tags::InverseJacobian<Dim, Frame::ElementLogical,
                                              Frame::Inertial>>(box),
        db::get<domain::Tags::DetInvJacobian<Frame::ElementLogical,
                                             Frame::Inertial>>(box),
        db::get<domain::Tags::Faces<
            Dim, domain::Tags::UnnormalizedFaceNormalMagnitude<Dim>>>(box),
        db::get<domain::Tags::Faces<
            Dim, domain::Tags::DetSurfaceJacobian<Frame::ElementLogical,
                                                  Frame::Inertial>>>(box),
        db::get<::Tags::Mortars<domain::Tags::Mesh<Dim - 1>, Dim>>(box),
        db::get<::Tags::Mortars<::Tags::MortarSize<Dim - 1>, Dim>>(box),
        db::get<::Tags::Mortars<domain::Tags::DetSurfaceJacobian<
                                    Frame::ElementLogical, Frame::Inertial>,
                                Dim>>(box),
        db::get<elliptic::dg::Tags::PenaltyParameter>(box),
        db::get<elliptic::dg::Tags::Massive>(box), temporal_id,
        std::forward_as_tuple(db::get<SourcesArgsTags>(box)...));

    return {std::move(box), Parallel::AlgorithmExecution::Continue};
  }
};

}  // namespace detail

/*!
 * \brief Initialize geometric and background quantities for the elliptic DG
 * operator
 *
 * The geometric and background quantities are initialized together because the
 * geometry depends on the background metric through the normalization of face
 * normals. Other examples for background fields are curvature quantities
 * associated with the background metric, or matter sources such as a
 * mass-density in the XCTS equations. All `System::background_fields` are
 * retrieved from the `BackgroundTag` together, to enable re-using cached
 * temporary quantities in the computations. The `variables` function is invoked
 * on the `BackgroundTag` with the inertial coordinates, the element's `Mesh`
 * and the element's inverse Jacobian. These arguments allow computing numeric
 * derivatives, if necessary. The `BackgroundTag` can be set to `void` (default)
 * if the `System` has no background fields.
 *
 * DataBox:
 * - Uses:
 *   - `domain::Tags::InitialExtents<Dim>`
 *   - `BackgroundTag`
 * - Adds:
 *   - `::Tags::Mortars<domain::Tags::Mesh<Dim - 1>, Dim>`
 *   - `::Tags::Mortars<::Tags::MortarSize<Dim - 1>, Dim>`
 *   - `::Tags::Variables<background_fields>`
 * - Adds on internal and external faces:
 *   - `domain::Tags::Coordinates<Dim, Frame::Inertial>`
 *   - `::Tags::Normalized<domain::Tags::UnnormalizedFaceNormal<Dim>>`
 *   - `::Tags::Magnitude<domain::Tags::UnnormalizedFaceNormal<Dim>>`
 *   - `::Tags::Variables<background_fields>`
 *
 * \note This action relies on the `SetupDataBox` aggregated initialization
 * mechanism, so `Actions::SetupDataBox` must be present in the `Initialization`
 * phase action list prior to this action.
 *
 * \see elliptic::dg::Actions::apply_operator
 */
template <typename System, typename BackgroundTag = void>
using initialize_operator = tmpl::list<
    detail::InitializeFacesMortarsAndBackground<System, BackgroundTag>>;

/*!
 * \brief Apply the DG operator to the `PrimalFieldsTag` and write the result to
 * the `OperatorAppliedToFieldsTag`
 *
 * Add this list to the action list of a parallel component to compute the
 * elliptic DG operator or its linearization. The operator involves a
 * communication between nearest-neighbor elements. See `elliptic::dg` for
 * details on the elliptic DG operator. Make sure to add
 * `elliptic::dg::Actions::initialize_operator` to the initialization phase of
 * your parallel component so the required DataBox tags are set up before
 * applying the operator.
 *
 * The result of the computation is written to the `OperatorAppliedToFieldsTag`.
 * Additionally, the primal fluxes are written to the `PrimalFluxesTag` as an
 * intermediate result. The auxiliary fields and fluxes are discarded to avoid
 * inflating the memory usage.
 *
 * You can specify the `PrimalMortarFieldsTag` and the `PrimalMortarFluxesTag`
 * to re-use mortar-data memory buffers from other operator applications, for
 * example when applying the nonlinear and linearized operator. They default to
 * the `PrimalFieldsTag` and the `PrimalFluxesTag`, meaning memory buffers
 * corresponding to these tags are set up in the DataBox.
 */
template <typename System, bool Linearized, typename TemporalIdTag,
          typename PrimalFieldsTag, typename PrimalFluxesTag,
          typename OperatorAppliedToFieldsTag,
          typename PrimalMortarFieldsTag = PrimalFieldsTag,
          typename PrimalMortarFluxesTag = PrimalFluxesTag>
using apply_operator =
    tmpl::list<detail::PrepareAndSendMortarData<
                   System, Linearized, TemporalIdTag, PrimalFieldsTag,
                   PrimalFluxesTag, OperatorAppliedToFieldsTag,
                   PrimalMortarFieldsTag, PrimalMortarFluxesTag>,
               detail::ReceiveMortarDataAndApplyOperator<
                   System, Linearized, TemporalIdTag, PrimalFieldsTag,
                   PrimalFluxesTag, OperatorAppliedToFieldsTag,
                   PrimalMortarFieldsTag, PrimalMortarFluxesTag>>;

/*!
 * \brief For linear systems, impose inhomogeneous boundary conditions as
 * contributions to the fixed sources (i.e. the RHS of the equations).
 *
 * \see elliptic::dg::impose_inhomogeneous_boundary_conditions_on_source
 */
template <
    typename System, typename FixedSourcesTag,
    typename FluxesArgsTags = typename System::fluxes_computer::argument_tags,
    typename SourcesArgsTags = typename System::sources_computer::argument_tags>
struct ImposeInhomogeneousBoundaryConditionsOnSource;

/// \cond
template <typename System, typename FixedSourcesTag, typename... FluxesArgsTags,
          typename... SourcesArgsTags>
struct ImposeInhomogeneousBoundaryConditionsOnSource<
    System, FixedSourcesTag, tmpl::list<FluxesArgsTags...>,
    tmpl::list<SourcesArgsTags...>> {
 private:
  static constexpr size_t Dim = System::volume_dim;
  using BoundaryConditionsBase = typename System::boundary_conditions_base;

 public:
  using const_global_cache_tags =
      tmpl::list<elliptic::dg::Tags::PenaltyParameter,
                 elliptic::dg::Tags::Massive>;

  template <typename DbTags, typename... InboxTags, typename Metavariables,
            typename ActionList, typename ParallelComponent>
  static std::tuple<db::DataBox<DbTags>&&> apply(
      db::DataBox<DbTags>& box,
      const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
      const Parallel::GlobalCache<Metavariables>& /*cache*/,
      const ElementId<Dim>& element_id, const ActionList /*meta*/,
      const ParallelComponent* const /*meta*/) {
    // Used to retrieve items out of the DataBox to forward to functions
    const auto get_items = [](const auto&... args) {
      return std::forward_as_tuple(args...);
    };
    const auto& domain = db::get<domain::Tags::Domain<Dim>>(box);
    const auto& boundary_conditions = domain.blocks()
                                          .at(element_id.block_id())
                                          .external_boundary_conditions();
    const auto apply_boundary_condition =
        [&box, &boundary_conditions, &element_id](
            const Direction<Dim>& direction, const auto... fields_and_fluxes) {
          ASSERT(
              boundary_conditions.contains(direction),
              "No boundary condition is available in block "
                  << element_id.block_id() << " in direction " << direction
                  << ". Make sure you are setting up boundary conditions when "
                     "creating the domain.");
          ASSERT(dynamic_cast<const BoundaryConditionsBase*>(
                     boundary_conditions.at(direction).get()) != nullptr,
                 "The boundary condition in block "
                     << element_id.block_id() << " in direction " << direction
                     << " has an unexpected type. Make sure it derives off the "
                        "'boundary_conditions_base' class set in the system.");
          const auto& boundary_condition =
              dynamic_cast<const BoundaryConditionsBase&>(
                  *boundary_conditions.at(direction));
          elliptic::apply_boundary_condition<false>(
              boundary_condition, box, direction, fields_and_fluxes...);
        };

    // Can't `db::get` the arguments for the boundary conditions within
    // `db::mutate`, so we extract the data to mutate and move it back in when
    // we're done.
    typename FixedSourcesTag::type fixed_sources;
    db::mutate<FixedSourcesTag>(
        make_not_null(&box), [&fixed_sources](const auto local_fixed_sources) {
          fixed_sources = std::move(*local_fixed_sources);
        });

    using fluxes_args_tags = typename System::fluxes_computer::argument_tags;
    using fluxes_args_volume_tags =
        typename System::fluxes_computer::volume_tags;
    DirectionMap<Dim, std::tuple<decltype(db::get<FluxesArgsTags>(box))...>>
        fluxes_args_on_faces{};
    for (const auto& direction : Direction<Dim>::all_directions()) {
      fluxes_args_on_faces.emplace(
          direction, elliptic::util::apply_at<
                         domain::make_faces_tags<Dim, fluxes_args_tags,
                                                 fluxes_args_volume_tags>,
                         fluxes_args_volume_tags>(get_items, box, direction));
    }

    elliptic::dg::impose_inhomogeneous_boundary_conditions_on_source<System>(
        make_not_null(&fixed_sources), db::get<domain::Tags::Element<Dim>>(box),
        db::get<domain::Tags::Mesh<Dim>>(box),
        db::get<domain::Tags::InverseJacobian<Dim, Frame::ElementLogical,
                                              Frame::Inertial>>(box),
        db::get<domain::Tags::DetInvJacobian<Frame::ElementLogical,
                                             Frame::Inertial>>(box),
        db::get<domain::Tags::Faces<Dim, domain::Tags::FaceNormal<Dim>>>(box),
        db::get<domain::Tags::Faces<
            Dim, domain::Tags::UnnormalizedFaceNormalMagnitude<Dim>>>(box),
        db::get<::Tags::Mortars<domain::Tags::Mesh<Dim - 1>, Dim>>(box),
        db::get<::Tags::Mortars<::Tags::MortarSize<Dim - 1>, Dim>>(box),
        db::get<elliptic::dg::Tags::PenaltyParameter>(box),
        db::get<elliptic::dg::Tags::Massive>(box), apply_boundary_condition,
        std::forward_as_tuple(db::get<FluxesArgsTags>(box)...),
        std::forward_as_tuple(db::get<SourcesArgsTags>(box)...),
        fluxes_args_on_faces);

    // Move the mutated data back into the DataBox
    db::mutate<FixedSourcesTag>(
        make_not_null(&box), [&fixed_sources](const auto local_fixed_sources) {
          *local_fixed_sources = std::move(fixed_sources);
        });
    return {std::move(box)};
  }
};
/// \endcond

}  // namespace elliptic::dg::Actions


//---------------------------------------------------------------------------//
// Copyright (c) 2020-2021 Mikhail Komarov <nemo@nil.foundation>
// Copyright (c) 2020-2021 Ilias Khairullin <ilias@nil.foundation>
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_DETAIL_STRXOR_HPP
#define CRYPTO3_DETAIL_STRXOR_HPP

#include <iterator>

#include <boost/concept/assert.hpp>
#include <boost/assert.hpp>
#include <boost/range/concepts.hpp>

namespace nil {
    namespace crypto3 {
        namespace detail {
            template<typename InputIterator1, typename InputIterator2, typename OutputIterator>
            constexpr inline typename std::enable_if<
                std::is_same<typename std::iterator_traits<InputIterator1>::value_type,
                             typename std::iterator_traits<InputIterator2>::value_type>::value &&
                    std::is_same<typename std::iterator_traits<InputIterator1>::value_type,
                                 typename std::iterator_traits<OutputIterator>::value_type>::value,
                OutputIterator>::type
                strxor(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2,
                       OutputIterator out) {
                BOOST_CONCEPT_ASSERT((boost::InputIteratorConcept<InputIterator1>));
                BOOST_CONCEPT_ASSERT((boost::InputIteratorConcept<InputIterator2>));
                BOOST_CONCEPT_ASSERT(
                    (boost::OutputIteratorConcept<OutputIterator,
                                                  typename std::iterator_traits<OutputIterator>::value_type>));

                assert(std::distance(first1, last1) == std::distance(first2, last2));

                for (; first1 != last1 && first2 != last2; first1++, first2++, out++) {
                    *out = *first1 ^ *first2;
                }

                return out;
            }

            template<typename InputRange1, typename InputRange2, typename OutputIterator>
            constexpr inline OutputIterator strxor(const InputRange1 &in1, const InputRange2 &in2, OutputIterator out) {
                BOOST_CONCEPT_ASSERT((boost::SinglePassRangeConcept<InputRange1>));
                BOOST_CONCEPT_ASSERT((boost::SinglePassRangeConcept<InputRange2>));

                return strxor(in1.cbegin(), in1.cend(), in2.cbegin(), in2.cend(), out);
            }
        }    // namespace detail
    }        // namespace crypto3
}    // namespace nil

#endif    // CRYPTO3_DETAIL_STRXOR_HPP


///////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 1998-2011, Industrial Light & Magic, a division of Lucas
// Digital Ltd. LLC
//
// 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 Industrial Light & Magic 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 AND CONTRIBUTORS
// "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.
//
///////////////////////////////////////////////////////////////////////////

#include "PyIlmBaseConfigInternal.h"

#define BOOST_PYTHON_MAX_ARITY 17

#include "PyImathMatrix.h"
#include "PyImathExport.h"
#include "PyImathDecorators.h"
#include <Python.h>
#include <boost/python.hpp>
#include <boost/python/make_constructor.hpp>
#include <boost/format.hpp>
#include <boost/python/tuple.hpp>
#include <boost/python/dict.hpp>
#include <boost/python/raw_function.hpp>
#include "PyImath.h"
#include "PyImathVec.h"
#include "PyImathMathExc.h"
#include <ImathVec.h>
#include <ImathMatrixAlgo.h>
#include <Iex.h>

namespace PyImath {

template<> const char *PyImath::M33fArray::name() { return "M33fArray"; }
template<> const char *PyImath::M33dArray::name() { return "M33dArray"; }

using namespace boost::python;
using namespace IMATH_NAMESPACE;

template <class T, int len>
struct MatrixRow {
    explicit MatrixRow(T *data) : _data(data) {}
    T & operator [] (int i) { return _data[i]; }
    T *_data;

    static const char *name;
    static void register_class()
    {
        typedef PyImath::StaticFixedArray<MatrixRow,T,len> MatrixRow_helper;
        class_<MatrixRow> matrixRow_class(name,no_init);
        matrixRow_class
            .def("__len__", MatrixRow_helper::len)
            .def("__getitem__", MatrixRow_helper::getitem,return_value_policy<copy_non_const_reference>())
            .def("__setitem__", MatrixRow_helper::setitem)
            ;
    }
};

template <> const char *MatrixRow<float,3>::name = "M33fRow";
template <> const char *MatrixRow<double,3>::name = "M33dRow";

template <class Container, class Data, int len>
struct IndexAccessMatrixRow {
    typedef MatrixRow<Data,len> result_type;
    static MatrixRow<Data,len> apply(Container &c, int i) { return MatrixRow<Data,len>(c[i]); }
};

template <class T> struct Matrix33Name { static const char *value; };
template<> const char *Matrix33Name<float>::value  = "M33f";
template<> const char *Matrix33Name<double>::value = "M33d";

template <class T>
static std::string Matrix33_str(const Matrix33<T> &v)
{
    std::stringstream stream;
    stream << Matrix33Name<T>::value << "(";
    for (int row = 0; row < 3; row++)
    {
        stream << "(";
	for (int col = 0; col < 3; col++)
	{
	    stream << v[row][col];
            stream << (col != 2 ? ", " : "");
	}
        stream << ")" << (row != 2 ? ", " : "");
    }
    stream << ")";
    return stream.str();
}

// Non-specialized repr is same as str
template <class T>
static std::string Matrix33_repr(const Matrix33<T> &v)
{
    return Matrix33_str(v);
}

// Specialization for float to full precision
template <>
std::string Matrix33_repr(const Matrix33<float> &v)
{
    return (boost::format("%s((%.9g, %.9g, %.9g), (%.9g, %.9g, %.9g), (%.9g, %.9g, %.9g))")
                        % Matrix33Name<float>::value
                        % v[0][0] % v[0][1] % v[0][2]
                        % v[1][0] % v[1][1] % v[1][2]
                        % v[2][0] % v[2][1] % v[2][2]).str();
}

// Specialization for double to full precision
template <>
std::string Matrix33_repr(const Matrix33<double> &v)
{
    return (boost::format("%s((%.17g, %.17g, %.17g), (%.17g, %.17g, %.17g), (%.17g, %.17g, %.17g))")
                        % Matrix33Name<double>::value
                        % v[0][0] % v[0][1] % v[0][2]
                        % v[1][0] % v[1][1] % v[1][2]
                        % v[2][0] % v[2][1] % v[2][2]).str();
}

template <class T>
static const Matrix33<T> &
invert33 (Matrix33<T> &m, bool singExc = true)
{
    MATH_EXC_ON;
    return m.invert(singExc);
}

template <class T>
static Matrix33<T>
inverse33 (Matrix33<T> &m, bool singExc = true)
{
    MATH_EXC_ON;
    return m.inverse(singExc);
}

template <class T>
static const Matrix33<T> &
gjInvert33 (Matrix33<T> &m, bool singExc = true)
{
    MATH_EXC_ON;
    return m.gjInvert(singExc);
}

template <class T>
static Matrix33<T>
gjInverse33 (Matrix33<T> &m, bool singExc = true)
{
    MATH_EXC_ON;
    return m.gjInverse(singExc);
}

template <class T, class U>
static const Matrix33<T> &
iadd33(Matrix33<T> &m, const Matrix33<U> &m2)
{
    MATH_EXC_ON;
    Matrix33<T> m3;
    m3.setValue (m2);
    return m += m3;
}

template <class T>
static const Matrix33<T> &
iadd33T(Matrix33<T> &mat, T a)
{
    MATH_EXC_ON;
    return mat += a;
}

template <class T>
static Matrix33<T>
add33(Matrix33<T> &m, const Matrix33<T> &m2)
{
    MATH_EXC_ON;
    return m + m2;
}

template <class T, class U>
static const Matrix33<T> &
isub33(Matrix33<T> &m, const Matrix33<U> &m2)
{
    MATH_EXC_ON;
    Matrix33<T> m3;
    m3.setValue (m2);
    return m -= m3;
}

template <class T>
static const Matrix33<T> &
isub33T(Matrix33<T> &mat, T a)
{
    MATH_EXC_ON;
    return mat -= a;
}

template <class T>
static Matrix33<T>
sub33(Matrix33<T> &m, const Matrix33<T> &m2)
{
    MATH_EXC_ON;
    return m - m2;
}

template <class T>
static const Matrix33<T> &
negate33 (Matrix33<T> &m)
{
    MATH_EXC_ON;
    return m.negate();
}

template <class T>
static Matrix33<T>
neg33 (Matrix33<T> &m)
{
    MATH_EXC_ON;
    return -m;
}

template <class T>
static const Matrix33<T> &
imul33T(Matrix33<T> &m, const T &t)
{
    MATH_EXC_ON;
    return m *= t;
}

template <class T>
static Matrix33<T>
mul33T(Matrix33<T> &m, const T &t)
{
    MATH_EXC_ON;
    return m * t;
}

template <class T>
static Matrix33<T>
rmul33T(Matrix33<T> &m, const T &t)
{
    MATH_EXC_ON;
    return t * m;
}

template <class T>
static const Matrix33<T> &
idiv33T(Matrix33<T> &m, const T &t)
{
    MATH_EXC_ON;
    return m /= t;
}

template <class T>
static Matrix33<T>
div33T(Matrix33<T> &m, const T &t)
{
    MATH_EXC_ON;
    return m / t;
}

template <class T>
static void
extractAndRemoveScalingAndShear33(Matrix33<T> &mat, IMATH_NAMESPACE::Vec2<T> &dstScl, IMATH_NAMESPACE::Vec2<T> &dstShr, int exc = 1)
{
    MATH_EXC_ON;
    T dstShrTmp;
    IMATH_NAMESPACE::extractAndRemoveScalingAndShear(mat, dstScl, dstShrTmp, exc);

    dstShr.setValue(dstShrTmp, T (0));
}

template <class T>
static void
extractEuler(Matrix33<T> &mat, Vec2<T> &dstObj)
{
    MATH_EXC_ON;
    T dst;
    IMATH_NAMESPACE::extractEuler(mat, dst);
    dstObj.setValue(dst, T (0));
}

template <class T>
static int
extractSHRT33(Matrix33<T> &mat, Vec2<T> &s, Vec2<T> &h, Vec2<T> &r, Vec2<T> &t, int exc = 1)
{
    MATH_EXC_ON;
    T hTmp, rTmp;

    int b = IMATH_NAMESPACE::extractSHRT(mat, s, hTmp, rTmp, t, exc);

    h.setValue(hTmp, T (0));
    r.setValue(rTmp, T (0));

    return b;
}

template <class T>
static void
extractScaling33(Matrix33<T> &mat, Vec2<T> &dst, int exc = 1)
{
    MATH_EXC_ON;
    IMATH_NAMESPACE::extractScaling(mat, dst, exc);
}

template <class T>
void
outerProduct33(Matrix33<T> &mat, const Vec3<T> &a, const Vec3<T> &b)
{
    MATH_EXC_ON;
    mat = IMATH_NAMESPACE::outerProduct(a,b);
}

template <class T>
static void
extractScalingAndShear33(Matrix33<T> &mat, Vec2<T> &dstScl, Vec2<T> &dstShr, int exc = 1)
{
    MATH_EXC_ON;
    T dstShrTmp;
    IMATH_NAMESPACE::extractScalingAndShear(mat, dstScl, dstShrTmp, exc);

    dstShr.setValue(dstShrTmp, T (0));
}

template <class TV,class TM>
static void
multDirMatrix33(Matrix33<TM> &mat, const Vec2<TV> &src, Vec2<TV> &dst)
{
    MATH_EXC_ON;
    mat.multDirMatrix(src, dst);
}

template <class TV,class TM>
static Vec2<TV>
multDirMatrix33_return_value(Matrix33<TM> &mat, const Vec2<TV> &src)
{
    MATH_EXC_ON;
    Vec2<TV> dst;
    mat.multDirMatrix(src, dst);
    return dst;
}

template <class TV,class TM>
static FixedArray<Vec2<TV> >
multDirMatrix33_array(Matrix33<TM> &mat, const FixedArray<Vec2<TV> >&src)
{
    MATH_EXC_ON;
    size_t len = src.len();
    FixedArray<Vec2<TV> > dst(len);
    for (size_t i=0; i<len; ++i) mat.multDirMatrix(src[i], dst[i]);
    return dst;
}

template <class TV,class TM>
static void
multVecMatrix33(Matrix33<TM> &mat, const Vec2<TV> &src, Vec2<TV> &dst)
{
    MATH_EXC_ON;
    mat.multVecMatrix(src, dst);
}

template <class TV,class TM>
static Vec2<TV>
multVecMatrix33_return_value(Matrix33<TM> &mat, const Vec2<TV> &src)
{
    MATH_EXC_ON;
    Vec2<TV> dst;
    mat.multVecMatrix(src, dst);
    return dst;
}

template <class TV,class TM>
static FixedArray<Vec2<TV> >
multVecMatrix33_array(Matrix33<TM> &mat, const FixedArray<Vec2<TV> >&src)
{
    MATH_EXC_ON;
    size_t len = src.len();
    FixedArray<Vec2<TV> > dst(len);
    for (size_t i=0; i<len; ++i) mat.multVecMatrix(src[i], dst[i]);
    return dst;
}

template <class T>
static int
removeScaling33(Matrix33<T> &mat, int exc = 1)
{
    MATH_EXC_ON;
    return IMATH_NAMESPACE::removeScaling(mat, exc);
}

template <class T>
static int
removeScalingAndShear33(Matrix33<T> &mat, int exc = 1)
{
    MATH_EXC_ON;
    return IMATH_NAMESPACE::removeScalingAndShear(mat, exc);
}

template <class T>
static const Matrix33<T> &
rotate33(Matrix33<T> &mat, const T &r)
{
    MATH_EXC_ON;
    return mat.rotate(r);
}

template <class T>
static Matrix33<T>
sansScaling33(const Matrix33<T> &mat, bool exc = true)
{
    MATH_EXC_ON;
    return IMATH_NAMESPACE::sansScaling(mat, exc);
}

template <class T>
static Matrix33<T>
sansScalingAndShear33(const Matrix33<T> &mat, bool exc = true)
{
    MATH_EXC_ON;
    return IMATH_NAMESPACE::sansScalingAndShear(mat, exc);
}

template <class T>
static const Matrix33<T> &
scaleSc33(Matrix33<T> &mat, const T &s)
{
    MATH_EXC_ON;
    Vec2<T> sVec(s, s);
    return mat.scale(sVec);
}

template <class T>
static const Matrix33<T> &
scaleV33(Matrix33<T> &mat, const Vec2<T> &s)
{
    MATH_EXC_ON;
    return mat.scale(s);
}

template <class T>
static const Matrix33<T> &
scale33Tuple(Matrix33<T> &mat, const tuple &t)
{
    MATH_EXC_ON;
    if(t.attr("__len__")() == 2)
    {
        Vec2<T> s;
        s.x = extract<T>(t[0]);
        s.y = extract<T>(t[1]);

        return mat.scale(s);
    }
    else
        THROW(IEX_NAMESPACE::LogicExc, "m.scale needs tuple of length 2");
}

template <class T>
static const Matrix33<T> &
setRotation33(Matrix33<T> &mat, const T &r)
{
    MATH_EXC_ON;
    return mat.setRotation(r);
}

template <class T>
static const Matrix33<T> &
setScaleSc33(Matrix33<T> &mat, const T &s)
{
    MATH_EXC_ON;
    Vec2<T> sVec(s, s);
    return mat.setScale(sVec);
}

template <class T>
static const Matrix33<T> &
setScaleV33(Matrix33<T> &mat, const Vec2<T> &s)
{
    MATH_EXC_ON;
    return mat.setScale(s);
}

template <class T>
static const Matrix33<T> &
setScale33Tuple(Matrix33<T> &mat, const tuple &t)
{
    MATH_EXC_ON;
    if(t.attr("__len__")() == 2)
    {
        Vec2<T> s;
        s.x = extract<T>(t[0]);
        s.y = extract<T>(t[1]);

        return mat.setScale(s);
    }
    else
        THROW(IEX_NAMESPACE::LogicExc, "m.setScale needs tuple of length 2");
}

template <class T>
static const Matrix33<T> &
setShearSc33(Matrix33<T> &mat, const T &h)
{
    MATH_EXC_ON;
    Vec2<T> hVec(h, T(0));
    return mat.setShear(hVec);
}

template <class T>
static const Matrix33<T> &
setShearV33(Matrix33<T> &mat, const Vec2<T> &h)
{
    MATH_EXC_ON;
    return mat.setShear(h);
}

template <class T>
static const Matrix33<T> &
setShear33Tuple(Matrix33<T> &mat, const tuple &t)
{
    MATH_EXC_ON;
    if(t.attr("__len__")() == 2)
    {
        Vec2<T> h;
        h.x = extract<T>(t[0]);
        h.y = extract<T>(t[1]);

        return mat.setShear(h);
    }
    else
        THROW(IEX_NAMESPACE::LogicExc, "m.shear needs tuple of length 2");
}

template <class T>
static const Matrix33<T> &
setTranslation33(Matrix33<T> &mat, const Vec2<T> &t)
{
    MATH_EXC_ON;
    return mat.setTranslation(t);
}

template <class T>
static const Matrix33<T> &
setTranslation33Tuple(Matrix33<T> &mat, const tuple &t)
{
    MATH_EXC_ON;
    if(t.attr("__len__")() == 2)
    {
        Vec2<T> trans;
        trans.x = extract<T>(t[0]);
        trans.y = extract<T>(t[1]);

        return mat.setTranslation(trans);
    }
    else
        THROW(IEX_NAMESPACE::LogicExc, "m.translate needs tuple of length 2");
}

template <class T>
static const Matrix33<T> &
setTranslation33Obj(Matrix33<T> &mat, const object &o)
{
    MATH_EXC_ON;
    Vec2<T> v;
    if (PyImath::V2<T>::convert (o.ptr(), &v))
    {
        return mat.setTranslation(v);
    }
    else
    {
        THROW(IEX_NAMESPACE::ArgExc, "m.setTranslation expected V2 argument");
        return mat;
    }
}

template <class T>
static void
setValue33(Matrix33<T> &mat, const Matrix33<T> &value)
{
    MATH_EXC_ON;
    mat.setValue(value);
}

template <class T>
static const Matrix33<T> &
shearSc33(Matrix33<T> &mat, const T &h)
{
    MATH_EXC_ON;
    Vec2<T> hVec(h, T (0));

    return mat.shear(hVec);
}

template <class T>
static const Matrix33<T> &
shearV33(Matrix33<T> &mat, const Vec2<T> &h)
{
    MATH_EXC_ON;
    return mat.shear(h);
}

template <class T>
static const Matrix33<T> &
shear33Tuple(Matrix33<T> &mat, const tuple &t)
{
    MATH_EXC_ON;
    if(t.attr("__len__")() == 2)
    {
        Vec2<T> h;
        h.x = extract<T>(t[0]);
        h.y = extract<T>(t[1]);

        return mat.shear(h);
    }
    else
        THROW(IEX_NAMESPACE::LogicExc, "m.shear needs tuple of length 2");
}

template <class T>
static const Matrix33<T> &
translate33(Matrix33<T> &mat, const object &t)
{
    MATH_EXC_ON;
    Vec2<T> v;
    if (PyImath::V2<T>::convert (t.ptr(), &v))
    {
        return mat.translate(v);
    }
    else
    {
        THROW(IEX_NAMESPACE::ArgExc, "m.translate expected V2 argument");
        return mat;
    }
}

template <class T>
static const Matrix33<T> &
translate33Tuple(Matrix33<T> &mat, const tuple &t)
{
    MATH_EXC_ON;
    if(t.attr("__len__")() == 2)
    {
        Vec2<T> trans;
        trans.x = extract<T>(t[0]);
        trans.y = extract<T>(t[1]);

        return mat.translate(trans);
    }
    else
        THROW(IEX_NAMESPACE::LogicExc, "m.translate needs tuple of length 2");
}

template <class T>
static Matrix33<T>
subtractTL33(Matrix33<T> &mat, T a)
{
    MATH_EXC_ON;
    Matrix33<T> m(mat.x);
    for(int i = 0; i < 3; ++i)
        for(int j = 0; j < 3; ++j)
            m.x[i][j] -= a;

    return m;
}

template <class T>
static Matrix33<T>
subtractTR33(Matrix33<T> &mat, T a)
{
    MATH_EXC_ON;
    Matrix33<T> m(mat.x);
    for(int i = 0; i < 3; ++i)
        for(int j = 0; j < 3; ++j)
            m.x[i][j] = a - m.x[i][j];

    return m;
}

template <class T>
static Matrix33<T>
add33T(Matrix33<T> &mat, T a)
{
    MATH_EXC_ON;
    Matrix33<T> m(mat.x);
    for(int i = 0; i < 3; ++i)
        for(int j = 0; j < 3; ++j)
            m.x[i][j] += a;

    return m;
}

template <class S, class T>
static Matrix33<T>
mul33(Matrix33<T> &mat1, Matrix33<S> &mat2)
{
    MATH_EXC_ON;
    Matrix33<T> mat2T;
    mat2T.setValue (mat2);
    return mat1 * mat2T;
}

template <class S, class T>
static Matrix33<T>
rmul33(Matrix33<T> &mat2, Matrix33<S> &mat1)
{
    MATH_EXC_ON;
    Matrix33<T> mat1T;
    mat1T.setValue (mat1);
    return mat1T * mat2;
}

template <class S, class T>
static const Matrix33<T> &
imul33(Matrix33<T> &mat1, Matrix33<S> &mat2)
{
    MATH_EXC_ON;
    Matrix33<T> mat2T;
    mat2T.setValue (mat2);
    return mat1 *= mat2T;
}

template <class T>
static bool
lessThan33(Matrix33<T> &mat1, const Matrix33<T> &mat2)
{
    for(int i = 0; i < 3; ++i){
        for(int j = 0; j < 3; ++j){
            if(mat1[i][j] > mat2[i][j]){
                return false;
            }
        }
    }

    return (mat1 != mat2);
}

template <class T>
static bool
lessThanEqual33(Matrix33<T> &mat1, const Matrix33<T> &mat2)
{
    for(int i = 0; i < 3; ++i){
        for(int j = 0; j < 3; ++j){
            if(mat1[i][j] > mat2[i][j]){
                return false;
            }
        }
    }

    return true;
}

template <class T>
static bool
greaterThan33(Matrix33<T> &mat1, const Matrix33<T> &mat2)
{
    for(int i = 0; i < 3; ++i){
        for(int j = 0; j < 3; ++j){
            if(mat1[i][j] < mat2[i][j]){
                std::cout << mat1[i][j] << " " << mat2[i][j] << std::endl;
                return false;
            }
        }
    }

    return (mat1 != mat2);
}

template <class T>
static bool
greaterThanEqual33(Matrix33<T> &mat1, const Matrix33<T> &mat2)
{
    for(int i = 0; i < 3; ++i){
        for(int j = 0; j < 3; ++j){
            if(mat1[i][j] < mat2[i][j]){
                return false;
            }
        }
    }

    return true;
}

template <class T>
static tuple
singularValueDecomposition33(const Matrix33<T>& m, bool forcePositiveDeterminant = false)
{
    IMATH_NAMESPACE::Matrix33<T> U, V;
    IMATH_NAMESPACE::Vec3<T> S;
    IMATH_NAMESPACE::jacobiSVD (m, U, S, V, IMATH_NAMESPACE::limits<T>::epsilon(), forcePositiveDeterminant);
    return make_tuple (U, S, V);
}

BOOST_PYTHON_FUNCTION_OVERLOADS(invert33_overloads, invert33, 1, 2);
BOOST_PYTHON_FUNCTION_OVERLOADS(inverse33_overloads, inverse33, 1, 2);
BOOST_PYTHON_FUNCTION_OVERLOADS(gjInvert33_overloads, gjInvert33, 1, 2);
BOOST_PYTHON_FUNCTION_OVERLOADS(gjInverse33_overloads, gjInverse33, 1, 2);
BOOST_PYTHON_FUNCTION_OVERLOADS(extractAndRemoveScalingAndShear33_overloads, extractAndRemoveScalingAndShear33, 3, 4)
BOOST_PYTHON_FUNCTION_OVERLOADS(extractSHRT33_overloads, extractSHRT33, 5, 6)
BOOST_PYTHON_FUNCTION_OVERLOADS(extractScaling33_overloads, extractScaling33, 2, 3)
BOOST_PYTHON_FUNCTION_OVERLOADS(extractScalingAndShear33_overloads, extractScalingAndShear33, 3, 4)
BOOST_PYTHON_FUNCTION_OVERLOADS(removeScaling33_overloads, removeScaling33, 1, 2)
BOOST_PYTHON_FUNCTION_OVERLOADS(removeScalingAndShear33_overloads, removeScalingAndShear33, 1, 2)
BOOST_PYTHON_FUNCTION_OVERLOADS(sansScaling33_overloads, sansScaling33, 1, 2)
BOOST_PYTHON_FUNCTION_OVERLOADS(sansScalingAndShear33_overloads, sansScalingAndShear33, 1, 2)
BOOST_PYTHON_FUNCTION_OVERLOADS(outerProduct33_overloads, outerProduct33, 3, 3);

template <class T>
static Matrix33<T> * Matrix3_tuple_constructor(const tuple &t0, const tuple &t1, const tuple &t2)
{
  if(t0.attr("__len__")() == 3 && t1.attr("__len__")() == 3 && t2.attr("__len__")() == 3)
  {
      return new Matrix33<T>(extract<T>(t0[0]),  extract<T>(t0[1]),  extract<T>(t0[2]),
                             extract<T>(t1[0]),  extract<T>(t1[1]),  extract<T>(t1[2]),
                             extract<T>(t2[0]),  extract<T>(t2[1]),  extract<T>(t2[2]));
  }
  else
      THROW(IEX_NAMESPACE::LogicExc, "Matrix33 takes 3 tuples of length 3");
}

template <class T, class S>
static Matrix33<T> *Matrix3_matrix_constructor(const Matrix33<S> &mat)
{
    Matrix33<T> *m = new Matrix33<T>;

    for(int i = 0; i < 3; ++i)
        for(int j = 0; j < 3; ++j)
            m->x[i][j] = T (mat.x[i][j]);

    return m;
}

template <class T>
class_<Matrix33<T> >
register_Matrix33()
{
    typedef PyImath::StaticFixedArray<Matrix33<T>,T,3,IndexAccessMatrixRow<Matrix33<T>,T,3> > Matrix33_helper;

    MatrixRow<T,3>::register_class();
    class_<Matrix33<T> > matrix33_class(Matrix33Name<T>::value, Matrix33Name<T>::value,init<Matrix33<T> >("copy construction"));
    matrix33_class
        .def(init<>("initialize to identity"))
        .def(init<T>("initialize all entries to a single value"))
        .def(init<T,T,T,T,T,T,T,T,T>("make from components"))
        .def("__init__", make_constructor(Matrix3_tuple_constructor<T>))
        .def("__init__", make_constructor(Matrix3_matrix_constructor<T,float>))
        .def("__init__", make_constructor(Matrix3_matrix_constructor<T,double>))

	//.def_readwrite("x00", &Matrix33<T>::x[0][0])
	//.def_readwrite("x01", &Matrix33<T>::x[0][1])
	//.def_readwrite("x02", &Matrix33<T>::x[0][2])
	//.def_readwrite("x10", &Matrix33<T>::x[1][0])
	//.def_readwrite("x11", &Matrix33<T>::x[1][1])
	//.def_readwrite("x12", &Matrix33<T>::x[1][2])
	//.def_readwrite("x20", &Matrix33<T>::x[2][0])
	//.def_readwrite("x21", &Matrix33<T>::x[2][1])
	//.def_readwrite("x22", &Matrix33<T>::x[2][2])
        .def("baseTypeEpsilon", &Matrix33<T>::baseTypeEpsilon,"baseTypeEpsilon() epsilon value of the base type of the vector")
        .staticmethod("baseTypeEpsilon")
        .def("baseTypeMax", &Matrix33<T>::baseTypeMax,"baseTypeMax() max value of the base type of the vector")
        .staticmethod("baseTypeMax")
        .def("baseTypeMin", &Matrix33<T>::baseTypeMin,"baseTypeMin() min value of the base type of the vector")
        .staticmethod("baseTypeMin")
        .def("baseTypeSmallest", &Matrix33<T>::baseTypeSmallest,"baseTypeSmallest() smallest value of the base type of the vector")
        .staticmethod("baseTypeSmallest")
        .def("equalWithAbsError", &Matrix33<T>::equalWithAbsError,"m1.equalWithAbsError(m2,e) true if the elements "
             "of v1 and v2 are the same with an absolute error of no more than e, "
             "i.e., abs(m1[i] - m2[i]) <= e")
        .def("equalWithRelError", &Matrix33<T>::equalWithRelError,"m1.equalWithAbsError(m2,e) true if the elements "
             "of m1 and m2 are the same with an absolute error of no more than e, "
             "i.e., abs(m1[i] - m2[i]) <= e * abs(m1[i])")
        // need a different version for matrix data access
        .def("__len__", Matrix33_helper::len)
        .def("__getitem__", Matrix33_helper::getitem)
	//.def("__setitem__", Matrix33_helper::setitem)
        .def("makeIdentity",&Matrix33<T>::makeIdentity,"makeIdentity() make this matrix the identity matrix")
        .def("transpose",&Matrix33<T>::transpose,return_internal_reference<>(),"transpose() transpose this matrix")
        .def("transposed",&Matrix33<T>::transposed,"transposed() return a transposed copy of this matrix")
        .def("invert",&invert33<T>,invert33_overloads("invert() invert this matrix")[return_internal_reference<>()])
        .def("inverse",&inverse33<T>,inverse33_overloads("inverse() return an inverted copy of this matrix"))
        .def("gjInvert",&gjInvert33<T>,gjInvert33_overloads("gjInvert() invert this matrix")[return_internal_reference<>()])
        .def("gjInverse",&gjInverse33<T>,gjInverse33_overloads("gjInverse() return an inverted copy of this matrix"))
        .def("minorOf",&Matrix33<T>::minorOf,"minorOf() return the matrix minor of the (row,col) element of this matrix")
        .def("fastMinor",&Matrix33<T>::fastMinor,"fastMinor() return the matrix minor using the specified rows and columns of this matrix")
        .def("determinant",&Matrix33<T>::determinant,"determinant() return the determinant of this matrix")
        .def(self == self) // NOSONAR - suppress SonarCloud bug report.
        .def(self != self) // NOSONAR - suppress SonarCloud bug report.
        .def("__iadd__", &iadd33<T, float>,return_internal_reference<>())
        .def("__iadd__", &iadd33<T, double>,return_internal_reference<>())
        .def("__iadd__", &iadd33T<T>,return_internal_reference<>())
        .def("__add__", &add33<T>)
        .def("__isub__", &isub33<T, float>,return_internal_reference<>())
        .def("__isub__", &isub33<T, double>,return_internal_reference<>())
        .def("__isub__", &isub33T<T>,return_internal_reference<>())
        .def("__sub__", &sub33<T>)
        .def("negate",&negate33<T>,return_internal_reference<>(),"negate() negate all entries in this matrix")
        .def("__neg__", &neg33<T>)
        .def("__imul__", &imul33T<T>,return_internal_reference<>())
        .def("__mul__", &mul33T<T>)
        .def("__rmul__", &rmul33T<T>)
        .def("__idiv__", &idiv33T<T>,return_internal_reference<>())
        .def("__itruediv__", &idiv33T<T>,return_internal_reference<>())
        .def("__div__", &div33T<T>)
        .def("__truediv__", &div33T<T>)
        .def("__add__", &add33T<T>)
        .def("__radd__", &add33T<T>)
        .def("__sub__", &subtractTL33<T>)
        .def("__rsub__", &subtractTR33<T>)
        .def("__mul__", &mul33<float, T>)
        .def("__mul__", &mul33<double, T>)
        .def("__rmul__", &rmul33<float, T>)
        .def("__rmul__", &rmul33<double, T>)
        .def("__imul__", &imul33<float, T>,return_internal_reference<>())
        .def("__imul__", &imul33<double, T>,return_internal_reference<>())
        .def("__lt__", &lessThan33<T>)
        .def("__le__", &lessThanEqual33<T>)
        .def("__gt__", &greaterThan33<T>)
        .def("__ge__", &greaterThanEqual33<T>)
	//.def(self_ns::str(self))
        .def("__str__",&Matrix33_str<T>)
        .def("__repr__",&Matrix33_repr<T>)
        .def("extractAndRemoveScalingAndShear", &extractAndRemoveScalingAndShear33<T>,
              extractAndRemoveScalingAndShear33_overloads(
              "M.extractAndRemoveScalingAndShear(scl, shr, "
              "[exc]) -- extracts the scaling component of "
              "M into scl and the shearing component of M "
              "into shr.  Also removes the scaling and "
              "shearing components from M.  "
              "Returns 1 unless the scaling component is "
              "nearly 0, in which case 0 is returned. "
              "If optional arg. exc == 1, then if the "
              "scaling component is nearly 0, then MathExc "
              "is thrown. "))

         .def("extractEuler", &extractEuler<T>,
              "M.extractEulerZYX(r) -- extracts the "
			  "rotation component of M into r. "
              "Assumes that M contains no shear or "
              "non-uniform scaling; results are "
              "meaningless if it does.")

         .def("extractSHRT", &extractSHRT33<T>, extractSHRT33_overloads(
              "M.extractSHRT(Vs, Vh, Vr, Vt, [exc]) -- "
	          "extracts the scaling component of M into Vs, "
			  "the shearing component of M in Vh (as XY, "
	          "XZ, YZ shear factors), the rotation of M "
	          "into Vr (as Euler angles in the order XYZ), "
	          "and the translaation of M into Vt. "
			  "If optional arg. exc == 1, then if the "
			  "scaling component is nearly 0, then MathExc "
			  "is thrown. "))

         .def("extractScaling", &extractScaling33<T>, extractScaling33_overloads("extract scaling"))
         .def("outerProduct", &outerProduct33<T>, outerProduct33_overloads(
              "M.outerProduct(Va,Vb) -- "
                  "Performs the outer product, or tensor product, of two 3D vectors, Va and Vb"))

         .def("extractScalingAndShear", &extractScalingAndShear33<T>, extractScalingAndShear33_overloads("extract scaling"))
        .def("singularValueDecomposition", &singularValueDecomposition33<T>,
             "Decomposes the matrix using the singular value decomposition (SVD) into three\n"
             "matrices U, S, and V which have the following properties: \n"
             "  1. U and V are both orthonormal matrices, \n"
             "  2. S is the diagonal matrix of singular values, \n"
             "  3. U * S * V.transposed() gives back the original matrix.\n"
             "The result is returned as a tuple [U, S, V].  Note that since S is diagonal we\n"
             "don't need to return the entire matrix, so we return it as a three-vector.  \n"
             "\n"
             "The 'forcePositiveDeterminant' argument can be used to force the U and V^T to\n"
             "have positive determinant (that is, to be proper rotation matrices); if\n"
             "forcePositiveDeterminant is False, then the singular values are guaranteed to\n"
             "be nonnegative but the U and V matrices might contain negative scale along one\n"
             "of the axes; if forcePositiveDeterminant is True, then U and V cannot contain\n"
             "negative scale but S[2] might be negative.  \n"
             "\n"
             "Our SVD implementation uses two-sided Jacobi rotations to iteratively\n"
             "diagonalize the matrix, which should be quite robust and significantly faster\n"
             "than the more general SVD solver in LAPACK.  \n",
             args("matrix", "forcePositiveDeterminant"))
        .def("symmetricEigensolve", &PyImath::jacobiEigensolve<IMATH_NAMESPACE::Matrix33<T> >,
             "Decomposes the matrix A using a symmetric eigensolver into matrices Q and S \n"
             "which have the following properties: \n"
             "  1. Q is the orthonormal matrix of eigenvectors, \n"
             "  2. S is the diagonal matrix of eigenvalues, \n"
             "  3. Q * S * Q.transposed() gives back the original matrix.\n"
             "\n"
             "IMPORTANT: It is vital that the passed-in matrix be symmetric, or the result \n"
             "won't make any sense.  This function will return an error if passed an \n"
             "unsymmetric matrix.\n"
             "\n"
             "The result is returned as a tuple [Q, S].  Note that since S is diagonal \n"
             "we don't need to return the entire matrix, so we return it as a three-vector. \n"
             "\n"
             "Our eigensolver implementation uses one-sided Jacobi rotations to iteratively \n"
             "diagonalize the matrix, which should be quite robust and significantly faster \n"
             "than the more general symmetric eigenvalue solver in LAPACK.  \n")
         .def("multDirMatrix", &multDirMatrix33<double,T>, "mult matrix")
         .def("multDirMatrix", &multDirMatrix33_return_value<double,T>, "mult matrix")
         .def("multDirMatrix", &multDirMatrix33_array<double,T>, "mult matrix")
         .def("multDirMatrix", &multDirMatrix33<float,T>, "mult matrix")
         .def("multDirMatrix", &multDirMatrix33_return_value<float,T>, "mult matrix")
         .def("multDirMatrix", &multDirMatrix33_array<float,T>, "mult matrix")
         .def("multVecMatrix", &multVecMatrix33<double,T>, "mult matrix")
         .def("multVecMatrix", &multVecMatrix33_return_value<double,T>, "mult matrix")
         .def("multVecMatrix", &multVecMatrix33_array<double,T>, "mult matrix")
         .def("multVecMatrix", &multVecMatrix33<float,T>, "mult matrix")
         .def("multVecMatrix", &multVecMatrix33_return_value<float,T>, "mult matrix")
         .def("multVecMatrix", &multVecMatrix33_array<float,T>, "mult matrix")
         .def("removeScaling", &removeScaling33<T>, removeScaling33_overloads("remove scaling"))

         .def("removeScalingAndShear", &removeScalingAndShear33<T>, removeScalingAndShear33_overloads("remove scaling"))
         .def("sansScaling", &sansScaling33<T>, sansScaling33_overloads("sans scaling"))
         .def("rotate", &rotate33<T>, return_internal_reference<>(),"rotate matrix")

         .def("sansScalingAndShear", &sansScalingAndShear33<T>, sansScalingAndShear33_overloads("sans scaling and shear"))
         .def("scale", &scaleSc33<T>, return_internal_reference<>(),"scale matrix")
         .def("scale", &scaleV33<T>, return_internal_reference<>(),"scale matrix")
         .def("scale", &scale33Tuple<T>, return_internal_reference<>(),"scale matrix")

         .def("setRotation", &setRotation33<T>, return_internal_reference<>(),"setRotation()")
         .def("setScale", &setScaleSc33<T>, return_internal_reference<>(),"setScale()")
         .def("setScale", &setScaleV33<T>, return_internal_reference<>(),"setScale()")
         .def("setScale", &setScale33Tuple<T>, return_internal_reference<>(),"setScale()")

         .def("setShear", &setShearSc33<T>, return_internal_reference<>(),"setShear()")
         .def("setShear", &setShearV33<T>, return_internal_reference<>(),"setShear()")
         .def("setShear", &setShear33Tuple<T>, return_internal_reference<>(),"setShear()")

         .def("setTranslation", &setTranslation33<T>, return_internal_reference<>(),"setTranslation()")
         .def("setTranslation", &setTranslation33Tuple<T>, return_internal_reference<>(),"setTranslation()")
         .def("setTranslation", &setTranslation33Obj<T>, return_internal_reference<>(),"setTranslation()")
         .def("setValue", &setValue33<T>, "setValue()")
         .def("shear", &shearSc33<T>, return_internal_reference<>(),"shear()")
         .def("shear", &shearV33<T>, return_internal_reference<>(),"shear()")
         .def("shear", &shear33Tuple<T>, return_internal_reference<>(),"shear()")
         .def("translate", &translate33<T>, return_internal_reference<>(),"translate()")
         .def("translate", &translate33Tuple<T>, return_internal_reference<>(),"translate()")
         .def("translation", &Matrix33<T>::translation, "translation()")
         ;

    decoratecopy(matrix33_class);

    return matrix33_class;
/*
    const Matrix33 &	operator = (const Matrix33 &v);
    const Matrix33 &	operator = (T a);
    T *			getValue ();
    const T *		getValue () const;
    template <class S> void getValue (Matrix33<S> &v) const;
    template <class S> Matrix33 & setValue (const Matrix33<S> &v);
    template <class S> Matrix33 & setTheMatrix (const Matrix33<S> &v);
    template <class S> void multVecMatrix(const Vec2<S> &src, Vec2<S> &dst) const;
    template <class S> void multDirMatrix(const Vec2<S> &src, Vec2<S> &dst) const;
    template <class S> const Matrix33 &	setRotation (S r);
    template <class S> const Matrix33 &	rotate (S r);
    const Matrix33 &	setScale (T s);
    template <class S> const Matrix33 &	setScale (const Vec2<S> &s);
    template <class S> const Matrix33 &	scale (const Vec2<S> &s);
    template <class S> const Matrix33 &	setTranslation (const Vec2<S> &t);
    Vec2<T>		translation () const;
    template <class S> const Matrix33 &	translate (const Vec2<S> &t);
    template <class S> const Matrix33 &	setShear (const S &h);
    template <class S> const Matrix33 &	setShear (const Vec2<S> &h);
    template <class S> const Matrix33 &	shear (const S &xy);
    template <class S> const Matrix33 &	shear (const Vec2<S> &h);
*/
}

template <class T>
static void
setM33ArrayItem(FixedArray<IMATH_NAMESPACE::Matrix33<T> > &ma,
                Py_ssize_t index,
                const IMATH_NAMESPACE::Matrix33<T> &m)
{
    ma[ma.canonical_index(index)] = m;
}

template <class T>
class_<FixedArray<IMATH_NAMESPACE::Matrix33<T> > >
register_M33Array()
{
    class_<FixedArray<IMATH_NAMESPACE::Matrix33<T> > > matrixArray_class = FixedArray<IMATH_NAMESPACE::Matrix33<T> >::register_("Fixed length array of IMATH_NAMESPACE::Matrix33");
    matrixArray_class
         .def("__setitem__", &setM33ArrayItem<T>)
        ;
    return matrixArray_class;
}

template PYIMATH_EXPORT class_<IMATH_NAMESPACE::Matrix33<float> > register_Matrix33<float>();
template PYIMATH_EXPORT class_<IMATH_NAMESPACE::Matrix33<double> > register_Matrix33<double>();

template PYIMATH_EXPORT class_<FixedArray<IMATH_NAMESPACE::Matrix33<float> > > register_M33Array<float>();
template PYIMATH_EXPORT class_<FixedArray<IMATH_NAMESPACE::Matrix33<double> > > register_M33Array<double>();

template<> PYIMATH_EXPORT IMATH_NAMESPACE::Matrix33<float> FixedArrayDefaultValue<IMATH_NAMESPACE::Matrix33<float> >::value() { return IMATH_NAMESPACE::Matrix33<float>(); }
template<> PYIMATH_EXPORT IMATH_NAMESPACE::Matrix33<double> FixedArrayDefaultValue<IMATH_NAMESPACE::Matrix33<double> >::value() { return IMATH_NAMESPACE::Matrix33<double>(); }
}


/*
 For more information, please see: http://software.sci.utah.edu

 The MIT License

 Copyright (c) 2015 Scientific Computing and Imaging Institute,
 University of Utah.

 License for the specific language governing rights and limitations under
 Permission is hereby granted, free of charge, to any person obtaining a
 copy of this software and associated documentation files (the "Software"),
 to deal in the Software without restriction, including without limitation
 the rights to use, copy, modify, merge, publish, distribute, sublicense,
 and/or sell copies of the Software, and to permit persons to whom the
 Software is furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included
 in all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 DEALINGS IN THE SOFTWARE.

 Author : Spencer Frisby, Moritz Dannhauer
 Date   : May 2014
*/

#include <iostream>

#include <Core/Datatypes/DenseMatrix.h>
#include <Core/Algorithms/BrainStimulator/SetConductivitiesToTetMeshAlgorithm.h>
#include <Core/GeometryPrimitives/Vector.h>
#include <Core/Datatypes/Legacy/Field/Field.h>
#include <Core/Datatypes/Legacy/Field/VField.h>
#include <Core/Datatypes/Legacy/Field/VMesh.h>
#include <Core/Algorithms/Base/AlgorithmPreconditions.h>
#include <Core/Datatypes/Legacy/Field/VField.h>
#include <Core/Datatypes/Legacy/Field/FieldInformation.h>
#include <Core/GeometryPrimitives/Vector.h>

#include <Core/Logging/Log.h>

#include <boost/assign.hpp>

using namespace boost::assign;
using namespace SCIRun;
using namespace SCIRun::Core::Algorithms;
using namespace SCIRun::Core::Algorithms::BrainStimulator;
using namespace SCIRun::Core::Geometry;
using namespace SCIRun::Core::Datatypes;
using namespace SCIRun::Core::Logging;

ALGORITHM_PARAMETER_DEF(BrainStimulator, Skin);
ALGORITHM_PARAMETER_DEF(BrainStimulator, SoftBone);
ALGORITHM_PARAMETER_DEF(BrainStimulator, HardBone);
ALGORITHM_PARAMETER_DEF(BrainStimulator, CSF);
ALGORITHM_PARAMETER_DEF(BrainStimulator, GM);
ALGORITHM_PARAMETER_DEF(BrainStimulator, WM);
ALGORITHM_PARAMETER_DEF(BrainStimulator, Electrode);
ALGORITHM_PARAMETER_DEF(BrainStimulator, InternalAir);

AlgorithmInputName  SetConductivitiesToMeshAlgorithm::InputField("InputField");
AlgorithmOutputName SetConductivitiesToMeshAlgorithm::OutputField("OutputField");

SetConductivitiesToMeshAlgorithm::SetConductivitiesToMeshAlgorithm()
{
  ElemLabelLookup += 1,2,3,4,5,6,7,8;

  using namespace Parameters;
  /// electrical conductivities (isotropic) default values based on the literature
  addParameter(Skin,         0.43);
  addParameter(SoftBone,     0.02856);
  addParameter(HardBone,     0.00640);
  addParameter(CSF,          1.79);
  addParameter(GM,           0.33);
  addParameter(WM,           0.142);
  addParameter(Electrode,    1.4);
  addParameter(InternalAir,  1e-6);
}

AlgorithmOutput SetConductivitiesToMeshAlgorithm::run(const AlgorithmInput& input) const
{
  auto mesh  = input.get<Field>(InputField);

  AlgorithmOutput output;

  FieldHandle output_field = run(mesh);

  output[OutputField] = output_field;

  return output;
}

FieldHandle SetConductivitiesToMeshAlgorithm::run(FieldHandle fh) const
{
  // making sure the field is not null
  if (!fh)
    THROW_ALGORITHM_INPUT_ERROR("Field supplied is empty ");

  /// making sure the data is on the elem and not the nodes
  FieldInformation fi(fh);
  if (!fi.is_constantdata())
    THROW_ALGORITHM_INPUT_ERROR("This function requires the data to be on the elements ");

  /// making sure the field contains data
  VField* vfield = fh->vfield();
  if (vfield->is_nodata())
    THROW_ALGORITHM_INPUT_ERROR("Field supplied contained no data ");

  /// making sure the field is not in vector or tensor format
  if (!vfield->is_scalar())
    THROW_ALGORITHM_INPUT_ERROR("Function only supports scalar labels. ");

  using namespace Parameters;

  LOG_DEBUG("SetConductivitiesToTetMeshAlgorithm parameters:\n\tSkin = {}\n\tSoftBone = {}\n\tHardBone = {}"
    "\n\tCSF = {}\n\tGM = {}\n\tWM = {}\n\tElectrode = {}\n\tInternalAir = {}",
    get(Skin).toDouble(), get(SoftBone).toDouble(), get(HardBone).toDouble(),
    get(CSF).toDouble(), get(GM).toDouble(), get(WM).toDouble(), get(Electrode).toDouble(),
    get(InternalAir).toDouble());

  /// array holding conductivities
  /// @todo: enable when VS2013 is supported
//  std::vector<double> conductivities = {get(Skin).toDouble(), get(SoftBone).toDouble(), get(HardBone).toDouble(),
//  get(CSF).toDouble(), get(GM).toDouble(), get(WM).toDouble(), get(Electrode).toDouble(), get(InternalAir).toDouble()};

  // stopgap measure until VS2013 is supported
  std::vector<double> conductivities;
  conductivities += get(Skin).toDouble(), get(SoftBone).toDouble(), get(HardBone).toDouble(),
  get(CSF).toDouble(), get(GM).toDouble(), get(WM).toDouble(), get(Electrode).toDouble(), get(InternalAir).toDouble();

  // check if defined conductivities and lookup table are consistent
  if (conductivities.size() != ElemLabelLookup.size())
    THROW_ALGORITHM_INPUT_ERROR("Defined conductivities and lookup table are inconsistent! ");

  /// replacing field value with conductivity value
  FieldHandle output = CreateField(fi, fh->mesh());
  VField* ofield = output->vfield();
  int val = 0;
  int cnt = 0;

  for (VMesh::Elem::index_type i = 0; i < vfield->vmesh()->num_elems(); i++) // loop over all tetrahedral elements
  {
    vfield->get_value(val, i);  //get the data value stored on the current element

    bool found = false; // boolean that indicates if element label was found in lookup

     // loop over lookup table and check if the current element has one of the desired labels, if not error
    for (size_t j = 0; j < ElemLabelLookup.size(); ++j)
    {
      if (val == ElemLabelLookup[j])
      {
        ofield->set_value(conductivities[j], i); // if so, set it to the isotropic conductivity value
        found = true;
        break;
      }
    }

    if (!found)
    {
      THROW_ALGORITHM_INPUT_ERROR("Tetrahedral element label could not be found in lookup table. ");
    }

    cnt++;
    if (cnt == 500)
    {
      cnt = 0;
      update_progress_max(i,vfield->vmesh()->num_elems());
    }
  }

  return output;
}