/*
 *  Copyright (C) 2004-2019 Edward F. Valeev
 *
 *  This file is part of Libint.
 *
 *  Libint is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Libint 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Libint.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <string>
#include <fstream>
#include <iface.h>
#include <default_params.h>

using namespace std;
using namespace libint2;

namespace {
  const char mh_name[] = "libint2.h";
  const char th_name[] = "libint2_types.h";
  const char ph_name[] = "libint2_params.h";
  const char ih_name[] = "libint2_iface.h";
  const char ii_name[] = "libint2_iface_internal.h";
  const char si_name[] = "libint2_static_init.cc";
  const char sc_name[] = "libint2_static_cleanup.cc";
  const char li_name[] = "libint2_init.cc";
  
  inline void header_guard_open(std::ostream& os, const std::string& label) {
    os << "#ifndef _libint2_" << label << "_h_" << endl
       << "#define _libint2_" << label << "_h_" << endl << endl;
  }
  inline void header_guard_close(std::ostream& os) {
    os << "#endif" << endl << endl;
  }

};

Libint2Iface::Libint2Iface(const SafePtr<CompilationParameters>& cparams,
                           const SafePtr<CodeContext>& ctext) :
  null_str_(""), oss_(), cparams_(cparams), ctext_(ctext),
  th_((cparams_->source_directory() + th_name).c_str()),
  ph_((cparams_->source_directory() + ph_name).c_str()),
  ih_((cparams_->source_directory() + ih_name).c_str()),
  ii_((cparams_->source_directory() + ii_name).c_str()),
  si_((cparams_->source_directory() + si_name).c_str()),
  sc_((cparams_->source_directory() + sc_name).c_str()),
  li_((cparams_->source_directory() + li_name).c_str())
{
  th_ << ctext_->copyright();
  ph_ << ctext_->copyright();
  ih_ << ctext_->copyright();
  ii_ << ctext_->copyright();
  si_ << ctext_->copyright();
  sc_ << ctext_->copyright();
  li_ << ctext_->copyright();

  header_guard_open(th_,"libint2types");
  header_guard_open(ph_,"libint2params");
  header_guard_open(ih_,"libint2iface");
  header_guard_open(ii_,"libint2ifaceint");

  ph_ << macro_define("API_PREFIX", cparams_->api_prefix());
  ph_ << macro_define("MAX_VECLEN", cparams_->max_vector_length());
  ph_ << macro_define("ALIGN_SIZE", cparams_->align_size());
  if (cparams_->count_flops())
    ph_ << macro_define("FLOP_COUNT",1);
  if (cparams_->profile())
    ph_ << macro_define("PROFILE",1);
  if (cparams_->accumulate_targets())
    ph_ << macro_define("ACCUM_INTS",1);
  const std::string realtype(cparams_->realtype());
  {
    // does LIBINT_USER_DEFINED_REAL need extra include statements?
#ifdef LIBINT_USER_DEFINED_REAL_INCLUDES
    ph_ << LIBINT_USER_DEFINED_REAL_INCLUDES << std::endl;
#endif
    ph_ << macro_define("REALTYPE", realtype);
  }
  if (cparams_->contracted_targets())
    ph_ << macro_define("CONTRACTED_INTS",1);
  
  ih_ << "#ifdef __cplusplus\n# include <cstddef>\n#else\n# include <stddef.h>\n#endif" << endl
      << ctext_->code_prefix();

  oss_.str(null_str_);
  oss_ << ctext_->std_header() << "#include <" << ih_name << ">" << endl
                               << "#include <" << ii_name << ">" << endl
                               << "#include <cstddef>" << endl
                               << "#include <cassert>" << endl
                               << "#include <cstdlib>" << endl
                               << ctext_->code_prefix();
  std::string pfix = oss_.str();
  si_ << pfix;
  sc_ << pfix;
  li_ << "#include <libint2/util/memory.h>" << endl << pfix; // moved from libint2.h to here

  // print out declarations for the array of pointers to evaluator functions
  LibraryTaskManager& taskmgr = LibraryTaskManager::Instance();
  typedef LibraryTaskManager::TasksCIter tciter;
  for(tciter t=taskmgr.first(); t!=taskmgr.plast(); ++t) {
    const std::string& tlabel = t->label();
    const unsigned int nbf = cparams_->num_bf(tlabel);

    ostringstream oss;
    oss << "void (*" << ctext->label_to_name(cparams->api_prefix()) << "libint2_build_" << tlabel;
    for(unsigned int c=0; c<nbf; ++c) {
      const unsigned int lmax = const_cast<const CompilationParameters*>(cparams_.get())->max_am(tlabel, c);
      oss << "[" << lmax+1 << "]";
      std::cout << "task=" << tlabel << " center=" << c << " lmax=" << lmax << std::endl;
    }
    oss << ")(" << ctext_->const_modifier() << ctext_->inteval_type_name(tlabel) << "*);" << endl;
    ih_ << "extern " << oss.str();
    si_ << oss.str();
  }
  
  // Declare library constructor/destructor
  oss_.str(null_str_);
  oss_ << ctext_->type_name<void>() << " "
       << ctext_->label_to_name(cparams->api_prefix() + "libint2_static_init") << "()";
  std::string si_fdec(oss_.str());
  
  oss_.str(null_str_);
  oss_ << ctext_->type_name<void>() << " "
       << ctext_->label_to_name(cparams->api_prefix() + "libint2_static_cleanup") << "()";
  std::string sc_fdec(oss_.str());

  ih_ << si_fdec << ctext_->end_of_stat() << endl;
  ih_ << sc_fdec << ctext_->end_of_stat() << endl;

  // Declare evaluator constructor/destructor (specific to the type of computation)
  for(tciter t=taskmgr.first(); t!=taskmgr.plast(); ++t) {
    const std::string& tlabel = t->label();

    oss_.str(null_str_);
    oss_ << ctext_->type_name<void>() << " "
	     << ctext_->label_to_name(cparams->api_prefix() + "libint2_init_" + tlabel)
	     << "(" << ctext_->inteval_type_name(tlabel)
	     << "* inteval, int max_am, void* buf)";
    std::string li_fdec(oss_.str());
    li_decls_.push_back(li_fdec);
  
    oss_.str(null_str_);
    oss_ << ctext_->type_name<size_t>() << " "
	     << ctext_->label_to_name(cparams->api_prefix() + "libint2_need_memory_" + tlabel)
	     << "(int max_am)";
    std::string lm_fdec(oss_.str());
    lm_decls_.push_back(lm_fdec);

    oss_.str(null_str_);
    oss_ << ctext_->type_name<void>() << " "
	     << ctext_->label_to_name(cparams->api_prefix() + "libint2_cleanup_" + tlabel)
	     << "(" << ctext_->inteval_type_name(tlabel) << "* inteval)";
    std::string lc_fdec(oss_.str());
    lc_decls_.push_back(lc_fdec);

    ih_ << li_fdec << ctext_->end_of_stat() << endl;
    ih_ << lm_fdec << ctext_->end_of_stat() << endl;
    ih_ << lc_fdec << ctext_->end_of_stat() << endl;
  }

  // if counting flops, need additional initializer function
  if (cparams_->count_flops()) {
    oss_.str(null_str_);
    oss_ << "#ifdef __cplusplus\n#ifdef LIBINT2_FLOP_COUNT\nextern \"C++\" template <typename EvalType> void "
       << ctext_->label_to_name(cparams->api_prefix() + "libint2_init_flopcounter")
       << "(EvalType* inteval_vector, int inteval_vector_size)"
       << ctext_->open_block();
      // TODO convert to ForLoop object
    oss_ << "for(int v=1; v!=inteval_vector_size; ++v)"
        << ctext_->open_block()
        << ctext_->assign("inteval_vector[v].nflops", "inteval_vector[0].nflops")
        << ctext_->close_block()
        << ctext_->close_block();
    oss_ << "#endif\n#endif\n";

    lf_decl_ = oss_.str();
    ih_ << lf_decl_ << ctext_->end_of_stat() << endl;
  }

  ih_ << ctext_->code_postfix() << endl;
  
  si_ << si_fdec << ctext_->open_block();
  sc_ << sc_fdec << ctext_->open_block();
  
}

Libint2Iface::~Libint2Iface()
{
  // For each task, print out defines for stack dimensions
  LibraryTaskManager& taskmgr = LibraryTaskManager::Instance();
  typedef LibraryTaskManager::TasksCIter tciter;
  for(tciter t=taskmgr.first(); t!=taskmgr.plast(); ++t) {
    SafePtr<TaskParameters> tparams = t->params();
    const std::string& tlabel = t->label();
    ph_ << macro_define(tlabel,"NUM_TARGETS",tparams->max_ntarget());
    const unsigned int max_am = tparams->max_am();
    for(unsigned int am=0; am<=max_am; ++am) {
      { std::ostringstream oss; oss << "MAX_STACK_SIZE_" << am;
        ph_ << macro_define(tlabel,oss.str(),tparams->max_stack_size(am)); }
      { std::ostringstream oss; oss << "MAX_VECTOR_STACK_SIZE_" << am;
        const unsigned int max_vector_stack_size = tparams->max_vector_stack_size(am);
        ph_ << macro_define(tlabel,oss.str(),max_vector_stack_size); }
      { std::ostringstream oss; oss << "MAX_HRR_HSRANK_" << am;
        ph_ << macro_define(tlabel,oss.str(),tparams->max_hrr_hsrank(am)); }
      { std::ostringstream oss; oss << "MAX_HRR_LSRANK_" << am;
        ph_ << macro_define(tlabel,oss.str(),tparams->max_hrr_lsrank(am)); }
    }
  }

  // For each task, generate the evaluator type
  th_ << "#include <libint2/util/vector.h>" << std::endl;
  th_ << "#include <libint2/util/intrinsic_operations.h>" << std::endl;
  th_ << "#include <libint2/util/timer.h>" << std::endl; // in case LIBINT2_PROFILE is on
  generate_inteval_type(th_);

  // libint2_iface.h needs macros to help forming prefixed names in API
  ih_ << ctext_->comment("Use LIBINT2_PREFIXED_NAME(fncname) to form properly prefixed function name from LIBINT2 API") << std::endl;
  ih_ << "#define LIBINT2_PREFIXED_NAME(name) __libint2_prefixed_name__(LIBINT2_API_PREFIX,name)" << std::endl;
  ih_ << "#define __libint2_prefixed_name__(prefix,name) __prescanned_prefixed_name__(prefix,name)" << std::endl;
  ih_ << "#define __prescanned_prefixed_name__(prefix,name) prefix##name" << std::endl;
  // also need macros to test what's in the evaluator
  ih_ << ctext_->comment("Use LIBINT2_PREFIXED_NAME(fncname) to form properly prefixed function name from LIBINT2 API") << std::endl;
  ih_ << "#define LIBINT2_DEFINED(taskname,symbol) __prescanned_libint2_defined__(taskname,symbol)" << std::endl;
  if (cparams_->single_evaltype())
    ih_ << "#define __prescanned_libint2_defined__(taskname,symbol) LIBINT2_DEFINED_##symbol" << std::endl << std::endl;
  else
    ih_ << "#define __prescanned_libint2_defined__(taskname,symbol) LIBINT2_DEFINED_##symbol##_##taskname" << std::endl << std::endl;


  
  header_guard_close(th_);
  header_guard_close(ph_);
  header_guard_close(ih_);
  header_guard_close(ii_);

  std::ostringstream oss;
  oss << ctext_->close_block() << ctext_->code_postfix() << endl << endl;
  std::string pfix = oss.str();

  si_ << pfix;
  sc_ << pfix;

  // Define Libint_t constructor/destructor (specific to the type of computation)
  {
    unsigned int i = 0;
    for(tciter t=taskmgr.first(); t!=taskmgr.plast(); ++t,++i) {
      const std::string& tlabel = t->label();

      li_ << lm_decls_[i] << ctext_->open_block();
      const unsigned int max_am = t->params()->max_am();
      for(unsigned int am=0; am<=max_am; ++am) {
        std::string ss, vss, hsr, lsr;
        { std::ostringstream oss;
          oss << "MAX_STACK_SIZE_" << am; ss = oss.str(); }
        { std::ostringstream oss;
          oss << "MAX_VECTOR_STACK_SIZE_" << am; vss = oss.str(); }
        { std::ostringstream oss;
          oss << "MAX_HRR_HSRANK_" << am; hsr = oss.str(); }
        { std::ostringstream oss;
          oss << "MAX_HRR_LSRANK_" << am; lsr = oss.str(); }

        li_ << "assert(max_am <= " << max_am << ");" << std::endl;

        li_ << "if (max_am == " << am << ") return " << macro(tlabel,ss) << " * " << macro("MAX_VECLEN") << " + "
            << macro(tlabel,vss) << " * " << macro("MAX_VECLEN") << " * ("
            << macro(tlabel,hsr) << " > " << macro(tlabel,lsr) << " ? "
            << macro(tlabel,hsr) << " : " << macro(tlabel,lsr) << ");" << std::endl;
      }
      li_ << "return 0; // unreachable" << std::endl;
      li_ << ctext_->close_block();
    }

    i = 0;
    for(tciter t=taskmgr.first(); t!=taskmgr.plast(); ++t,++i) {
      const std::string& tlabel = t->label();

      li_ << li_decls_[i] << ctext_->open_block();
      li_ << "if (buf != 0) inteval->stack = reinterpret_cast<LIBINT2_REALTYPE*>(buf);" << std::endl << "else " << std::endl;
      {
        std::string tmp = ctext_->label_to_name(cparams_->api_prefix() + "libint2_need_memory_" + tlabel) + "(max_am)";

        // no posix_memalign? use new, with default alignment
        li_ << ctext_->assign("inteval->stack",std::string("libint2::malloc<LIBINT2_REALTYPE>(") + tmp + std::string(")"));

      }

      const unsigned int max_am = t->params()->max_am();
      for(unsigned int am=0; am<=max_am; ++am) {
        std::string ss;
        { std::ostringstream oss;
        oss << "MAX_STACK_SIZE_" << am; ss = oss.str(); }

        li_ << "assert(max_am <= " << max_am << ");" << std::endl;
        li_ << "if (max_am == " << am << ")" << std::endl;
        std::string vstack_ptr("inteval->stack + ");
        vstack_ptr += macro(tlabel,ss);
        vstack_ptr += " * ";
        vstack_ptr += macro("MAX_VECLEN");
        li_ << ctext_->assign("inteval->vstack",vstack_ptr);
      }

      if (cparams_->count_flops()) {
        // allocate the counter and set it to zero
        li_ << "inteval->nflops = new " << macro("UINT_LEAST64") << ";" << endl;
        li_ << "inteval->nflops[0] = 0;" << endl;
      }
      if (cparams_->profile()) { // zero out the timers
        li_ << ctext_->macro_if("LIBINT2_CPLUSPLUS_STD >= 2011");
        li_ << "inteval->timers = new libint2::Timers<2>;" << endl;
        li_ << "inteval->timers->clear();" << endl;
        li_ << ctext_->macro_endif(); // >= C++11
      }
      li_ << ctext_->close_block();
    }

    i = 0;
    for(tciter t=taskmgr.first(); t!=taskmgr.plast(); ++t,++i) {
      li_ << lc_decls_[i] << ctext_->open_block();

      li_ << "free(inteval->stack);\n";
      li_ << ctext_->assign("inteval->stack","0");
      li_ << ctext_->assign("inteval->vstack","0");
      if (cparams_->count_flops()) {
        // free the counter and set the pointer to zero
        li_ << "delete inteval->nflops;" << endl;
        li_ << "inteval->nflops = 0;" << endl;
      }
      if (cparams_->profile()) {
        li_ << ctext_->macro_if("LIBINT2_CPLUSPLUS_STD >= 2011");
        li_ << "delete inteval->timers;" << endl;
        li_ << "inteval->timers = 0;" << endl;
        li_ << ctext_->macro_endif(); // >= C++11
      }
      li_ << ctext_->close_block();
    }
  }

  li_ << ctext_->code_postfix() << std::endl << std::endl;

  th_.close();
  ph_.close();
  ih_.close();
  ii_.close();
  si_.close();
  sc_.close();
  li_.close();
}

namespace libint2 {

  /// Parses the symbol if it is composed of a prefix followed by a number.
  class Parser_prefixN {

    public:
      Parser_prefixN(const std::string& symbol) :
        match_(false), N_(0) {

        std::string N_str;
        bool minus = false;

        auto prefix_rbegin = symbol.rbegin();
        for(auto ri = symbol.rbegin(); ri != symbol.rend(); ++ri) {
          if (isdigit(*ri)) {
            match_ = true;
            N_str.insert(0, 1, *ri);
          }
          else { // no more digits? check for a minus sign
            prefix_rbegin = ri;
            if (*ri == '_') {
              std::string token("_");
              ++ri;
              for(int i=0; i<6 && ri != symbol.rend(); ++ri, ++i) {
                token.push_back(*ri);
              }
              if (token == "_sunim_") { // minus reversed
                minus = true;
                prefix_rbegin = ri;
              }
            }
            break;
          }
        }

        if (match_) {

          N_ = atoi(N_str.c_str());
          if (minus)
            N_ *= -1;

          prefix_ = std::string(prefix_rbegin, symbol.rend());
          std::reverse(prefix_.begin(), prefix_.end());
        }
      }

      /// returns true if the pattern matched
      bool match() const { return match_; }
      /// returns N (only valid if match()==true)
      int N() const { return N_; }
      /// returns prefix (only valid if match()==true)
      const std::string& prefix() const { return prefix_; }

    private:
      bool match_;
      std::string prefix_;
      int N_;

  };
}

void
Libint2Iface::generate_inteval_type(std::ostream& os)
{
  LibraryTaskManager& taskmgr = LibraryTaskManager::Instance();

  //
  // If need to generate single type for all tasks, take a union of all symbols
  // else process each task separately
  //
  typedef LibraryTaskManager::TasksCIter tciter;
  const tciter tend = cparams_->single_evaltype() ? taskmgr.first()+1 : taskmgr.plast();
  for(tciter t=taskmgr.first(); t!=tend; ++t) {
    const SafePtr<TaskExternSymbols> tsymbols = t->symbols();
    
    // Prologue
    os << "typedef struct {" << std::endl;

    //
    // Declare external symbols
    //
    typedef TaskExternSymbols::SymbolList SymbolList;
    std::string tlabel;
    SymbolList symbols;
    if (cparams_->single_evaltype()) {
      TaskExternSymbols composite_symbols;
      const tciter tend = taskmgr.plast();
      for(tciter t=taskmgr.first(); t!=tend; ++t) {
        const SafePtr<TaskExternSymbols> tsymbols = t->symbols();
        composite_symbols.add(tsymbols->symbols());
      }
      symbols = composite_symbols.symbols();
      tlabel = "";
    }
    else {
      symbols = tsymbols->symbols();
      tlabel = t->label();
    }

    // Spend some effort to ensure reasonable ordering/grouping of symbols in the list, e.g.
    // all symbols of form PrefixIndex (e.g. crazyprefixN) will be grouped together in the order of increasing Index.
    SymbolList ordered_symbols;
    // 1) convert all symbols to valid code
    // 2) then scan for symbols matching pattern prefixN, for each prefix determine range of N
    std::map<std::string,std::pair<int,int> > prefix_symbols;
    for(auto s : symbols) {
      Parser_prefixN parser(ctext_->label_to_name(s));
      if (parser.match()) {
        if (prefix_symbols.find(parser.prefix()) == prefix_symbols.end()) {
          prefix_symbols[parser.prefix()] = std::make_pair(parser.N(), parser.N());
        }
        else {
          const int N = parser.N();
          auto Nlimits = prefix_symbols[parser.prefix()];
          if (N < Nlimits.first)  Nlimits.first = N;
          if (N > Nlimits.second) Nlimits.second = N;
          prefix_symbols[parser.prefix()] = Nlimits;
        }
      }
    }

    // 3) for each prefix iterate over the corresponding range, and add the matching symbols in the same order
    for(auto pi=prefix_symbols.begin(); pi!=prefix_symbols.end(); ++pi) {
      auto prefix = pi->first;
      auto Nlimits = pi->second;
      for(int N=Nlimits.first; N<=Nlimits.second; ++N) {
        std::ostringstream oss; oss << prefix << (N<0 ? "-" : "") << abs(N);
        std::string code_symbol = ctext_->label_to_name(oss.str());
        for(auto v : symbols) {
          if (code_symbol == ctext_->label_to_name(v)) {
            ordered_symbols.push_back(code_symbol);
          }
        }
      }
    }
    // 4) the rest of symbols can appear in any order
    //    in practice, the order is lexicographic, hence xyz components are automatically ordered x,y,z
    for(auto s : symbols) {
      if (std::find(ordered_symbols.begin(), ordered_symbols.end(), ctext_->label_to_name(s)) == ordered_symbols.end())
        ordered_symbols.push_back(s);
    }

    // now dump all symbols into the evaluator data type definition
    for(auto s=ordered_symbols.begin(); s!=ordered_symbols.end(); ++s) {
      // for each extrnal symbol #define a macro
      std::string tmplabel("DEFINED_"); tmplabel += ctext_->label_to_name(*s);
      os << macro_define(tlabel,tmplabel,1);
      // all symbols are doubles
      os << var_declare_v<double>(*s);
    }

    // Declare members common to all evaluators
    os << ctext_->comment("Scratch buffer to hold intermediates") << std::endl;
    os << ctext_->declare(ctext_->mutable_modifier() + ctext_->type_name<double*>(),std::string("stack"));

    os << ctext_->comment("Buffer to hold vector intermediates. Only used by set-level RR code if it is vectorized linewise") << std::endl;
    os << ctext_->declare(ctext_->mutable_modifier() + ctext_->type_name<double*>(),std::string("vstack"));

    os << ctext_->comment("On completion, this contains pointers to computed targets") << std::endl;
    if (cparams_->single_evaltype()) {
      // figure out the maximum number of targets
      unsigned int max_ntargets = 0;
      const tciter tend = taskmgr.plast();
      for(tciter t=taskmgr.first(); t!=tend; ++t) {
        SafePtr<TaskParameters> tparams = t->params();
        max_ntargets = std::max(max_ntargets,tparams->max_ntarget());
      }
      ostringstream oss;
      oss << max_ntargets;
      os << ctext_->declare_v(ctext_->mutable_modifier() + ctext_->type_name<double*>(),std::string("targets"),oss.str());
    }
    else {
      os << ctext_->declare_v(ctext_->mutable_modifier() + ctext_->type_name<double*>(),std::string("targets"),macro(tlabel,"NUM_TARGETS"));
    }

    os << ctext_->comment("Actual vector length. Not to exceed MAX_VECLEN! If MAX_VECLEN is 1 then veclen is not used") << std::endl;
    os << ctext_->declare(ctext_->type_name<int>(),std::string("veclen"));

    os << ctext_->macro_if(macro("FLOP_COUNT"));
    os << ctext_->comment("FLOP counter. Libint must be configured with --enable-flop-counter to allow FLOP counting. It is user's reponsibility to set zero nflops before computing integrals.") << std::endl;
    os << ctext_->declare(ctext_->mutable_modifier() + macro("UINT_LEAST64*"),std::string("nflops"));
    os << ctext_->macro_endif();

    os << ctext_->macro_if(macro("PROFILE"));
    os << ctext_->macro_if("LIBINT2_CPLUSPLUS_STD >= 2011");
    os << ctext_->comment("profiling timers. Libint must be configured with --enable-profile to allow profiling.") << std::endl;
    os << "#ifdef __cplusplus" << std::endl
       << ctext_->declare(ctext_->mutable_modifier() + "libint2::Timers<2>*",std::string("timers")) // 1 timer for HRR and 1 timer for VRR
       << "#else // timers are not accessible from C" << std::endl
       << "  void* timers;" << std::endl
       << "#endif" << std::endl;
    os << ctext_->macro_endif(); // >= C++11
    os << ctext_->macro_endif();

    os << ctext_->macro_if(macro("ACCUM_INTS"));
    os << ctext_->comment("If libint was configured with --enable-accum-ints then the target integrals are accumulated. To zero out the targets automatically before the computation, set this to nonzero.") << std::endl;
    os << ctext_->declare(ctext_->type_name<int>(),std::string("zero_out_targets"));
    os << ctext_->macro_endif();

    os << ctext_->macro_if(macro("CONTRACTED_INTS"));
    os << ctext_->comment("If libint was configured with --enable-contracted-ints then contracted integrals are supported. Set this parameter to the total number of primitive combinations.") << std::endl;
    os << ctext_->declare(ctext_->type_name<int>(),std::string("contrdepth"));
    os << ctext_->macro_endif();

    // Epilogue
    os << "} " << ctext_->inteval_type_name(tlabel) << ctext_->end_of_stat() << std::endl;
    
  }

  // If generating single evaluator type, create aliases from the specialized types to the actual type
  if (cparams_->single_evaltype()) {
    const tciter tend = taskmgr.plast();
    for(tciter t=taskmgr.first(); t!=tend; ++t) {
      const std::string& tlabel = t->label();
      os << "typedef "
	 << ctext_->inteval_gen_type_name() << " "
	 << ctext_->inteval_spec_type_name(tlabel)
	 << ctext_->end_of_stat() << std::endl;
    }
  }

}
