/*------------------------------------------------------------------------------
| Part of tweedledum.  This file is distributed under the MIT License.
| See accompanying file /LICENSE for details.
*-----------------------------------------------------------------------------*/
#pragma once

#include "../../ir/Circuit.h"
#include "../../ir/GateLib.h"
#include "../../ir/Wire.h"
#include "../../support/Matrix.h"

// This implementation is based on:
//
// Patel, Ketan N., Igor L. Markov, and John P. Hayes. "Optimal synthesis of
// linear reversible circuits."  Quantum Information & Computation 8.3 (2008):
// 282-294.
//
// Linear reversible classical circuits form an important subclass of quantum
// circuits, which can be generated by using only the CX (CNOT) gate.
//
// Other quantum circuit synthesis algorithms, e.g., Gray-Synth, require the
// synthesis of linear reversible circuits or can generate circuits with blocks
// of CX gates.  Therefore, synthesis methods that reduce the size of these
// circuits or sub-blocks would, in turn, reduce the size of the overall
// quantum circuit as well.
//
// What is the problem we are trying to solve here?
//
//    Synthesize an arbitrary linear reversible circuit on N wires using as
//    few CX gates as possible.
//
// This problem can be mapped to the problem of row reducing an N × N binary 
// matrix.  Patel et al. presented an algorithm that is asymptotically optimal
// up to a multiplicative constant. Their algorithm can be understood as a
// matrix decomposition algorithm that yields an asymptotically efficient
// elementary matrix decomposition of a binary matrix.
//
namespace tweedledum {
#pragma region Implementation details
namespace cnot_synth_detail {

using AbstractGate = std::pair<uint32_t, uint32_t>;
using GateList = std::vector<AbstractGate>;

template<typename T>
inline void pattern_elimination(
    Matrix<T>& matrix, uint32_t start, uint32_t end, GateList& gates)
{
	std::vector<uint32_t> table(matrix.num_rows(), 0);
	auto const begin = table.begin() + start;
	for (uint32_t row = start; row < matrix.num_rows(); ++row) {
		uint32_t pattern = 0u;
		uint32_t subrow_col = 0u;
		for (uint32_t col = start; col < end; ++col) {
			pattern |= (matrix(row, col) << subrow_col);
			subrow_col += 1;
		}
		if (pattern == 0) {
			continue;
		}
		auto const it = std::find(begin, table.end(), pattern);
		if (it != table.end()) {
			uint32_t pos = std::distance(table.begin(), it);
			matrix.row(row) ^= std::valarray(matrix.row(pos));
			gates.emplace_back(pos, row);
		} else {
			table[row] = pattern;
		}
	}
}

template<typename T>
inline void gaussian_elimination(
    Matrix<T>& matrix, uint32_t start, uint32_t end, GateList& gates)
{
	for (uint32_t col = start; col < end; ++col) {
		bool is_diagonal_one = (matrix(col, col) == 1);
		for (uint32_t row = col + 1; row < matrix.num_rows(); ++row) {
			if (matrix(row, col) == 0) {
				continue;
			}
			if (!is_diagonal_one) {
				is_diagonal_one = 1;
				matrix.row(col)
				    ^= std::valarray(matrix.row(row));
				gates.emplace_back(row, col);
			}
			matrix.row(row) ^= std::valarray(matrix.row(col));
			gates.emplace_back(col, row);
		}
	}
}

template<typename T>
inline GateList lower_cnot_synthesis(Matrix<T>& matrix, uint32_t section_size)
{
	GateList gates;
	uint32_t const num_cols = matrix.num_columns();
	uint32_t const num_sections = (num_cols - 1u) / section_size + 1u;
	for (uint32_t section = 0u; section < num_sections; ++section) {
		uint32_t start = section * section_size;
		uint32_t end = std::min(start + section_size, num_cols);
		pattern_elimination(matrix, start, end, gates);
		gaussian_elimination(matrix, start, end, gates);
	}
	return gates;
}

template<typename T>
void synthesize(
    Circuit& circuit, std::vector<WireRef> const& qubits, Matrix<T> matrix)
{
	// FIXME: for now the section_size is hardcoded to be '2'
	GateList lower = lower_cnot_synthesis(matrix, 2u);
	// TODO: Implement a transpose inpace
	auto transposed = transpose(matrix);
	GateList upper = lower_cnot_synthesis(transposed, 2u);

	for (auto const& [control, target] : upper) {
		// switch control/target of CX gates in gates_upper;
		circuit.create_instruction(
		    GateLib::X(), {qubits[target]}, qubits[control]);
	}
	std::reverse(lower.begin(), lower.end());
	for (auto const& [control, target] : lower) {
		circuit.create_instruction(
		    GateLib::X(), {qubits[control]}, qubits[target]);
	}
}

} // namespace cnot_synth_detail
#pragma endregion

/*! \brief Synthesis of linear reversible circuits (CNOT synthesis).
 *
 * This is the in-place variant of ``cnot_synth`` in which the circuit is
 * passed as a parameter and can potentially already contain some gates.  The
 * parameter ``qubits`` provides a qubit mapping to the existing qubits in the
 * circuit.
 * 
 * \param[inout] circuit A circuit in which the linear transformation will be 
 * synthesized on.
 * \param[in] qubits The wires that will be used.
 * \param[in] matrix An N x N binary matrix.
 */
template<typename T>
inline void cnot_synth(Circuit& circuit, std::vector<WireRef> const& qubits,
    Matrix<T> const& matrix)
{
	cnot_synth_detail::synthesize(circuit, qubits, matrix);
}

/*! \brief Synthesis of linear reversible circuits (CNOT synthesis).
 *
 * \param[in] matrix An N x N binary matrix.
 * \return A linear reversible circuit on N wires.
 */
template<typename T>
inline Circuit cnot_synth(Matrix<T> const& matrix)
{
	assert(matrix.num_rows() == matrix.num_columns());
	// TODO: method to generate a name;
	Circuit circuit("my_circuit");

	// Create the necessary qubits
	uint32_t const num_qubits = matrix.num_rows();
	std::vector<WireRef> wires;
	wires.reserve(num_qubits);
	for (uint32_t i = 0u; i < num_qubits; ++i) {
		wires.emplace_back(circuit.create_qubit());
	}
	cnot_synth(circuit, wires, matrix);
	return circuit;
}

} // namespace tweedledum
