#include <iomanip>
#include <boost/algorithm/string.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include "parsing_error.hpp"
#include "ligand.hpp"

using boost::filesystem::ifstream;
using boost::filesystem::ofstream;

ligand::ligand(boost::filesystem::ifstream& ifs) : num_active_torsions(0)
{
	// Initialize necessary variables for constructing a ligand.
	lines.reserve(200); // A ligand typically consists of <= 200 lines.
	frames.reserve(30); // A ligand typically consists of <= 30 frames.
	frames.push_back(frame(0, 0, 1, 0, 0, 0)); // ROOT is also treated as a frame. The parent and rotorX of ROOT frame are dummy.
	heavy_atoms.reserve(100); // A ligand typically consists of <= 100 heavy atoms.
	hydrogens.reserve(50); // A ligand typically consists of <= 50 hydrogens.

	// Initialize helper variables for parsing.
	vector<size_t> numbers; ///< Atom serial numbers.
	numbers.reserve(100); // A ligand typically consists of <= 100 heavy atoms.
	vector<vector<size_t>> bonds; ///< Covalent bonds.
	bonds.reserve(100); // A ligand typically consists of <= 100 heavy atoms.
	size_t current = 0; // Index of current frame, initialized to ROOT frame.
	frame* f = &frames.front(); // Pointer to the current frame.
	f->rotorYidx = 0; // Assume the rotorY of ROOT frame is the first atom.
	size_t num_lines = 0; // Used to track line number for reporting parsing errors, if any.
	string line;
	line.reserve(79); // According to PDBQT specification, the last item AutoDock atom type locates at 1-based [78, 79].

	// Parse ROOT, ATOM/HETATM, ENDROOT, BRANCH, ENDBRANCH, TORSDOF.
	while (getline(ifs, line))
	{
		++num_lines;
		if (starts_with(line, "ATOM") || starts_with(line, "HETATM"))
		{
			// Whenever an ATOM/HETATM line shows up, the current frame must be the last one.
			BOOST_ASSERT(current == frames.size() - 1);
			BOOST_ASSERT(f == &frames.back());

			// This line will be dumped to the output ligand file.
			lines.push_back(line);

			// Parse and validate AutoDock4 atom type.
			const string ad_type_string = line.substr(77, isspace(line[78]) ? 1 : 2);
			const size_t ad = parse_ad_type_string(ad_type_string);
			if (ad == AD_TYPE_SIZE) throw parsing_error(num_lines, "Atom type " + ad_type_string + " is not supported by idock.");

			// Parse the Cartesian coordinate.
			string name = line.substr(12, 4);
			boost::algorithm::trim(name);
			atom a(static_cast<string&&>(name), vec3(right_cast<fl>(line, 31, 38), right_cast<fl>(line, 39, 46), right_cast<fl>(line, 47, 54)), ad);

			if (a.is_hydrogen()) // Current atom is a hydrogen.
			{
				// For a polar hydrogen, the bonded hetero atom must be a hydrogen bond donor.
				if (ad == AD_TYPE_HD)
				{
					for (size_t i = heavy_atoms.size(); i > f->habegin;)
					{
						atom& b = heavy_atoms[--i];
						if (!b.is_hetero()) continue; // Only a hetero atom can be a hydrogen bond donor.
						if (a.is_neighbor(b))
						{
							b.donorize();
							break;
						}
					}
				}

				// Save the hydrogen.
				hydrogens.push_back(a);
			}
			else // Current atom is a heavy atom.
			{
				// Find bonds between the current atom and the other atoms of the same frame.
				BOOST_ASSERT(bonds.size() == heavy_atoms.size());
				bonds.push_back(vector<size_t>());
				bonds.back().reserve(4); // An atom typically consists of <= 4 bonds.
				for (size_t i = heavy_atoms.size(); i > f->habegin;)
				{
					atom& b = heavy_atoms[--i];
					if (a.is_neighbor(b))
					{
						bonds[heavy_atoms.size()].push_back(i);
						bonds[i].push_back(heavy_atoms.size());

						// If carbon atom b is bonded to hetero atom a, b is no longer a hydrophobic atom.
						if (a.is_hetero() && !b.is_hetero())
						{
							b.dehydrophobicize();
						}
						// If carbon atom a is bonded to hetero atom b, a is no longer a hydrophobic atom.
						else if (!a.is_hetero() && b.is_hetero())
						{
							a.dehydrophobicize();
						}
					}
				}

				// Set rotorYidx if the serial number of current atom is rotorYsrn.
				numbers.push_back(right_cast<size_t>(line, 7, 11));
				if (current && (numbers.back() == f->rotorYsrn)) // current > 0, i.e. BRANCH frame.
				{
					f->rotorYidx = heavy_atoms.size();
				}

				// Save the heavy atom.
				heavy_atoms.push_back(a);
			}
		}
		else if (starts_with(line, "BRANCH"))
		{
			// This line will be dumped to the output ligand file.
			lines.push_back(line);

			// Parse "BRANCH   X   Y". X and Y are right-justified and 4 characters wide.
			const size_t rotorXsrn = right_cast<size_t>(line,  7, 10);
			const size_t rotorYsrn = right_cast<size_t>(line, 11, 14);

			// Find the corresponding heavy atom with x as its atom serial number in the current frame.
			for (size_t i = f->habegin; true; ++i)
			{
				if (numbers[i] == rotorXsrn)
				{
					// Insert a new frame whose parent is the current frame.
					frames.push_back(frame(current, rotorXsrn, rotorYsrn, i, heavy_atoms.size(), hydrogens.size()));
					break;
				}
			}

			// Now the current frame is the newly inserted BRANCH frame.
			current = frames.size() - 1;

			// Update the pointer to the current frame.
			f = &frames[current];

			// The ending index of atoms of previous frame is the starting index of atoms of current frame.
			frames[current - 1].haend = f->habegin;
			frames[current - 1].hyend = f->hybegin;
		}
		else if (starts_with(line, "ENDBRANCH"))
		{
			// This line will be dumped to the output ligand file.
			lines.push_back(line);

			// A frame may be empty, e.g. "BRANCH   4   9" is immediately followed by "ENDBRANCH   4   9".
			// This emptiness is likely to be caused by invalid input structure, especially when all the atoms are located in the same plane.
			if (f->habegin == heavy_atoms.size()) throw parsing_error(num_lines, "An empty BRANCH has been detected, indicating the input ligand structure is probably invalid.");

			// If the current frame consists of rotor Y and a few hydrogens only, e.g. -OH and -NH2,
			// the torsion of this frame will have no effect on scoring and is thus redundant.
			if ((current == frames.size() - 1) && (f->habegin + 1 == heavy_atoms.size()))
			{
				f->active = false;
			}
			else
			{
				++num_active_torsions;
			}

			// Set up bonds between rotorX and rotorY.
			bonds[f->rotorYidx].push_back(f->rotorXidx);
			bonds[f->rotorXidx].push_back(f->rotorYidx);

			// Dehydrophobicize rotorX and rotorY if necessary.
			atom& rotorY = heavy_atoms[f->rotorYidx];
			atom& rotorX = heavy_atoms[f->rotorXidx];
			if ((rotorY.is_hetero()) && (!rotorX.is_hetero())) rotorX.dehydrophobicize();
			if ((rotorX.is_hetero()) && (!rotorY.is_hetero())) rotorY.dehydrophobicize();

			// Calculate parent_rotorY_to_current_rotorY and parent_rotorX_to_current_rotorY.
			const frame& p = frames[f->parent];
			f->parent_rotorY_to_current_rotorY =  rotorY.coordinate - heavy_atoms[p.rotorYidx].coordinate;
			f->parent_rotorX_to_current_rotorY = (rotorY.coordinate - rotorX.coordinate).normalize();

			// Now the parent of the following frame is the parent of current frame.
			current = f->parent;

			// Update the pointer to the current frame.
			f = &frames[current];
		}
		else if (starts_with(line, "ROOT") || starts_with(line, "ENDROOT") || starts_with(line, "TORSDOF"))
		{
			// This line will be dumped to the output ligand file.
			lines.push_back(line);
			if (starts_with(line, "TORSDOF")) break;
		}
	}
	BOOST_ASSERT(lines.size() <= num_lines); // Some lines like "REMARK", "WARNING", "TER" will not be dumped to the output ligand file.
	BOOST_ASSERT(current == 0); // current should remain its original value if "BRANCH" and "ENDBRANCH" properly match each other.
	BOOST_ASSERT(f == &frames.front()); // The frame pointer should remain its original value if "BRANCH" and "ENDBRANCH" properly match each other.

	// Determine num_heavy_atoms and num_hydrogens.
	num_heavy_atoms = heavy_atoms.size();
	num_hydrogens = hydrogens.size();
	frames.back().haend = num_heavy_atoms;
	frames.back().hyend = num_hydrogens;

	// Determine num_frames, num_torsions, flexibility_penalty_factor.
	num_frames = frames.size();
	BOOST_ASSERT(num_frames >= 1);
	num_torsions = num_frames - 1;
	BOOST_ASSERT(num_torsions + 1 == num_frames);
	BOOST_ASSERT(num_torsions >= num_active_torsions);
	BOOST_ASSERT(num_heavy_atoms + num_hydrogens + (num_torsions << 1) + 3 == lines.size()); // ATOM/HETATM lines + BRANCH/ENDBRANCH lines + ROOT/ENDROOT/TORSDOF lines == lines.size()
	flexibility_penalty_factor = 1 / (1 + 0.05846 * (num_active_torsions + 0.5 * (num_torsions - num_active_torsions)));
	BOOST_ASSERT(flexibility_penalty_factor <= 1);

	// Update heavy_atoms[].coordinate and hydrogens[].coordinate relative to frame origin.
	for (size_t k = 0; k < num_frames; ++k)
	{
		const frame& f = frames[k];
		const vec3 origin = heavy_atoms[f.rotorYidx].coordinate;
		for (size_t i = f.habegin; i < f.haend; ++i)
		{
			heavy_atoms[i].coordinate -= origin;
		}
		for (size_t i = f.hybegin; i < f.hyend; ++i)
		{
			hydrogens[i].coordinate -= origin;
		}
	}

	// Find intra-ligand interacting pairs that are not 1-4.
	interacting_pairs.reserve(num_heavy_atoms * num_heavy_atoms);
	vector<size_t> neighbors;
	neighbors.reserve(10); // An atom typically consists of <= 10 neighbors.
	for (size_t k1 = 0; k1 < num_frames; ++k1)
	{
		const frame& f1 = frames[k1];
		for (size_t i = f1.habegin; i < f1.haend; ++i)
		{
			// Find neighbor atoms within 3 consecutive covalent bonds.
			const vector<size_t>& i0_bonds = bonds[i];
			const size_t num_i0_bonds = i0_bonds.size();
			for (size_t i0 = 0; i0 < num_i0_bonds; ++i0)
			{
				const size_t b1 = i0_bonds[i0];
				if (find(neighbors.begin(), neighbors.end(), b1) == neighbors.end())
				{
					neighbors.push_back(b1);
				}
				const vector<size_t>& i1_bonds = bonds[b1];
				const size_t num_i1_bonds = i1_bonds.size();
				for (size_t i1 = 0; i1 < num_i1_bonds; ++i1)
				{
					const size_t b2 = i1_bonds[i1];
					if (find(neighbors.begin(), neighbors.end(), b2) == neighbors.end())
					{
						neighbors.push_back(b2);
					}
					const vector<size_t>& i2_bonds = bonds[b2];
					const size_t num_i2_bonds = i2_bonds.size();
					for (size_t i2 = 0; i2 < num_i2_bonds; ++i2)
					{
						const size_t b3 = i2_bonds[i2];
						if (find(neighbors.begin(), neighbors.end(), b3) == neighbors.end())
						{
							neighbors.push_back(b3);
						}
					}
				}
			}

			// Determine if interacting pairs can be possibly formed.
			for (size_t k2 = k1 + 1; k2 < num_frames; ++k2)
			{
				const frame& f2 = frames[k2];
				for (size_t j = f2.habegin; j < f2.haend; ++j)
				{
					if (((k1 == f2.parent) && ((j == f2.rotorYidx) || (i == f2.rotorXidx))) || (find(neighbors.begin(), neighbors.end(), j) != neighbors.end())) continue;
					const size_t type_pair_index = triangular_matrix_permissive_index(heavy_atoms[i].xs, heavy_atoms[j].xs);
					interacting_pairs.push_back(interacting_pair(i, j, type_pair_index));
				}
			}

			// Clear the current neighbor set for the next atom.
			neighbors.clear();
		}
	}
}

vector<size_t> ligand::get_atom_types() const
{
	vector<size_t> atom_types;
	atom_types.reserve(10); // A ligand typically consists of <= 10 XScore atom types.
	for (size_t i = 0; i < num_heavy_atoms; ++i)
	{
		const size_t t = heavy_atoms[i].xs;
		if (find(atom_types.begin(), atom_types.end(), t) == atom_types.end()) atom_types.push_back(t);
	}
	return atom_types;
}

bool ligand::evaluate(const conformation& conf, const scoring_function& sf, const box& b, const vector<array3d<fl>>& grid_maps, const fl e_upper_bound, fl& e, fl& f, change& g) const
{
	if (!b.within(conf.position))
		return false;

	// Initialize frame-wide conformational variables.
	vector<vec3> origins; ///< Origin coordinate, which is rotorY.
	vector<vec3> axes; ///< Vector pointing from rotor Y to rotor X.
	vector<qtn4> orientations_q; ///< Orientation in the form of quaternion.
	vector<mat3> orientations_m; ///< Orientation in the form of 3x3 matrix.
	vector<vec3> forces; ///< Aggregated derivatives of heavy atoms.
	vector<vec3> torques; /// Torque of the force.
	origins.resize(num_frames);
	axes.resize(num_frames);
	orientations_q.resize(num_frames);
	orientations_m.resize(num_frames);
	forces.resize(num_frames, zero3); // Initialize forces to zero3 for subsequent aggregation.
	torques.resize(num_frames, zero3); // Initialize torques to zero3 for subsequent aggregation.

	// Initialize atom-wide conformational variables.
	vector<vec3> coordinates; ///< Heavy atom coordinates.
	vector<vec3> derivatives; ///< Heavy atom derivatives.
	coordinates.resize(num_heavy_atoms);
	derivatives.resize(num_heavy_atoms);

	// Apply position and orientation to ROOT frame.
	const frame& root = frames.front();
	origins.front() = conf.position;
	orientations_q.front() = conf.orientation;
	orientations_m.front() = conf.orientation.to_mat3();
	for (size_t i = root.habegin; i < root.haend; ++i)
	{
		coordinates[i] = origins.front() + orientations_m.front() * heavy_atoms[i].coordinate;
		if (!b.within(coordinates[i]))
			return false;
	}

	// Apply torsions to BRANCH frames.
	for (size_t k = 1, t = 0; k < num_frames; ++k)
	{
		const frame& f = frames[k];

		// Update origin.
		origins[k] = origins[f.parent] + orientations_m[f.parent] * f.parent_rotorY_to_current_rotorY;
		if (!b.within(origins[k]))
			return false;

		// If the current BRANCH frame does not have an active torsion, skip it.
		if (!f.active)
		{
			BOOST_ASSERT(f.habegin + 1 == f.haend);
			BOOST_ASSERT(f.habegin == f.rotorYidx);
			coordinates[f.rotorYidx] = origins[k];
			continue;
		}

		// Update orientation.
		BOOST_ASSERT(f.parent_rotorX_to_current_rotorY.normalized());
		axes[k] = orientations_m[f.parent] * f.parent_rotorX_to_current_rotorY;
		BOOST_ASSERT(axes[k].normalized());
		orientations_q[k] = qtn4(axes[k], conf.torsions[t++]) * orientations_q[f.parent];
		BOOST_ASSERT(orientations_q[k].is_normalized());
		orientations_m[k] = orientations_q[k].to_mat3();

		// Update coordinates.
		for (size_t i = f.habegin; i < f.haend; ++i)
		{
			coordinates[i] = origins[k] + orientations_m[k] * heavy_atoms[i].coordinate;
			if (!b.within(coordinates[i]))
				return false;
		}
	}

	// Check steric clash between atoms of different frames except for (rotorX, rotorY) pair.
/*	for (size_t k1 = num_frames - 1; k1 > 0; --k1)
	{
		const frame& f1 = frames[k1];
		for (size_t i1 = f1.habegin; i1 < f1.haend; ++i1)
		{
			for (size_t k2 = 0; k2 < k1; ++k2)
			{
				const frame& f2 = frames[k2];
				for (size_t i2 = f2.habegin; i2 < f2.haend; ++i2)
				{
					if ((distance_sqr(coordinates[i1], coordinates[i2]) < sqr(heavy_atoms[i1].covalent_radius() + heavy_atoms[i2].covalent_radius())) && (!((k2 == f1.parent) && (i1 == f1.rotorYidx) && (i2 == f1.rotorXidx))))
						return false;
				}
			}
		}
	}*/

	e = 0;
	for (size_t i = 0; i < num_heavy_atoms; ++i)
	{
		// Retrieve the grid map in need.
		const array3d<fl>& grid_map = grid_maps[heavy_atoms[i].xs];
		BOOST_ASSERT(grid_map.initialized());

		// Find the index and fraction of the current coordinates.
		const array<size_t, 3> index = b.grid_index(coordinates[i]);

		// Assert the validity of index.
		BOOST_ASSERT(index[0] < b.num_grids[0]);
		BOOST_ASSERT(index[1] < b.num_grids[1]);
		BOOST_ASSERT(index[2] < b.num_grids[2]);

		// (x0, y0, z0) is the beginning corner of the partition.
		const size_t x0 = index[0];
		const size_t y0 = index[1];
		const size_t z0 = index[2];
		const fl e000 = grid_map(x0, y0, z0);

		// The derivative of probe atoms can be precalculated at the cost of massive memory storage.
		const fl e100 = grid_map(x0 + 1, y0,     z0    );
		const fl e010 = grid_map(x0,     y0 + 1, z0    );
		const fl e001 = grid_map(x0,     y0,     z0 + 1);
		derivatives[i][0] = (e100 - e000) * b.grid_granularity_inverse;
		derivatives[i][1] = (e010 - e000) * b.grid_granularity_inverse;
		derivatives[i][2] = (e001 - e000) * b.grid_granularity_inverse;

		e += e000; // Aggregate the energy.
	}

	// Save inter-molecular free energy into f.
	f = e;

	// Calculate intra-ligand free energy.
	const size_t num_interacting_pairs = interacting_pairs.size();
	for (size_t i = 0; i < num_interacting_pairs; ++i)
	{
		const interacting_pair& p = interacting_pairs[i];
		const vec3 r = coordinates[p.i2] - coordinates[p.i1];
		const fl r2 = r.norm_sqr();
		if (r2 < scoring_function::Cutoff_Sqr)
		{
			const scoring_function_element element = sf.evaluate(p.type_pair_index, r2);
			e += element.e;
			const vec3 derivative = element.dor * r;
			derivatives[p.i1] -= derivative;
			derivatives[p.i2] += derivative;
		}
	}

	// If the free energy is no better than the upper bound, refuse this conformation.
	if (e >= e_upper_bound) return false;

	// Calculate and aggregate the force and torque of BRANCH frames to their parent frame.
	for (size_t k = num_frames - 1, t = num_active_torsions; k > 0; --k)
	{
		const frame&  f = frames[k];

		for (size_t i = f.habegin; i < f.haend; ++i)
		{
			// The derivatives with respect to the position, orientation, and torsions
			// would be the negative total force acting on the ligand,
			// the negative total torque, and the negative torque projections, respectively,
			// where the projections refer to the torque applied to the branch moved by the torsion,
			// projected on its rotation axis.
			forces[k]  += derivatives[i];
			torques[k] += cross_product(coordinates[i] - origins[k], derivatives[i]);
		}

		// Aggregate the force and torque of current frame to its parent frame.
		forces[f.parent]  += forces[k];
		torques[f.parent] += torques[k] + cross_product(origins[k] - origins[f.parent], forces[k]);

		// If the current BRANCH frame does not have an active torsion, skip it.
		if (!f.active) continue;

		// Save the torsion.
		g[6 + (--t)] = torques[k] * axes[k]; // dot product
	}

	// Calculate and aggregate the force and torque of ROOT frame.
	for (size_t i = root.habegin; i < root.haend; ++i)
	{
		forces.front()  += derivatives[i];
		torques.front() += cross_product(coordinates[i] - origins.front(), derivatives[i]);
	}

	// Save the aggregated force and torque to g.
	g[0] = forces.front()[0];
	g[1] = forces.front()[1];
	g[2] = forces.front()[2];
	g[3] = torques.front()[0];
	g[4] = torques.front()[1];
	g[5] = torques.front()[2];

	return true;
}

result ligand::compose_result(const fl e, const fl f, const conformation& conf) const
{
	vector<vec3> origins(num_frames);
	vector<qtn4> orientations_q(num_frames);
	vector<mat3> orientations_m(num_frames);
	vector<vec3> heavy_atoms(num_heavy_atoms);
	vector<vec3> hydrogens(num_hydrogens);

	origins.front() = conf.position;
	orientations_q.front() = conf.orientation;
	orientations_m.front() = conf.orientation.to_mat3();

	// Calculate the coordinates of both heavy atoms and hydrogens of ROOT frame.
	const frame& root = frames.front();
	for (size_t i = root.habegin; i < root.haend; ++i)
	{
		heavy_atoms[i] = origins.front() + orientations_m.front() * this->heavy_atoms[i].coordinate;
	}
	for (size_t i = root.hybegin; i < root.hyend; ++i)
	{
		hydrogens[i]   = origins.front() + orientations_m.front() * this->hydrogens[i].coordinate;
	}

	// Calculate the coordinates of both heavy atoms and hydrogens of BRANCH frames.
	for (size_t k = 1, t = 0; k < num_frames; ++k)
	{
		const frame& f = frames[k];

		// Update origin.
		origins[k] = origins[f.parent] + orientations_m[f.parent] * f.parent_rotorY_to_current_rotorY;

		// Update orientation.
		orientations_q[k] = qtn4(orientations_m[f.parent] * f.parent_rotorX_to_current_rotorY, f.active ? conf.torsions[t++] : 0) * orientations_q[f.parent];
		orientations_m[k] = orientations_q[k].to_mat3();

		// Update coordinates.
		for (size_t i = f.habegin; i < f.haend; ++i)
		{
			heavy_atoms[i] = origins[k] + orientations_m[k] * this->heavy_atoms[i].coordinate;
		}
		for (size_t i = f.hybegin; i < f.hyend; ++i)
		{
			hydrogens[i]   = origins[k] + orientations_m[k] * this->hydrogens[i].coordinate;
		}
	}

	return result(conf, e, f, static_cast<vector<vec3>&&>(heavy_atoms), static_cast<vector<vec3>&&>(hydrogens));
}

void ligand::write_model(boost::iostreams::filtering_ostream& ligands_pdbqt_gz, const summary& s, const result& r, const box& b, const vector<array3d<fl>>& grid_maps)
{
	// Dump binding conformations to the output ligand file.
	using namespace std;
	ligands_pdbqt_gz
		<< "REMARK 921   NORMALIZED FREE ENERGY PREDICTED BY IDOCK:" << setw(8) << r.f * flexibility_penalty_factor << " KCAL/MOL\n"
		<< "REMARK 922        TOTAL FREE ENERGY PREDICTED BY IDOCK:" << setw(8) << r.e       << " KCAL/MOL\n"
		<< "REMARK 923 INTER-LIGAND FREE ENERGY PREDICTED BY IDOCK:" << setw(8) << r.f       << " KCAL/MOL\n"
		<< "REMARK 924 INTRA-LIGAND FREE ENERGY PREDICTED BY IDOCK:" << setw(8) << (r.e - r.f) << " KCAL/MOL\n"
		<< "REMARK 927      BINDING AFFINITY PREDICTED BY RF-SCORE:" << setw(8) << s.rfscore << " PKD\n"
	;
	const size_t num_lines = lines.size();
	size_t heavy_atom = 0, hydrogen = 0;
	for (size_t j = 0; j < num_lines; ++j)
	{
		const string& line = lines[j];
		if (line.size() >= 79) // This line starts with "ATOM" or "HETATM"
		{
			const bool is_hydrogen = line[77] == 'H' && (line[78] == ' ' || line[78] == 'D');
			const fl   atom_energy = is_hydrogen ? 0 : grid_maps[heavy_atoms[heavy_atom].xs](b.grid_index(r.heavy_atoms[heavy_atom]));
			const vec3& coordinate = is_hydrogen ? r.hydrogens[hydrogen++] : r.heavy_atoms[heavy_atom++];
			ligands_pdbqt_gz
				<< line.substr(0, 30)
				<< setw(8) << coordinate[0]
				<< setw(8) << coordinate[1]
				<< setw(8) << coordinate[2]
				<< line.substr(54, 16)
				<< setw(6) << atom_energy
				<< line.substr(76);
		}
		else // This line starts with "ROOT", "ENDROOT", "BRANCH", "ENDBRANCH", TORSDOF", which will not change during docking.
		{
			ligands_pdbqt_gz << line;
		}
		ligands_pdbqt_gz << '\n';
	}
	assert(heavy_atom == r.heavy_atoms.size());
	assert(hydrogen == r.hydrogens.size());
}


#include "algorithms/synthesis/syrec_synthesis.hpp"

#include "core/syrec/expression.hpp"
#include "core/syrec/program.hpp"
#include "core/syrec/variable.hpp"
#include "core/utils/timer.hpp"

#include <boost/dynamic_bitset.hpp>
#include <cmath>
#include <functional>
#include <memory>
#include <numeric>

namespace syrec {

    struct annotater {
        explicit annotater(circuit& circ, const std::stack<statement::ptr>& stmts):
            _circ(circ),
            _stmts(stmts) {}

        // Operator needs this signature to work
        void operator()(gate& g) const {
            if (!_stmts.empty()) {
                _circ.annotate(g, "lno", std::to_string(_stmts.top()->line_number));
            }
        }

    private:
        circuit&                          _circ;
        const std::stack<statement::ptr>& _stmts;
    };

    // Helper Functions for the synthesis methods
    standard_syrec_synthesizer::standard_syrec_synthesizer(circuit& circ, const program& prog [[maybe_unused]]):
        _circ(circ) {
        free_const_lines_map.insert(std::make_pair(false, std::vector<unsigned>()));
        free_const_lines_map.insert(std::make_pair(true, std::vector<unsigned>()));

        // root anlegen
        cct_man.current = add_vertex(cct_man.tree);
        cct_man.root    = cct_man.current;
        // Blatt anlegen
        cct_man.current                                             = add_vertex(cct_man.tree);
        get(boost::vertex_name, cct_man.tree)[cct_man.current].circ = std::make_shared<circuit>();
        get(boost::vertex_name, cct_man.tree)[cct_man.current].circ->gate_added.connect(annotater(*get(boost::vertex_name, cct_man.tree)[cct_man.current].circ, _stmts));
        add_edge(cct_man.root, cct_man.current, cct_man.tree);
    }

    void standard_syrec_synthesizer::set_main_module(const module::ptr& main_module) {
        assert(modules.empty());
        modules.push(main_module);
    }

    bool standard_syrec_synthesizer::on_module(const module::ptr& main) {
        for (const auto& stat: main->statements) {
            if (!full_statement(stat)) {
                if (!on_statement(stat)) {
                    return false;
                }
            }
        }
        return assemble_circuit(cct_man.root);
    }
    /// checking the entire statement
    bool standard_syrec_synthesizer::full_statement(const statement::ptr& statement) {
        bool okay = false;
        if (auto* stat = dynamic_cast<assign_statement*>(statement.get())) {
            okay = full_statement(*stat);
        } else {
            return false;
        }

        return okay;
    }

    bool standard_syrec_synthesizer::full_statement(const assign_statement& statement) {
        std::vector<unsigned> d, dd, stat_lhs, comp, ddd;
        std::vector<unsigned> lines;
        get_variables(statement.lhs, stat_lhs);

        op_rhs_lhs_expression(statement.rhs, d);

        if (op_vec.empty()) {
            return false;
        }
        flow(statement.rhs, ddd);

        /// Only when the rhs input signals are repeated (since the results are stored in the rhs)

        if (check_repeats()) {
            flow(statement.rhs, dd);

            if (exp_op_vector.size() == 1) {
                if (exp_op_vector.at(0) == 1 or exp_op_vector.at(0) == 2) {
                    /// cancel out the signals

                    exp_op_vector.clear();
                    assign_op_vector.clear();
                    exp_lhs_vector.clear();
                    exp_rhs_vector.clear();
                    op_vec.clear();
                } else {
                    if (statement.op == 1) {
                        expression_single_op(1, exp_lhs_vector.at(0), stat_lhs);
                        expression_single_op(1, exp_rhs_vector.at(0), stat_lhs);
                        exp_op_vector.clear();
                        assign_op_vector.clear();
                        exp_lhs_vector.clear();
                        exp_rhs_vector.clear();
                        op_vec.clear();
                    } else {
                        expression_single_op(statement.op, exp_lhs_vector.at(0), stat_lhs);
                        expression_single_op(exp_op_vector.at(0), exp_rhs_vector.at(0), stat_lhs);
                        exp_op_vector.clear();
                        assign_op_vector.clear();
                        exp_lhs_vector.clear();
                        exp_rhs_vector.clear();
                        op_vec.clear();
                    }
                }

            } else {
                if (exp_lhs_vector.at(0) == exp_rhs_vector.at(0)) {
                    if (exp_op_vector.at(0) == 1 or exp_op_vector.at(0) == 2) {
                        /// cancel out the signals
                    } else if (exp_op_vector.at(0) != 1 or exp_op_vector.at(0) != 2) {
                        expression_single_op(statement.op, exp_lhs_vector.at(0), stat_lhs);
                        expression_single_op(exp_op_vector.at(0), exp_rhs_vector.at(0), stat_lhs);
                    }
                } else {
                    solver(stat_lhs, statement.op, exp_lhs_vector.at(0), exp_op_vector.at(0), exp_rhs_vector.at(0));
                }

                unsigned              j = 0;
                unsigned              z;
                std::vector<unsigned> stat_assign_op;
                if ((exp_op_vector.size() % 2) == 0) {
                    z = ((exp_op_vector.size()) / (2));
                } else {
                    z = (((exp_op_vector.size()) - 1) / (2));
                }

                for (unsigned k = 0; k <= z - 1; k++) {
                    stat_assign_op.push_back(assign_op_vector.at(k));
                }

                /// Assignment operations

                std::reverse(stat_assign_op.begin(), stat_assign_op.end());

                /// If reversible assignment is "-", the assignment operations must negated appropriately

                if (statement.op == 1) {
                    for (unsigned int& i: stat_assign_op) {
                        if (i == 0) {
                            i = 1;
                        } else if (i == 1) {
                            i = 0;
                        } else {
                            continue;
                        }
                    }
                }

                for (unsigned i = 1; i <= exp_op_vector.size() - 1; i++) {
                    /// when both rhs and lhs exist
                    if ((exp_lhs_vector.at(i) != comp) && (exp_rhs_vector.at(i) != comp)) {
                        if (exp_lhs_vector.at(i) == exp_rhs_vector.at(i)) {
                            if (exp_op_vector.at(i) == 1 or exp_op_vector.at(i) == 2) {
                                /// cancel out the signals
                                j = j + 1;
                            } else if (exp_op_vector.at(i) != 1 or exp_op_vector.at(i) != 2) {
                                if (stat_assign_op.at(j) == 1) {
                                    expression_single_op(1, exp_lhs_vector.at(i), stat_lhs);
                                    expression_single_op(1, exp_rhs_vector.at(i), stat_lhs);
                                    j = j + 1;
                                } else {
                                    expression_single_op(stat_assign_op.at(j), exp_lhs_vector.at(i), stat_lhs);
                                    expression_single_op(exp_op_vector.at(i), exp_rhs_vector.at(i), stat_lhs);
                                    j = j + 1;
                                }
                            }
                        } else {
                            solver(stat_lhs, stat_assign_op.at(j), exp_lhs_vector.at(i), exp_op_vector.at(i), exp_rhs_vector.at(i));
                            j = j + 1;
                        }
                    }

                    /// when only rhs exists
                    else if ((exp_lhs_vector.at(i) == comp) && (exp_rhs_vector.at(i) != comp)) {
                        exp_evaluate(lines, stat_assign_op.at(j), exp_rhs_vector.at(i), stat_lhs);

                        j = j + 1;
                    }

                    /// when only lhs exists
                    else if ((exp_lhs_vector.at(i) != comp) && (exp_rhs_vector.at(i) == comp)) {
                        exp_evaluate(lines, stat_assign_op.at(j), exp_rhs_vector.at(i), stat_lhs);

                        j = j + 1;
                    } else if ((exp_lhs_vector.at(i) == comp) && (exp_rhs_vector.at(i) == comp)) {
                    }
                }
                exp_op_vector.clear();
                assign_op_vector.clear();
                exp_lhs_vector.clear();
                exp_rhs_vector.clear();
                op_vec.clear();
            }
        } else {
            exp_op_vector.clear();
            assign_op_vector.clear();
            exp_lhs_vector.clear();
            exp_rhs_vector.clear();
            op_vec.clear();
            return false;
        }

        exp_op_vector.clear();
        assign_op_vector.clear();
        exp_lhs_vector.clear();
        exp_rhs_vector.clear();
        op_vec.clear();
        return true;
    }

    bool standard_syrec_synthesizer::flow(const expression::ptr& expression, std::vector<unsigned>& v) {
        if (auto* binary = dynamic_cast<binary_expression*>(expression.get())) {
            return flow(*binary, v);
        } else if (auto* var = dynamic_cast<variable_expression*>(expression.get())) {
            return flow(*var, v);
        } else {
            return false;
        }
    }

    bool standard_syrec_synthesizer::flow(const variable_expression& expression, std::vector<unsigned>& v) {
        get_variables(expression.var, v);
        return true;
    }

    /// generating LHS and RHS (can be whole expressions as well)
    bool standard_syrec_synthesizer::flow(const binary_expression& expression, std::vector<unsigned>& v [[maybe_unused]]) {
        std::vector<unsigned> lhs, rhs, comp;
        assign_op_vector.push_back(expression.op);

        if (!flow(expression.lhs, lhs) || !flow(expression.rhs, rhs)) {
            return false;
        }

        exp_lhs_vector.push_back(lhs);
        exp_rhs_vector.push_back(rhs);
        exp_op_vector.push_back(expression.op);
        return true;
    }

    bool standard_syrec_synthesizer::solver(const std::vector<unsigned>& stat_lhs, unsigned stat_op, const std::vector<unsigned>& exp_lhs, unsigned exp_op, const std::vector<unsigned>& exp_rhs) {
        std::vector<unsigned> lines;
        if (stat_op == exp_op) {
            if (exp_op == 1) {
                expression_single_op(1, exp_lhs, stat_lhs);
                expression_single_op(0, exp_rhs, stat_lhs);
            } else {
                expression_single_op(stat_op, exp_lhs, stat_lhs);
                expression_single_op(stat_op, exp_rhs, stat_lhs);
            }
        } else {
            sub_flag = true;
            exp_evaluate(lines, exp_op, exp_lhs, exp_rhs);
            sub_flag = false;
            exp_evaluate(lines, stat_op, lines, stat_lhs);
            sub_flag = true;
            if (exp_op < 3) {
                expression_op_inverse(exp_op, exp_lhs, exp_rhs);
            }
        }
        sub_flag = false;
        return true;
    }

    /// If the input signals are repeated (i.e., rhs input signals are repeated)
    bool standard_syrec_synthesizer::check_repeats() {
        std::vector check_lhs_vec(exp_lhs_vector.cbegin(), exp_lhs_vector.cend());
        std::vector check_rhs_vec(exp_rhs_vector.cbegin(), exp_rhs_vector.cend());

        for (unsigned k = 0; k < check_lhs_vec.size(); k++) {
            if (check_lhs_vec.at(k).empty()) {
                check_lhs_vec.erase(check_lhs_vec.begin() + (k));
            }
        }

        for (unsigned k = 0; k < check_rhs_vec.size(); k++) {
            if (check_rhs_vec.at(k).empty()) {
                check_rhs_vec.erase(check_rhs_vec.begin() + (k));
            }
        }

        for (int i = 0; i < int(check_rhs_vec.size()); i++) {
            for (int j = 0; j < int(check_rhs_vec.size()); j++) {
                if (j != i) {
                    if (check_rhs_vec.at(i) == check_rhs_vec.at(j)) {
                        exp_op_vector.clear();
                        exp_lhs_vector.clear();
                        exp_rhs_vector.clear();
                        return true;
                    }
                }
            }
        }

        for (auto& i: check_lhs_vec) {
            for (auto& j: check_rhs_vec) {
                if (i == j) {
                    exp_op_vector.clear();
                    exp_lhs_vector.clear();
                    exp_rhs_vector.clear();
                    return true;
                }
            }
        }

        exp_op_vector.clear();
        exp_lhs_vector.clear();
        exp_rhs_vector.clear();
        return false;
    }

    /// generating LHS and RHS (not whole expressions, just the corresponding variables)
    bool standard_syrec_synthesizer::op_rhs_lhs_expression(const expression::ptr& expression, std::vector<unsigned>& v) {
        if (auto* binary = dynamic_cast<binary_expression*>(expression.get())) {
            return op_rhs_lhs_expression(*binary, v);
        } else if (auto* var = dynamic_cast<variable_expression*>(expression.get())) {
            return op_rhs_lhs_expression(*var, v);
        } else {
            return false;
        }
    }

    bool standard_syrec_synthesizer::op_rhs_lhs_expression(const variable_expression& expression, std::vector<unsigned>& v) {
        get_variables(expression.var, v);
        return true;
    }

    bool standard_syrec_synthesizer::op_rhs_lhs_expression(const binary_expression& expression, std::vector<unsigned>& v) {
        std::vector<unsigned> lhs, rhs;

        if (!op_rhs_lhs_expression(expression.lhs, lhs) || !op_rhs_lhs_expression(expression.rhs, rhs)) {
            return false;
        }

        v = rhs;
        op_vec.push_back(expression.op);
        return true;
    }

    /////////When the input signals are not repeated//////////////////
    bool standard_syrec_synthesizer::on_statement(const statement::ptr& statement) {
        _stmts.push(statement);
        bool okay = false;
        if (auto* swap_stat = dynamic_cast<swap_statement*>(statement.get())) {
            okay = on_statement(*swap_stat);
        } else if (auto* unary_stat = dynamic_cast<unary_statement*>(statement.get())) {
            okay = on_statement(*unary_stat);
        } else if (auto* assign_stat = dynamic_cast<assign_statement*>(statement.get())) {
            okay = on_statement(*assign_stat);
        } else if (auto* if_stat = dynamic_cast<if_statement*>(statement.get())) {
            okay = on_statement(*if_stat);
        } else if (auto* for_stat = dynamic_cast<for_statement*>(statement.get())) {
            okay = on_statement(*for_stat);
        } else if (auto* call_stat = dynamic_cast<call_statement*>(statement.get())) {
            okay = on_statement(*call_stat);
        } else if (auto* uncall_stat = dynamic_cast<uncall_statement*>(statement.get())) {
            okay = on_statement(*uncall_stat);
        } else if (auto* skip_stat = dynamic_cast<skip_statement*>(statement.get())) {
            okay = on_statement(*skip_stat);
        } else {
            return false;
        }

        _stmts.pop();
        return okay;
    }

    bool standard_syrec_synthesizer::on_statement(const swap_statement& statement) {
        std::vector<unsigned> lhs, rhs;

        get_variables(statement.lhs, lhs);
        get_variables(statement.rhs, rhs);

        assert(lhs.size() == rhs.size());

        swap(lhs, rhs);

        return true;
    }

    bool standard_syrec_synthesizer::on_statement(const unary_statement& statement) {
        // load variable
        std::vector<unsigned> var;
        get_variables(statement.var, var);

        switch (statement.op) {
            case unary_statement::invert:
                bitwise_negation(var);
                break;
            case unary_statement::increment:
                increment(var);
                break;
            case unary_statement::decrement:
                decrement(var);
                break;
            default:
                return false;
        }
        return true;
    }

    /// Function when the assignment statements does not include repeated input signals
    bool standard_syrec_synthesizer::on_statement(const assign_statement& statement) {
        std::vector<unsigned> lhs, rhs, d;

        get_variables(statement.lhs, lhs);

        op_rhs_lhs_expression(statement.rhs, d);
        on_expression(statement.rhs, rhs, lhs, statement.op);
        op_vec.clear();

        bool status = false;

        switch (statement.op) {
            case assign_statement::add: {
                if (!exp_opp.empty() && exp_opp.top() == statement.op) {
                    status = increase_new(lhs, exp_lhss.top());
                    status = increase_new(lhs, exp_rhss.top());
                    exp_opp.pop();
                    exp_lhss.pop();
                    exp_rhss.pop();
                } else {
                    status = increase_new(lhs, rhs);
                }
                while (!exp_opp.empty()) {
                    expression_op_inverse(exp_opp.top(), exp_lhss.top(), exp_rhss.top());
                    sub_flag = false;
                    exp_opp.pop();
                    exp_lhss.pop();
                    exp_rhss.pop();
                }
            } break;

            case assign_statement::subtract: {
                if (!exp_opp.empty() && exp_opp.top() == statement.op) {
                    status = decrease_new(lhs, exp_lhss.top());
                    status = increase_new(lhs, exp_rhss.top());
                    exp_opp.pop();
                    exp_lhss.pop();
                    exp_rhss.pop();
                } else {
                    status = decrease_new(lhs, rhs);
                }
                while (!exp_opp.empty()) {
                    expression_op_inverse(exp_opp.top(), exp_lhss.top(), exp_rhss.top());
                    sub_flag = false;
                    exp_opp.pop();
                    exp_lhss.pop();
                    exp_rhss.pop();
                }
            } break;

            case assign_statement::exor: {
                if (!exp_opp.empty() && exp_opp.top() == statement.op) {
                    status = bitwise_cnot(lhs, exp_lhss.top());
                    status = bitwise_cnot(lhs, exp_rhss.top());
                    exp_opp.pop();
                    exp_lhss.pop();
                    exp_rhss.pop();
                } else {
                    status = bitwise_cnot(lhs, rhs);
                }
                while (!exp_opp.empty()) {
                    expression_op_inverse(exp_opp.top(), exp_lhss.top(), exp_rhss.top());
                    sub_flag = false;
                    exp_opp.pop();
                    exp_lhss.pop();
                    exp_rhss.pop();
                }
            } break;

            default:
                return false;
        }

        return status;
    }

    bool standard_syrec_synthesizer::on_statement(const if_statement& statement) {
        // calculate expression
        std::vector<unsigned> expression_result, lhs_stat;
        unsigned              op = 0u;
        on_expression(statement.condition, expression_result, lhs_stat, op);
        assert(expression_result.size() == 1u);

        // add new helper line
        unsigned helper_line = expression_result.front();

        // activate this line
        add_active_control(helper_line);

        for (const auto& stat: statement.then_statements) {
            if (!full_statement(stat)) {
                if (!on_statement(stat)) {
                    return false;
                }
            }
        }

        // toggle helper line
        remove_active_control(helper_line);
        (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(helper_line);
        add_active_control(helper_line);

        for (const auto& stat: statement.else_statements) {
            if (!full_statement(stat)) {
                if (!on_statement(stat)) {
                    return false;
                }
            }
        }

        // de-active helper line
        remove_active_control(helper_line);
        (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(helper_line);

        return true;
    }

    bool standard_syrec_synthesizer::on_statement(const for_statement& statement) {
        const auto [nfrom, nto] = statement.range;

        const unsigned     from          = nfrom ? nfrom->evaluate(loop_map) : 1u; // default value is 1u
        const unsigned     to            = nto->evaluate(loop_map);
        const unsigned     step          = statement.step ? statement.step->evaluate(loop_map) : 1u; // default step is +1
        const std::string& loop_variable = statement.loop_variable;

        if (from <= to) {
            for (unsigned i = from; i <= to; i += step) {
                // adjust loop variable if necessary

                if (!loop_variable.empty()) {
                    loop_map[loop_variable] = i;
                }

                for (const auto& stat: statement.statements) {
                    if (!full_statement(stat)) {
                        if (!on_statement(stat)) {
                            return false;
                        }
                    }
                }
            }
        }

        else if (from > to) {
            for (int i = (int)from; i >= (int)to; i -= (int)step) {
                // adjust loop variable if necessary

                if (!loop_variable.empty()) {
                    loop_map[loop_variable] = i;
                }

                for (const auto& stat: statement.statements) {
                    if (!full_statement(stat)) {
                        if (!on_statement(stat)) {
                            return false;
                        }
                    }
                }
            }
        }
        // clear loop variable if necessary
        if (!loop_variable.empty()) {
            assert(loop_map.erase(loop_variable) == 1u);
        }

        return true;
    }

    bool standard_syrec_synthesizer::on_statement(const call_statement& statement) {
        // 1. Adjust the references module's parameters to the call arguments
        for (unsigned i = 0u; i < statement.parameters.size(); ++i) {
            const std::string& parameter        = statement.parameters.at(i);
            const auto&        module_parameter = statement.target->parameters.at(i);

            module_parameter->set_reference(modules.top()->find_parameter_or_variable(parameter));
        }

        // 2. Create new lines for the module's variables
        add_variables(_circ, statement.target->variables);

        modules.push(statement.target);
        for (const auto& stat: statement.target->statements) {
            if (!full_statement(stat)) {
                if (!on_statement(stat)) {
                    return false;
                }
            }
        }

        modules.pop();

        return true;
    }

    bool standard_syrec_synthesizer::on_statement(const uncall_statement& statement) {
        // 1. Adjust the references module's parameters to the call arguments
        for (unsigned i = 0u; i < statement.parameters.size(); ++i) {
            const std::string& parameter        = statement.parameters.at(i);
            const auto&        module_parameter = statement.target->parameters.at(i);

            module_parameter->set_reference(modules.top()->find_parameter_or_variable(parameter));
        }

        // 2. Create new lines for the module's variables
        add_variables(_circ, statement.target->variables);

        modules.push(statement.target);

        const auto statements = statement.target->statements;
        for (auto it = statements.rbegin(); it != statements.rend(); ++it) {
            const auto reverse_statement = (*it)->reverse();
            if (!full_statement(reverse_statement)) {
                if (!on_statement(reverse_statement)) {
                    return false;
                }
            }
        }

        modules.pop();

        return true;
    }

    bool standard_syrec_synthesizer::on_statement(const skip_statement& statement [[maybe_unused]]) {
        return true;
    }

    bool standard_syrec_synthesizer::on_expression(const expression::ptr& expression, std::vector<unsigned>& lines, std::vector<unsigned>& lhs_stat, unsigned op) {
        if (auto* numeric = dynamic_cast<numeric_expression*>(expression.get())) {
            return on_expression(*numeric, lines);
        } else if (auto* variable = dynamic_cast<variable_expression*>(expression.get())) {
            return on_expression(*variable, lines);
        } else if (auto* binary = dynamic_cast<binary_expression*>(expression.get())) {
            return on_expression(*binary, lines, lhs_stat, op);
        } else if (auto* shift = dynamic_cast<shift_expression*>(expression.get())) {
            return on_expression(*shift, lines, lhs_stat, op);
        } else {
            return false;
        }
    }

    bool standard_syrec_synthesizer::on_expression(const numeric_expression& expression, std::vector<unsigned>& lines) {
        get_constant_lines(expression.bitwidth(), expression.value->evaluate(loop_map), lines);
        return true;
    }

    bool standard_syrec_synthesizer::on_expression(const variable_expression& expression, std::vector<unsigned>& lines) {
        get_variables(expression.var, lines);
        return true;
    }

    /// Function when the assignment statements consist of binary expressions and does not include repeated input signals

    bool standard_syrec_synthesizer::on_expression(const binary_expression& expression, std::vector<unsigned>& lines, std::vector<unsigned>& lhs_stat, unsigned op) {
        std::vector<unsigned> lhs, rhs;

        if (!on_expression(expression.lhs, lhs, lhs_stat, op) || !on_expression(expression.rhs, rhs, lhs_stat, op)) {
            return false;
        }

        exp_lhss.push(lhs);
        exp_rhss.push(rhs);
        exp_opp.push(expression.op);

        if (exp_opp.size() == op_vec.size()) {
            if (exp_opp.top() == op) {
                return true;
            }
        }

        switch (expression.op) {
            case binary_expression::add: // +
                increase_new(rhs, lhs);
                lines = rhs;
                break;
            case binary_expression::subtract: // -
                decrease_new_assign(rhs, lhs);
                lines = rhs;
                break;
            case binary_expression::exor: // ^
                bitwise_cnot(rhs, lhs);   // duplicate lhs
                lines = rhs;
                break;
            case binary_expression::multiply: // *
                get_constant_lines(expression.bitwidth(), 0u, lines);
                multiplication(lines, lhs, rhs);
                break;
            case binary_expression::divide: // /
                get_constant_lines(expression.bitwidth(), 0u, lines);
                division(lines, lhs, rhs);
                break;
            case binary_expression::modulo: {
                get_constant_lines(expression.bitwidth(), 0u, lines);
                std::vector<unsigned> quot;
                get_constant_lines(expression.bitwidth(), 0u, quot);

                bitwise_cnot(lines, lhs); // duplicate lhs
                modulo(quot, lines, rhs);
            } break;
            case binary_expression::logical_and: // &&
                lines.emplace_back(get_constant_line(false));
                conjunction(lines.at(0), lhs.at(0), rhs.at(0));
                break;
            case binary_expression::logical_or: // ||
                lines.emplace_back(get_constant_line(false));
                disjunction(lines.at(0), lhs.at(0), rhs.at(0));
                break;
            case binary_expression::bitwise_and: // &
                get_constant_lines(expression.bitwidth(), 0u, lines);
                bitwise_and(lines, lhs, rhs);
                break;
            case binary_expression::bitwise_or: // |
                get_constant_lines(expression.bitwidth(), 0u, lines);
                bitwise_or(lines, lhs, rhs);
                break;
            case binary_expression::less_than: // <
                lines.emplace_back(get_constant_line(false));
                less_than(lines.at(0), lhs, rhs);
                break;
            case binary_expression::greater_than: // >
                lines.emplace_back(get_constant_line(false));
                greater_than(lines.at(0), lhs, rhs);
                break;
            case binary_expression::equals: // =
                lines.emplace_back(get_constant_line(false));
                equals(lines.at(0), lhs, rhs);
                break;
            case binary_expression::not_equals: // !=
                lines.emplace_back(get_constant_line(false));
                not_equals(lines.at(0), lhs, rhs);
                break;
            case binary_expression::less_equals: // <=
                lines.emplace_back(get_constant_line(false));
                less_equals(lines.at(0), lhs, rhs);
                break;
            case binary_expression::greater_equals: // >=
                lines.emplace_back(get_constant_line(false));
                greater_equals(lines.at(0), lhs, rhs);
                break;
            default:
                return false;
        }

        return true;
    }

    /// This function is used when input signals (rhs) are equal (just to solve statements individually)
    bool standard_syrec_synthesizer::exp_evaluate(std::vector<unsigned>& lines, unsigned op, const std::vector<unsigned>& lhs, const std::vector<unsigned>& rhs) {
        switch (op) {
            case binary_expression::add: // +
                increase_new(rhs, lhs);
                lines = rhs;
                break;
            case binary_expression::subtract: // -
                if (sub_flag) {
                    decrease_new_assign(rhs, lhs);
                    lines = rhs;
                } else {
                    decrease_new(rhs, lhs);
                    lines = rhs;
                }
                break;
            case binary_expression::exor: // ^
                bitwise_cnot(rhs, lhs);   // duplicate lhs
                lines = rhs;
                break;
            default:
                return false;
        }

        return true;
    }

    bool standard_syrec_synthesizer::on_expression(const shift_expression& expression, std::vector<unsigned>& lines, std::vector<unsigned>& lhs_stat, unsigned op) {
        std::vector<unsigned> lhs;
        if (!on_expression(expression.lhs, lhs, lhs_stat, op)) {
            return false;
        }

        unsigned rhs = expression.rhs->evaluate(loop_map);

        switch (expression.op) {
            case shift_expression::left: // <<
                get_constant_lines(expression.bitwidth(), 0u, lines);
                left_shift(lines, lhs, rhs);
                break;
            case shift_expression::right: // <<
                get_constant_lines(expression.bitwidth(), 0u, lines);
                right_shift(lines, lhs, rhs);
                break;
            default:
                return false;
        }

        return true;
    }

    //**********************************************************************
    //*****                      Unary Operations                      *****
    //**********************************************************************

    bool standard_syrec_synthesizer::bitwise_negation(const std::vector<unsigned>& dest) {
        for (unsigned idx: dest) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(idx);
        }
        return true;
    }

    bool standard_syrec_synthesizer::decrement(const std::vector<unsigned>& dest) {
        for (unsigned int i: dest) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(i);
            add_active_control(i);
        }

        for (unsigned int i: dest) {
            remove_active_control(i);
        }

        return true;
    }

    bool standard_syrec_synthesizer::increment(const std::vector<unsigned>& dest) {
        for (unsigned int i: dest) {
            add_active_control(i);
        }

        for (int i = int(dest.size()) - 1; i >= 0; --i) {
            remove_active_control(dest.at(i));
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(dest.at(i));
        }

        return true;
    }

    //**********************************************************************
    //*****                     Binary Operations                      *****
    //**********************************************************************

    bool standard_syrec_synthesizer::bitwise_and(const std::vector<unsigned>& dest, const std::vector<unsigned>& src1, const std::vector<unsigned>& src2) {
        bool ok = true;
        for (unsigned i = 0u; i < dest.size(); ++i) {
            ok &= conjunction(dest.at(i), src1.at(i), src2.at(i));
        }
        return ok;
    }

    bool standard_syrec_synthesizer::bitwise_cnot(const std::vector<unsigned>& dest, const std::vector<unsigned>& src) {
        for (unsigned i = 0u; i < src.size(); ++i) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(src.at(i), dest.at(i));
        }
        return true;
    }

    bool standard_syrec_synthesizer::bitwise_or(const std::vector<unsigned>& dest, const std::vector<unsigned>& src1, const std::vector<unsigned>& src2) {
        bool ok = true;
        for (unsigned i = 0u; i < dest.size(); ++i) {
            ok &= disjunction(dest.at(i), src1.at(i), src2.at(i));
        }
        return ok;
    }

    bool standard_syrec_synthesizer::conjunction(unsigned dest, unsigned src1, unsigned src2) {
        (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_toffoli(src1, src2, dest);

        return true;
    }

    bool standard_syrec_synthesizer::decrease_with_carry(const std::vector<unsigned>& dest, const std::vector<unsigned>& src, unsigned carry) {
        for (unsigned i = 0u; i < src.size(); ++i) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(dest.at(i));
        }

        increase_with_carry(dest, src, carry);

        for (unsigned i = 0u; i < src.size(); ++i) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(dest.at(i));
        }

        return true;
    }

    bool standard_syrec_synthesizer::disjunction(unsigned dest, unsigned src1, unsigned src2) {
        (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(src1, dest);
        (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(src2, dest);
        (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_toffoli(src1, src2, dest);

        return true;
    }

    bool standard_syrec_synthesizer::division(const std::vector<unsigned>& dest, const std::vector<unsigned>& src1, const std::vector<unsigned>& src2) {
        if (!modulo(dest, src1, src2)) return false;

        std::vector<unsigned> sum;
        std::vector<unsigned> partial;

        for (unsigned i = 1u; i < src1.size(); ++i) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(src2.at(i));
        }

        for (unsigned i = 1u; i < src1.size(); ++i) {
            add_active_control(src2.at(i));
        }

        for (int i = int(src1.size()) - 1; i >= 0; --i) {
            partial.push_back(src2.at(src1.size() - 1u - i));
            sum.insert(sum.begin(), src1.at(i));
            add_active_control(dest.at(i));
            increase_new(sum, partial);
            remove_active_control(dest.at(i));
            if (i > 0) {
                for (unsigned j = (src1.size() - i); j < src1.size(); ++j) {
                    remove_active_control(src2.at(j));
                }
                (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(src2.at(src1.size() - i));
                for (unsigned j = (src1.size() + 1u - i); j < src1.size(); ++j) {
                    add_active_control(src2.at(j));
                }
            }
        }

        return true;
    }

    bool standard_syrec_synthesizer::equals(unsigned dest, const std::vector<unsigned>& src1, const std::vector<unsigned>& src2) {
        for (unsigned i = 0u; i < src1.size(); ++i) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(src2.at(i), src1.at(i));
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(src1.at(i));
        }

        gate::line_container controls(src1.begin(), src1.end());
        (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_multi_control_toffoli(controls, dest);

        for (unsigned i = 0u; i < src1.size(); ++i) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(src2.at(i), src1.at(i));
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(src1.at(i));
        }

        return true;
    }

    bool standard_syrec_synthesizer::greater_equals(unsigned dest, const std::vector<unsigned>& src1, const std::vector<unsigned>& src2) {
        if (!greater_than(dest, src2, src1)) return false;
        (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(dest);

        return true;
    }

    bool standard_syrec_synthesizer::greater_than(unsigned dest, const std::vector<unsigned>& src1, const std::vector<unsigned>& src2) {
        return less_than(dest, src2, src1);
    }

    bool standard_syrec_synthesizer::increase_new(const std::vector<unsigned>& rhs, const std::vector<unsigned>& lhs) {
        unsigned bitwidth = rhs.size();

        if (bitwidth == 1) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(lhs.at(0), rhs.at(0));
        } else {
            for (unsigned i = 1; i <= bitwidth - 1; ++i) {
                (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(lhs.at(i), rhs.at(i));
            }
            for (unsigned i = bitwidth - 2; i >= 1; --i) {
                (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(lhs.at(i), lhs.at(i + 1));
            }
            for (unsigned i = 0; i <= bitwidth - 2; ++i) {
                (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_toffoli(rhs.at(i), lhs.at(i), lhs.at(i + 1));
            }

            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(lhs.at(bitwidth - 1), rhs.at(bitwidth - 1));

            for (unsigned i = bitwidth - 2; i >= 1; --i) {
                (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_toffoli(lhs.at(i), rhs.at(i), lhs.at(i + 1));
                (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(lhs.at(i), rhs.at(i));
            }
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_toffoli(lhs.at(0), rhs.at(0), lhs.at(1));
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(lhs.at(0), rhs.at(0));

            for (unsigned i = 1; i <= bitwidth - 2; ++i) {
                (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(lhs.at(i), lhs.at(i + 1));
            }
            for (unsigned i = 1; i <= bitwidth - 1; ++i) {
                (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(lhs.at(i), rhs.at(i));
            }
        }

        return true;
    }

    bool standard_syrec_synthesizer::decrease_new(const std::vector<unsigned>& rhs, const std::vector<unsigned>& lhs) {
        for (unsigned int rh: rhs) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(rh);
        }

        increase_new(rhs, lhs);

        for (unsigned int rh: rhs) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(rh);
        }
        return true;
    }

    bool standard_syrec_synthesizer::decrease_new_assign(const std::vector<unsigned>& rhs, const std::vector<unsigned>& lhs) {
        for (unsigned int lh: lhs) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(lh);
        }

        increase_new(rhs, lhs);

        for (unsigned int lh: lhs) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(lh);
        }

        for (unsigned i = 0u; i < lhs.size(); ++i) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(rhs.at(i));
        }
        return true;
    }

    bool standard_syrec_synthesizer::expression_op_inverse(unsigned op, const std::vector<unsigned>& exp_lhs, const std::vector<unsigned>& exp_rhs) {
        switch (op) {
            case binary_expression::add: // +
                decrease_new(exp_rhs, exp_lhs);
                break;
            case binary_expression::subtract: // -
                decrease_new_assign(exp_rhs, exp_lhs);
                break;
            case binary_expression::exor: // ^
                bitwise_cnot(exp_rhs, exp_lhs);
                break;
            default:
                return false;
        }
        return true;
    }

    bool standard_syrec_synthesizer::expression_single_op(unsigned op, const std::vector<unsigned>& exp_lhs, const std::vector<unsigned>& exp_rhs) {
        switch (op) {
            case binary_expression::add: // +
                increase_new(exp_rhs, exp_lhs);
                break;
            case binary_expression::subtract: // -
                if (sub_flag) {
                    decrease_new_assign(exp_rhs, exp_lhs);
                } else {
                    decrease_new(exp_rhs, exp_lhs);
                }
                break;
            case binary_expression::exor: // ^
                bitwise_cnot(exp_rhs, exp_lhs);
                break;
            default:
                return false;
        }
        return true;
    }

    bool standard_syrec_synthesizer::increase_with_carry(const std::vector<unsigned>& dest, const std::vector<unsigned>& src, unsigned carry) {
        unsigned bitwidth = src.size();

        if (bitwidth == 0) return true;

        for (unsigned i = 1u; i < bitwidth; ++i) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(src.at(i), dest.at(i));
        }

        if (bitwidth > 1) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(src.at(bitwidth - 1), carry);
        }
        for (int i = (int)bitwidth - 2; i > 0; --i) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(src.at(i), src.at(i + 1));
        }

        for (unsigned i = 0u; i < bitwidth - 1; ++i) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_toffoli(src.at(i), dest.at(i), src.at(i + 1));
        }
        (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_toffoli(src.at(bitwidth - 1), dest.at(bitwidth - 1), carry);

        for (int i = (int)bitwidth - 1; i > 0; --i) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(src.at(i), dest.at(i));
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_toffoli(dest.at(i - 1), src.at(i - 1), src.at(i));
        }

        for (unsigned i = 1u; i < bitwidth - 1u; ++i) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(src.at(i), src.at(i + 1));
        }

        for (unsigned i = 0u; i < bitwidth; ++i) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(src.at(i), dest.at(i));
        }

        return true;
    }

    bool standard_syrec_synthesizer::less_equals(unsigned dest, const std::vector<unsigned>& src1, const std::vector<unsigned>& src2) {
        if (!less_than(dest, src2, src1)) return false;
        (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(dest);

        return true;
    }

    bool standard_syrec_synthesizer::less_than(unsigned dest, const std::vector<unsigned>& src1, const std::vector<unsigned>& src2) {
        return (decrease_with_carry(src1, src2, dest) && increase_new(src1, src2));
    }

    bool standard_syrec_synthesizer::modulo(const std::vector<unsigned>& dest, const std::vector<unsigned>& src1, const std::vector<unsigned>& src2) {
        std::vector<unsigned> sum;
        std::vector<unsigned> partial;

        for (unsigned i = 1u; i < src1.size(); ++i) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(src2.at(i));
        }

        for (unsigned i = 1u; i < src1.size(); ++i) {
            add_active_control(src2.at(i));
        }

        for (int i = int(src1.size()) - 1; i >= 0; --i) {
            partial.push_back(src2.at(src1.size() - 1u - i));
            sum.insert(sum.begin(), src1.at(i));
            decrease_with_carry(sum, partial, dest.at(i));
            add_active_control(dest.at(i));
            increase_new(sum, partial);
            remove_active_control(dest.at(i));
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(dest.at(i));
            if (i > 0) {
                for (unsigned j = (src1.size() - i); j < src1.size(); ++j) {
                    remove_active_control(src2.at(j));
                }
                (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(src2.at(src1.size() - i));
                for (unsigned j = (src1.size() + 1u - i); j < src1.size(); ++j) {
                    add_active_control(src2.at(j));
                }
            }
        }
        return true;
    }

    bool standard_syrec_synthesizer::multiplication(const std::vector<unsigned>& dest, const std::vector<unsigned>& src1, const std::vector<unsigned>& src2) {
        if ((src1.empty()) || (dest.empty())) return true;

        std::vector<unsigned> sum     = dest;
        std::vector<unsigned> partial = src2;

        bool ok = true;

        add_active_control(src1.at(0));
        ok = ok && bitwise_cnot(sum, partial);
        remove_active_control(src1.at(0));

        for (unsigned i = 1; i < dest.size(); ++i) {
            sum.erase(sum.begin());
            partial.pop_back();
            add_active_control(src1.at(i));
            ok = ok && increase_new(sum, partial);
            remove_active_control(src1.at(i));
        }

        return ok;
    }

    bool standard_syrec_synthesizer::not_equals(unsigned dest, const std::vector<unsigned>& src1, const std::vector<unsigned>& src2) {
        if (!equals(dest, src1, src2)) return false;
        (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(dest);
        return true;
    }

    void standard_syrec_synthesizer::swap(const std::vector<unsigned>& dest1, const std::vector<unsigned>& dest2) {
        for (unsigned i = 0u; i < dest1.size(); ++i) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_fredkin(dest1.at(i), dest2.at(i));
        }
    }

    //**********************************************************************
    //*****                      Shift Operations                      *****
    //**********************************************************************

    void standard_syrec_synthesizer::left_shift(const std::vector<unsigned>& dest, const std::vector<unsigned>& src1, unsigned src2) {
        for (unsigned i = 0u; (i + src2) < dest.size(); ++i) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(src1.at(i), dest.at(i + src2));
        }
    }

    void standard_syrec_synthesizer::right_shift(const std::vector<unsigned>& dest, const std::vector<unsigned>& src1, unsigned src2) {
        for (unsigned i = src2; i < dest.size(); ++i) {
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_cnot(src1.at(i), dest.at(i - src2));
        }
    }

    //**********************************************************************
    //*****                     Efficient Controls                     *****
    //**********************************************************************

    void standard_syrec_synthesizer::add_active_control(unsigned control) {
        // aktuelles Blatt vollendet, zurueck zum parent
        cct_man.current = source(*(in_edges(cct_man.current, cct_man.tree).first), cct_man.tree);

        // child fuer neuen control anlegen
        cct_node child                                        = add_vertex(cct_man.tree);
        get(boost::vertex_name, cct_man.tree)[child].control  = control;
        get(boost::vertex_name, cct_man.tree)[child].controls = get(boost::vertex_name, cct_man.tree)[cct_man.current].controls;
        get(boost::vertex_name, cct_man.tree)[child].controls.insert(control);
        // get( boost::vertex_name, cct_man.tree )[child].circ = std::shared_ptr<circuit>( new circuit() );
        add_edge(cct_man.current, child, cct_man.tree);
        cct_man.current = child;

        // neues Blatt anlegen
        cct_node leaf                                        = add_vertex(cct_man.tree);
        get(boost::vertex_name, cct_man.tree)[leaf].controls = get(boost::vertex_name, cct_man.tree)[cct_man.current].controls;
        get(boost::vertex_name, cct_man.tree)[leaf].circ     = std::make_shared<circuit>();
        get(boost::vertex_name, cct_man.tree)[leaf].circ->gate_added.connect(annotater(*get(boost::vertex_name, cct_man.tree)[leaf].circ, _stmts));
        add_edge(cct_man.current, leaf, cct_man.tree);
        cct_man.current = leaf;
    }

    void standard_syrec_synthesizer::remove_active_control(unsigned control [[maybe_unused]]) {
        // aktuelles Blatt vollendet, zurueck zum parent
        cct_man.current = source(*(in_edges(cct_man.current, cct_man.tree).first), cct_man.tree);

        // aktueller Knoten abgeschlossen, zurueck zum parent
        cct_man.current = source(*(in_edges(cct_man.current, cct_man.tree).first), cct_man.tree);

        // neues Blatt anlegen
        cct_node leaf                                        = add_vertex(cct_man.tree);
        get(boost::vertex_name, cct_man.tree)[leaf].controls = get(boost::vertex_name, cct_man.tree)[cct_man.current].controls;
        get(boost::vertex_name, cct_man.tree)[leaf].circ     = std::make_shared<circuit>();
        get(boost::vertex_name, cct_man.tree)[leaf].circ->gate_added.connect(annotater(*get(boost::vertex_name, cct_man.tree)[leaf].circ, _stmts));
        add_edge(cct_man.current, leaf, cct_man.tree);
        cct_man.current = leaf;
    }

    bool standard_syrec_synthesizer::assemble_circuit(const cct_node& current) {
        // leaf
        if (out_edges(current, cct_man.tree).first == out_edges(current, cct_man.tree).second /*get( boost::vertex_name, cct_man.tree )[current].circ.get()->num_gates() > 0u*/) {
            _circ.insert_circuit(_circ.num_gates(), *(get(boost::vertex_name, cct_man.tree)[current].circ), get(boost::vertex_name, cct_man.tree)[current].controls);
            return true;
        }
        // assemble optimized circuits of successors
        for (auto edge_it = out_edges(current, cct_man.tree).first; edge_it != out_edges(current, cct_man.tree).second; ++edge_it) {
            if (!assemble_circuit(target(*edge_it, cct_man.tree))) {
                return false;
            }
        }
        return true;
    }

    void standard_syrec_synthesizer::get_variables(const variable_access::ptr& var, std::vector<unsigned>& lines) {
        unsigned offset = _var_lines[var->get_var()];

        if (!var->indexes.empty()) {
            // check if it is all numeric_expressions
            unsigned n = var->get_var()->dimensions.size(); // dimensions
            if ((unsigned)std::count_if(var->indexes.cbegin(), var->indexes.cend(), [&](const auto& p) { return dynamic_cast<numeric_expression*>(p.get()); }) == n) {
                for (unsigned i = 0u; i < n; ++i) {
                    offset += dynamic_cast<numeric_expression*>(var->indexes.at(i).get())->value->evaluate(loop_map) *
                              std::accumulate(var->get_var()->dimensions.begin() + i + 1u, var->get_var()->dimensions.end(), 1u, std::multiplies<>()) *
                              var->get_var()->bitwidth;
                }
            }
        }

        if (var->range) {
            auto [nfirst, nsecond] = *var->range;

            unsigned first  = nfirst->evaluate(loop_map);
            unsigned second = nsecond->evaluate(loop_map);

            if (first < second) {
                for (unsigned i = first; i <= second; ++i) {
                    lines.emplace_back(offset + i);
                }
            } else {
                for (int i = (int)first; i >= (int)second; --i) {
                    lines.emplace_back(offset + i);
                }
            }
        } else {
            for (unsigned i = 0u; i < var->get_var()->bitwidth; ++i) {
                lines.emplace_back(offset + i);
            }
        }
    }

    /**
   * Function to access array variables
   *
   * The array variable that corresponds to the given indexes is exchanged (via swap operations) with some given helper lines
   *
   * \param offset is the first line number associated to the array
   * \param dimensions is the dimensions of the array
   * \param indexes is the indexes of the array
   * \param bitwidth is the bitwidth of the variables within the array
   * \param lines is the destination, where
   */
    unsigned standard_syrec_synthesizer::get_constant_line(bool value) {
        unsigned const_line = 0u;

        if (!free_const_lines_map[value].empty()) {
            const_line = free_const_lines_map[value].back();
            free_const_lines_map[value].pop_back();
        } else if (!free_const_lines_map[!value].empty()) {
            const_line = free_const_lines_map[!value].back();
            free_const_lines_map[!value].pop_back();
            (*(get(boost::vertex_name, cct_man.tree)[cct_man.current].circ)).append_not(const_line);
        } else {
            const_line = _circ.add_line((std::string("const_") + std::to_string(value)), "garbage", value, true);
        }

        return const_line;
    }

    void standard_syrec_synthesizer::get_constant_lines(unsigned bitwidth, unsigned value, std::vector<unsigned>& lines) {
        boost::dynamic_bitset<> number(bitwidth, value);

        for (unsigned i = 0u; i < bitwidth; ++i) {
            lines.emplace_back(get_constant_line(number.test(i)));
        }
    }

    void standard_syrec_synthesizer::add_variable(circuit& circ, const std::vector<unsigned>& dimensions, const variable::ptr& var,
                                                  constant _constant, bool _garbage, const std::string& arraystr) {
        if (dimensions.empty()) {
            for (unsigned i = 0u; i < var->bitwidth; ++i) {
                std::string name = var->name + arraystr + "." + std::to_string(i);
                circ.add_line(name, name, _constant, _garbage);
            }
        } else {
            unsigned              len = dimensions.front();
            std::vector<unsigned> new_dimensions(dimensions.begin() + 1u, dimensions.end());

            for (unsigned i = 0u; i < len; ++i) {
                add_variable(circ, new_dimensions, var, _constant, _garbage, arraystr + "[" + std::to_string(i) + "]");
            }
        }
    }

    void standard_syrec_synthesizer::add_variables(circuit& circ, const variable::vec& variables) {
        for (const auto& var: variables) {
            // entry in var lines map
            _var_lines.insert(std::make_pair(var, circ.get_lines()));

            // types of constant and garbage
            constant _constant = (var->type == variable::out || var->type == variable::wire) ? constant(false) : constant();
            bool     _garbage  = (var->type == variable::in || var->type == variable::wire);

            add_variable(circ, var->dimensions, var, _constant, _garbage, std::string());
        }
    }

    bool syrec_synthesis(circuit& circ, const program& program, const properties::ptr& settings, const properties::ptr& statistics) {
        // Settings parsing
        auto variable_name_format  = get<std::string>(settings, "variable_name_format", "%1$s%3$s.%2$d");
        auto main_module           = get<std::string>(settings, "main_module", std::string());
        auto statement_synthesizer = standard_syrec_synthesizer(circ, program);

        // get the main module
        module::ptr main;

        if (!main_module.empty()) {
            main = program.find_module(main_module);
            if (!main) {
                std::cerr << "Program has no module: " << main_module << std::endl;
                return false;
            }
        } else {
            main = program.find_module("main");
            if (!main) {
                main = program.modules().front();
            }
        }

        // declare as top module
        statement_synthesizer.set_main_module(main);

        // create lines for global variables
        statement_synthesizer.add_variables(circ, main->parameters);
        statement_synthesizer.add_variables(circ, main->variables);

        // Run-time measuring
        timer<properties_timer> t;

        if (statistics) {
            properties_timer rt(statistics);
            t.start(rt);
        }
        // synthesize the statements
        bool synth_okay = statement_synthesizer.on_module(main);

        if (statistics) {
            t.stop();
        }

        return synth_okay;
    }

} // namespace syrec