#ifndef ORACLECHEBYSHEV_H
#define ORACLECHEBYSHEV_H
#include "ScaledHamiltonian.h"

namespace Dmrg {

template<typename TargetingCommonType, typename TargetParamsType>
class OracleChebyshev {

public:

	typedef typename TargetingCommonType::ModelType ModelType;
	typedef typename ModelType::ModelHelperType ModelHelperType;
	typedef typename ModelHelperType::LeftRightSuperType LeftRightSuperType;
	typedef typename LeftRightSuperType::BasisWithOperatorsType BasisWithOperatorsType;
	typedef typename BasisWithOperatorsType::OperatorType OperatorType;
	typedef typename TargetParamsType::RealType RealType;
	typedef typename TargetingCommonType::LanczosSolverType LanczosSolverType;
	typedef typename TargetingCommonType::ComplexOrRealType ComplexOrRealType;
	typedef typename TargetingCommonType::VectorWithOffsetType VectorWithOffsetType;
	typedef typename LanczosSolverType::MatrixType MatrixLanczosType;
	typedef ScaledHamiltonian<MatrixLanczosType, TargetParamsType> ScaledHamiltonianType;
	typedef typename PsimagLite::Vector<ComplexOrRealType>::Type VectorType;
	typedef typename TargetingCommonType::FermionSignType FermionSignType;

	OracleChebyshev(const ModelType& model,
	                const LeftRightSuperType& lrs,
	                const RealType& currentTime,
	                const TargetParamsType& tstStruct,
	                RealType E0)
	    : model_(model), lrs_(lrs), currentTime_(currentTime), tstStruct_(tstStruct), E0_(E0)
	{}

	void operator()(SizeType n,
	                const TargetingCommonType& common,
	                SizeType systemOrEnviron,
	                SizeType site,
	                OperatorType& A,
	                typename TargetingCommonType::BorderEnumType border)
	{
		VectorWithOffsetType p0;
		typename TargetingCommonType::ApplyOperatorType applyOpLocal(lrs_,
		                                                             common.withLegacyBugs());
		typename PsimagLite::Vector<bool>::Type signs;
		model_.findOddElectronsOfOneSite(signs, site);
		FermionSignType fs(lrs_.left(), signs);
		OperatorType Aprime = A;
		Aprime.dagger();
		applyOpLocal(p0,common.psi(),Aprime,fs,systemOrEnviron,border);

		VectorWithOffsetType p1;
		VectorType r;
		chebyIteration(r, p0, p0, true);
		p1.fromFull(r, lrs_.super());
		for (SizeType i = 0; i < n; ++i) {
			chebyIteration(r, p1, p0, false); // p2 = 2*H*p1 - p0;
			VectorWithOffsetType p2;
			p2.fromFull(r, lrs_.super());
			// <gs|c|p2>;
			ComplexOrRealType result = common.testRealWork(p2,
			                                               common.psi(),
			                                               systemOrEnviron,
			                                               site,
			                                               A,
			                                               border);
			std::cout<<"OracleChebyshev: <gs|H|p"<<(i+2)<<">= "<<result<<"\n";
			// prepare for next iteration
			p0 = p1;
			p1 = p2;
		}
	}

private:

	void chebyIteration(VectorType& r,
	                    const VectorWithOffsetType& p1,
	                    const VectorWithOffsetType& p0,
	                    bool firstOne) const
	{
		SizeType i0 = 0;
		for (SizeType ii = 0; ii < p1.sectors(); ++ii)
			i0 = p1.sector(ii);

		SizeType p = lrs_.super().findPartitionNumber(p1.offset(i0));
		typename ModelType::HamiltonianConnectionType hc(p,
		                                                 lrs_,
		                                                 model_.geometry(),
		                                                 ModelType::modelLinks(),
		                                                 currentTime_,
		                                                 0);
		MatrixLanczosType lanczosHelper(model_,
		                                hc);

		ScaledHamiltonianType lanczosHelper2(lanczosHelper, tstStruct_, E0_);

		SizeType total = p1.effectiveSize(i0);
		r.resize(total);
		VectorType x2(total);
		const RealType factor = (firstOne) ? 1.0 : 2.0;
		VectorWithOffsetType x = factor*p1;
		x.extract(x2, i0);
		lanczosHelper2.matrixVectorProduct(r, x2); // applying Hprime
		if (firstOne) return;

		VectorType phi2(total);
		p0.extract(phi2, i0);
 		r += (-1.0)*phi2;
	}

	const ModelType& model_;
	const LeftRightSuperType& lrs_;
	const RealType& currentTime_;
	const TargetParamsType& tstStruct_;
	const RealType& E0_;
};
}
#endif // ORACLECHEBYSHEV_H
