#ifndef KRONECKERDUMPER_H
#define KRONECKERDUMPER_H
#include "../Version.h"
#include "Concurrency.h"
#include "ProgramGlobals.h"
#include "TypeToString.h"
#include "Vector.h"
#include <fstream>

namespace Dmrg
{

template <typename LeftRightSuperType, typename SolverParamsType>
class KroneckerDumper
{

	typedef PsimagLite::Concurrency ConcurrencyType;
	typedef typename LeftRightSuperType::BasisWithOperatorsType BasisWithOperatorsType;
	typedef typename BasisWithOperatorsType::BasisType BasisType;
	typedef typename BasisWithOperatorsType::SparseMatrixType SparseMatrixType;
	typedef typename SparseMatrixType::value_type ComplexOrRealType;
	typedef typename PsimagLite::Vector<ComplexOrRealType>::Type VectorType;
	typedef typename PsimagLite::Real<ComplexOrRealType>::Type RealType;
	typedef typename PsimagLite::Vector<bool>::Type VectorBoolType;
	typedef typename PsimagLite::Vector<SizeType>::Type VectorSizeType;
	typedef typename BasisType::QnType QnType;
	typedef std::pair<SizeType, SizeType> PairSizeType;

public:

	struct ParamsForKroneckerDumper {
		ParamsForKroneckerDumper(bool enable = false,
		    SizeType b = 0,
		    SizeType e = 0,
		    SizeType p = 6,
		    SizeType nOfQns_ = 0)
		    : enabled(enable)
		    , begin(b)
		    , end(e)
		    , precision(p)
		    , nOfQns(nOfQns_)
		{
		}

		bool enabled;
		SizeType begin;
		SizeType end;
		SizeType precision;
		SizeType nOfQns;
	}; // struct ParamsForKroneckerDumper

	KroneckerDumper(const SolverParamsType& params,
	    const LeftRightSuperType& lrs,
	    ProgramGlobals::DirectionEnum dir)
	    : enabled_(false)
	    , pairCount_(0)
	    , disable_(false)
	{
		counter_ = 0;
		if (dir == ProgramGlobals::DirectionEnum::INFINITE)
			return;

		enabled_ = params.options.isSet("KroneckerDumper");
		if (!enabled_)
			return;
		ParamsForKroneckerDumper p(enabled_,
		    params.dumperBegin,
		    params.dumperEnd,
		    params.precision);

		bool b = (p.end > 0 && counter_ >= p.end);
		if (counter_ < p.begin || b) {
			++counter_;
			enabled_ = false;
			return;
		}

		if (p.nOfQns == 0) {
			PsimagLite::String msg("KroneckerDumper::ctor(): internal error ");
			throw PsimagLite::RuntimeError(msg + "nOfQns\n");
		}

		ConcurrencyType::mutexInit(&mutex_);

		PsimagLite::String filename = "kroneckerDumper" + ttos(counter_) + ".txt";
		fout_.open(filename.c_str());
		fout_.precision(p.precision);
		fout_ << "KroneckerDumper for DMRG++ version " << DMRGPP_VERSION << "\n";
		fout_ << "Instance=" << counter_ << "\n";
		fout_ << "EncodingOfQuantumNumbers=" << (2 * ProgramGlobals::maxElectronsOneSpin) << "\n";

		printOneBasis("Left", lrs.left(), p.nOfQns);
		printOneBasis("Right", lrs.right(), p.nOfQns);

		fout_ << "SuperBasisPermutation\n";
		fout_ << lrs.super().permutationVector();
		// QnType qtarget = lrs.super().qnEx(m);
		// fout_<<qtarget<<"\n";

		signs_ = lrs.left().signs();
		++counter_;
	}

	~KroneckerDumper()
	{
		if (!enabled_)
			return;

		fout_ << "EOF\n";
		fout_.close();

		ConcurrencyType::mutexDestroy(&mutex_);
	}

	void push(const SparseMatrixType& A,
	    const SparseMatrixType& B,
	    ComplexOrRealType val,
	    ProgramGlobals::FermionOrBosonEnum bosonOrFermion,
	    const VectorType& y)
	{
		if (!enabled_)
			return;
		if (disable_)
			return;

		assert(y_.size() > 0);
		if (notFirstVector(y)) {
			disable_ = true;
			return;
		}

		ConcurrencyType::mutexLock(&mutex_);
		fout_ << "START_AB_PAIR\n";
		fout_ << "link.value=" << val << "\n";
		fout_ << "A" << pairCount_ << "\n";
		printMatrix(A);
		fout_ << "Ahat" << pairCount_ << "\n";
		SparseMatrixType Ahat;
		calculateAhat(Ahat, A, val, bosonOrFermion);
		printMatrix(Ahat);
		fout_ << "B" << pairCount_ << "\n";
		printMatrix(B);
		fout_ << "END_AB_PAIR\n";
		pairCount_++;
		ConcurrencyType::mutexUnlock(&mutex_);
	}

	void push(bool option,
	    const SparseMatrixType& hamiltonian,
	    const VectorType& y)
	{
		if (!enabled_)
			return;
		if (disable_)
			return;

		if (y_.size() == 0)
			y_ = y;
		if (notFirstVector(y)) {
			disable_ = true;
			return;
		}

		if (option)
			fout_ << "LeftHamiltonian\n";
		else
			fout_ << "RightHamiltonian\n";
		printMatrix(hamiltonian);
	}

private:

	void printMatrix(const SparseMatrixType& matrix)
	{
		fout_ << matrix.rows() << " " << matrix.cols() << "\n";
		for (SizeType i = 0; i < matrix.rows(); ++i) {
			for (int k = matrix.getRowPtr(i); k < matrix.getRowPtr(i + 1); ++k) {
				ComplexOrRealType value = matrix.getValue(k);
				if (PsimagLite::norm(value) == 0)
					continue;
				fout_ << i << " " << matrix.getCol(k) << " " << value << "\n";
			}
		}
	}

	void printOneBasis(PsimagLite::String name,
	    const BasisType& basis,
	    SizeType nOfQns)
	{
		fout_ << "" + name + "Basis\n";
		fout_ << "Sites\n";
		fout_ << basis.block();
		fout_ << "permutationVector\n";
		fout_ << basis.permutationVector();
		fout_ << "ElectronsUp_ElectronsDown\n";
		fout_ << basis.size() << "\n";
		SizeType npart = basis.partition() - 1;
		for (SizeType i = 0; i < npart; ++i)
			fout_ << basis.pseudoQnToString(i) << "\n";

		fout_ << "Signs\n";
		fout_ << basis.signs();
	}

	PairSizeType getNupNdown(QnType q) const
	{
		assert(q.other.size() >= 1);
		return PairSizeType(q.other[0], q.electrons - q.other[0]);
	}

	// Ahat(ia,ja) = (-1)^e_L(ia) A(ia,ja)*value
	void calculateAhat(SparseMatrixType& Ahat,
	    const SparseMatrixType& A,
	    ComplexOrRealType val,
	    ProgramGlobals::FermionOrBosonEnum bosonOrFermion) const
	{
		Ahat = A;
		SizeType rows = Ahat.rows();
		SizeType counter = 0;
		for (SizeType i = 0; i < rows; ++i) {
			RealType sign = (bosonOrFermion == ProgramGlobals::FermionOrBosonEnum::FERMION && signs_[i]) ? -1.0 : 1.0;
			for (int k = Ahat.getRowPtr(i); k < Ahat.getRowPtr(i + 1); ++k) {
				ComplexOrRealType tmp = Ahat.getValue(k) * sign * val;
				Ahat.setValues(counter++, tmp);
			}
		}
	}

	bool notFirstVector(const VectorType& y) const
	{
		VectorType ydiff;
		ydiff <= y - y_;
		return (PsimagLite::norm(ydiff) > 1e-8);
	}

	static SizeType counter_;
	bool enabled_;
	SizeType pairCount_;
	bool disable_;
	VectorType y_;
	std::ofstream fout_;
	VectorBoolType signs_;
	ConcurrencyType::MutexType mutex_;
}; // class KroneckerDumpter

template <typename T1, typename T2>
SizeType KroneckerDumper<T1, T2>::counter_ = 0;

} // namespace Dmrg
#endif // KRONECKERDUMPER_H
