// This file is part of GQCG-GQCP.
//
// Copyright (C) 2017-2020  the GQCG developers
//
// GQCG-GQCP is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GQCG-GQCP is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with GQCG-GQCP.  If not, see <http://www.gnu.org/licenses/>.

#include "QCMethod/CC/CCSDEnvironment.hpp"
#include "Utilities/complex.hpp"

#include <pybind11/complex.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>


namespace gqcpy {


// Provide some shortcuts for frequent namespaces.
namespace py = pybind11;
using namespace GQCP;


/**
 *  Add Python bindings for some APIs related to `CCSDEnvironment`.
 * 
 *  @tparam Class               The type of the Pybind11 `class_` (generated by the compiler).
 *  
 *  @param py_class             The Pybind11 `class_` that should obtain APIs related to `CCSDEnvironment`.
 */
template <typename Class>
void bindCCSDEnvironmentInterface(Class& py_class) {

    // The C++ type corresponding to the Python class.
    using Type = typename Class::type;

    using Scalar = typename Type::Scalar;


    py_class

        /*
         *  MARK: Named constructors
         */

        .def_static(
            "PerturbativeCCSD",
            [](const GSQHamiltonian<Scalar>& sq_hamiltonian, const OrbitalSpace& orbital_space) {
                return CCSDEnvironment<Scalar>::PerturbativeCCSD(sq_hamiltonian, orbital_space);
            },
            "Initialize a CCSD algorithmic environment with initial guesses for the T1- and T2-amplitudes based on perturbation theory.")

        .def_static(
            "PerturbativeCCD",
            [](const GSQHamiltonian<Scalar>& sq_hamiltonian, const OrbitalSpace& orbital_space) {
                return CCSDEnvironment<Scalar>::PerturbativeCCD(sq_hamiltonian, orbital_space);
            },
            "Initialize a CCD algorithmic environment with initial guesses for the T2-amplitudes based on perturbation theory.")

        // Bind read-write members/properties, exposing intermediary environment variables to the Python interface.
        .def_readwrite(
            "correlation_energies",
            &CCSDEnvironment<Scalar>::correlation_energies)


        // Define read-only 'getters'.
        .def_readonly(
            "t1_amplitudes",
            &CCSDEnvironment<Scalar>::t1_amplitudes)

        .def_readonly(
            "t2_amplitudes",
            &CCSDEnvironment<Scalar>::t2_amplitudes)


        // Bind methods for the replacement of the most current iterates.
        .def("replace_current_t1_amplitudes",
             [](CCSDEnvironment<Scalar>& environment, const T1Amplitudes<Scalar>& new_t1_amplitudes) {
                 environment.t1_amplitudes.pop_back();
                 environment.t1_amplitudes.push_back(new_t1_amplitudes);
             })

        .def("replace_current_t2_amplitudes",
             [](CCSDEnvironment<Scalar>& environment, const T2Amplitudes<Scalar>& new_t2_amplitudes) {
                 environment.t2_amplitudes.pop_back();
                 environment.t2_amplitudes.push_back(new_t2_amplitudes);
             });
}


/**
 *  Register multiple templated `QCMethod::CCD` to the gqcpy module and expose parts of their C++ interfaces to Python.
 * 
 *  @param module           The Pybind11 module in which the classes should be registered.
 */
void bindCCSDEnvironment(py::module& module) {

    // Real-valued Python bindings.
    py::class_<CCSDEnvironment<double>> py_CCSDEnvironment_d {module, "CCSDEnvironment_d", "An algorithmic environment suitable for real-valued coupled-cluster calculations up to the CCSD level."};
    bindCCSDEnvironmentInterface(py_CCSDEnvironment_d);

    // Complex-valued Python bindings.
    py::class_<CCSDEnvironment<complex>> py_CCSDEnvironment_cd {module, "CCSDEnvironment_cd", "An algorithmic environment suitable for complex-valued coupled-cluster calculations up to the CCSD level."};
    bindCCSDEnvironmentInterface(py_CCSDEnvironment_cd);
}


}  // namespace gqcpy
