/*
 * @BEGIN LICENSE
 *
 * Psi4: an open-source quantum chemistry software package
 *
 * Copyright (c) 2007-2019 The Psi4 Developers.
 *
 * The copyrights for code used from other parties are included in
 * the corresponding files.
 *
 * This file is part of Psi4.
 *
 * Psi4 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, version 3.
 *
 * Psi4 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 Psi4; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * @END LICENSE
 */

#ifndef _psi_src_lib_libmoinfo_moinfo_h_
#define _psi_src_lib_libmoinfo_moinfo_h_

#include <bitset>
#include <string>
#include <vector>
#include <utility>

#include "moinfo_base.h"

#define size_det 2048

namespace psi {

enum ReferenceType { AllRefs, UniqueRefs, ClosedShellRefs, UniqueOpenShellRefs };

class MOInfo : public MOInfoBase {
    typedef std::vector<std::string> strvec;
    typedef std::vector<std::pair<int, int> > intpairvec;

   public:
    /*********************************************************
      SlaterDeterminant Class
      1) Purpose
        This class is used to store all the information that
        belongs to a Slater Determinant
      2) Use
      3) Details
        The MOs that describe the reference are stored in the
        arrays aocc,bocc,avir and bvir. These refer to the MOs
        in Pitzer order with the frozen occupied and virtual
        already excluded. Therefore this code assumes that the
        integral transformation code has already eliminated
        the frozen integrals and relabeled them.

        type stores the type of reference
        type = 0 -> closed-shell determinant
        type = 2 -> open-shell   determinant

        number stores the ID of this reference

      4) Uses
        STL vector
    *********************************************************/
    class SlaterDeterminant {
        const MOInfo* moinfo;

       public:
        typedef std::bitset<size_det> bitdet;
        SlaterDeterminant(const MOInfo*);
        ~SlaterDeterminant();
        void set(int n) { bits.set(n); }
        bool test(int n) const { return (bits.test(n)); }
        bool is_closed_shell();
        bool is_spin_flipped(SlaterDeterminant& det);
        bitdet& get_bits() { return (bits); }
        void get_internal_excitations(SlaterDeterminant& det, double& sign,
                                      std::vector<std::pair<int, int> >& alpha_operators,
                                      std::vector<std::pair<int, int> >& beta_operators);
        char get_occupation_symbol(int i);
        std::string get_label();
        intvec get_aocc();
        intvec get_bocc();
        intvec get_avir();
        intvec get_bvir();
        boolvec get_is_aocc();
        boolvec get_is_bocc();
        boolvec get_is_avir();
        boolvec get_is_bvir();

       private:
        double annihilate(bitdet& bits_det, int so);
        double create(bitdet& bits_det, int so);
        bitdet bits;
        std::string type;
    };

   public:
    friend class SlaterDeterminant;
    MOInfo(Wavefunction& ref_wf_, Options& options_, bool silent_ = false);
    ~MOInfo();

    // DGEMM timing
    void set_dgemm_timing(double value) { dgemm_timing = value; }
    void add_dgemm_timing(double value) { dgemm_timing += value; }
    double get_dgemm_timing() const { return (dgemm_timing); }

    // Convergence Options
    double get_no_damp_convergence() const { return (no_damp_convergence); }

    intvec get_mo_sym() const { return (all_sym); }
    int get_mo_sym(int i) const { return (all_sym[i]); }
    int get_wfn_sym() const { return (wfn_sym); }
    int get_root() const { return (root); }

    int get_nmo() const { return (nmo); }
    int get_nactive_ael() const { return (nactive_ael); }
    int get_nactive_bel() const { return (nactive_bel); }

    int get_nall() const { return (nall); }
    int get_nfocc() const { return (nfocc); }
    int get_nextr() const { return (nextr); }
    int get_nfvir() const { return (nfvir); }
    int get_nocc() const { return (nocc); }
    int get_nvir() const { return (nvir); }

    intvec get_sopi() const { return (sopi); }
    intvec get_mopi() const { return (mopi); }
    intvec get_docc() const { return (docc); }
    intvec get_actv() const { return (actv); }
    intvec get_focc() const { return (focc); }
    intvec get_extr() const { return (extr); }
    intvec get_fvir() const { return (fvir); }
    intvec get_occ() const { return (occ); }
    intvec get_vir() const { return (vir); }
    intvec get_all() const { return (all); }

    int get_sopi(int i) const { return (sopi[i]); }
    int get_mopi(int i) const { return (mopi[i]); }
    int get_focc(int i) const { return (focc[i]); }
    int get_docc(int i) const { return (docc[i]); }
    int get_actv(int i) const { return (actv[i]); }
    int get_extr(int h) const { return (extr[h]); }
    int get_fvir(int i) const { return (fvir[i]); }

    // Mapping functions
    intvec get_focc_to_mo() const { return (focc_to_mo); }
    intvec get_docc_to_mo() const { return (docc_to_mo); }
    intvec get_actv_to_mo() const { return (actv_to_mo); }
    intvec get_extr_to_mo() const { return (extr_to_mo); }
    intvec get_fvir_to_mo() const { return (fvir_to_mo); }
    intvec get_occ_to_mo() const { return (occ_to_mo); }
    intvec get_vir_to_mo() const { return (vir_to_mo); }
    intvec get_all_to_mo() const { return (all_to_mo); }
    intvec get_mo_to_all() const { return (mo_to_all); }
    intvec get_actv_to_occ() const { return (actv_to_occ); }
    intvec get_actv_to_vir() const { return (actv_to_vir); }
    intvec get_occ_to_actv() const { return (occ_to_actv); }
    intvec get_vir_to_actv() const { return (vir_to_actv); }
    boolvec get_is_actv_in_occ() const { return (is_actv_in_occ); }
    boolvec get_is_actv_in_vir() const { return (is_actv_in_vir); }

    int get_all_to_occ(int i) const { return (all_to_occ[i]); }
    int get_all_to_vir(int i) const { return (all_to_vir[i]); }
    int get_all_to_mo(int i) const { return (all_to_mo[i]); }

    double get_scf_energy() const { return (scf_energy); }
    double get_fzcore_energy() const { return (fzcore_energy); }
    void set_fzcore_energy(double efzc) { fzcore_energy = efzc; }

    // Model space functions
    void setup_model_space();
    int get_nrefs() { return (all_refs.size()); };
    int get_nunique() { return (unique_refs.size()); };
    int get_ref_number(int n, ReferenceType ref_type = AllRefs);
    int get_ref_size(ReferenceType ref_type);
    std::string get_determinant_label(int i);

    strvec get_matrix_names(std::string str);
    intvec get_aocc(int i, ReferenceType ref_type);
    intvec get_bocc(int i, ReferenceType ref_type);
    intvec get_avir(int i, ReferenceType ref_type);
    intvec get_bvir(int i, ReferenceType ref_type);

    boolvec get_is_aocc(int i, ReferenceType ref_type);
    boolvec get_is_bocc(int i, ReferenceType ref_type);
    boolvec get_is_avir(int i, ReferenceType ref_type);
    boolvec get_is_bvir(int i, ReferenceType ref_type);

    intvec get_determinant(int i);  // Array with occupation of reference i (in all ordering)

    intpairvec get_alpha_internal_excitation(int i, int j);
    intpairvec get_beta_internal_excitation(int i, int j);
    double get_sign_internal_excitation(int i, int j);

   private:
    void tuning();
    void read_info();
    void read_mo_spaces();
    void read_mo_spaces2();
    void compute_mo_mappings();
    void print_info();
    void print_mo();
    void free_memory();

    // Model space functions
    void print_model_space();
    void build_model_space();
    void make_internal_excitations();

    /////////////////////////////////////////////////////////////////////////////////////////////////
    // MOInfo variables
    int root;

    double scf_energy;
    double fzcore_energy;

    /////////////////////////////////////////////////////////////////////////////////////////////////
    double dgemm_timing;
    // In-core/Out-of-core
    double no_damp_convergence;

    int nel;
    int reference;

    // Total number of orbitals in each space
    int nfocc;       // Frozen doubly-occupied MOs
    int nfvir;       // Frozen external MOs
    int nactv_docc;  // Number of active ???
    int nocc;        // Generalized occupied (docc + actv)
    int nvir;        // Generalized virtual (actv + extr)
    int nall;        // Non-frozen MOs (docc + actv + extr)
    int nextr;       // Non-frozen external orbitals (extr)

    // Orbitals arrays
    intvec focc;
    intvec fvir;
    intvec occ;
    intvec vir;
    intvec all;
    intvec extr;
    intvec actv_docc;
    intvec mopi;

    // Mapping arrays
    intvec all_to_mo;
    intvec mo_to_all;
    intvec orbs_to_mo;
    intvec focc_to_mo;
    intvec docc_to_mo;
    intvec actv_to_mo;
    intvec extr_to_mo;
    intvec fvir_to_mo;
    intvec occ_to_mo;
    intvec vir_to_mo;
    intvec mo_to_occ_act;
    intvec mo_to_act_vir;
    intvec occ_to_vir;
    intvec all_to_occ;
    intvec all_to_vir;
    intvec actv_to_occ;
    intvec actv_to_vir;
    intvec occ_to_actv;
    intvec vir_to_actv;
    intvec occ_to_all;
    intvec extr_to_all;
    boolvec is_actv_in_occ;
    boolvec is_actv_in_vir;

    // Symmetry
    intvec all_sym;

    // Model space
    std::vector<SlaterDeterminant> references;
    std::vector<std::vector<std::vector<std::pair<int, int> > > > alpha_internal_excitations;
    std::vector<std::vector<std::vector<std::pair<int, int> > > > beta_internal_excitations;
    std::vector<std::vector<double> > sign_internal_excitations;
    std::vector<int> all_refs;
    std::vector<int> unique_refs;
    std::vector<int> closed_shell_refs;
    std::vector<int> unique_open_shell_refs;
};

}  // namespace psi

#endif  // _psi_src_lib_libmoinfo_moinfo_h_
