/*! \file
 \ingroup INPUT
 \brief Enter brief description of file here 
 */
#define EXTERN
#include <cstdio>
#include <cstdlib>
#include <strings.h>
#include <libciomr/libciomr.h>
#include <cmath>
#include <cstring> // for memset
#include "input.h"
#include "global.h"
#include "defines.h"

namespace psi {
  namespace input {
    
    void build_so_classes() {
      int i, j, l, tmp;
      int atom, ua;
      int kfirst, klast;
      int clss, lmax, irr, ao, symop, char_i;
      int ao_first, ao_last, ao_max, ao_irr;
      int class_first, class_last, uc;
      int shell, shell_first, shell_last;
      int **class_num_angmom;
      int **coeff_irr;
      int so_cnt, so_blk_lim, so_blk_off, cnt;
      int ao_offset;
      int basisfn_num_in_symblk;
      int *first_basisfn_in_symblk_offset;
      int *first_basisfn_in_symblk;
      
      /*----------------------------------------------------------------
       Compute maximum angular momentum number for each class of atoms
       ----------------------------------------------------------------*/

      max_angmom_class = init_int_array(num_classes);
      class_num_angmom = init_int_matrix(num_classes, max_angmom+1);
      for (i=0; i<num_atoms; i++) {
        kfirst = first_shell_on_atom[i];
        klast = kfirst + nshells_per_atom[i];
        clss = atom_class[i];
        for (j=kfirst; j<klast; j++) {
          max_angmom_class[clss]
              = (max_angmom_class[clss] > shell_ang_mom[j]) ? max_angmom_class[clss]
                                                            : shell_ang_mom[j];
          class_num_angmom[clss][shell_ang_mom[j]]++;
        }
      }
      
      /*-----------------------
       Allocate global arrays
       -----------------------*/

      num_cart_so_in_class = (int ***) malloc(num_classes*sizeof(int **));
      for (clss=0; clss<num_classes; clss++)
        num_cart_so_in_class[clss] = init_int_matrix(max_angmom_class[clss]+1,
                                                     nirreps);
      class_so_coeff = (double ****) malloc(num_classes*sizeof(double ***));
      for (i=0; i<num_classes; i++)
        class_so_coeff[i] = (double ***) malloc((max_angmom_class[i]+1)
            *sizeof(double **));
      if (puream) {
        num_pureang_so_in_class = (int ***) malloc(num_classes*sizeof(int **));
        for (clss=0; clss<num_classes; clss++)
          num_pureang_so_in_class[clss]
              = init_int_matrix(max_angmom_class[clss]+1, nirreps);
      }
      
      /*----------------------
       Allocate local arrays
       -----------------------*/

      coeff_irr = init_int_matrix(nirreps, ioff[max_angmom+1]);
      
      /*------------------------------------------------------------------------------------------------------------------------
       Loop over each unique class, each angular momentum number within class, irreps, and basis function types
       and 1) count number of SO in each representation num_cart_so_in_class[class][l][irr] generated by a shell of ang momentum l
       on an atom of a unique class "uc" or its symmetry equivalent classes;
       2) form matrices of coefficients and labels for classes equivalent to the unique class "uc"
       ------------------------------------------------------------------------------------------------------------------------*/
      for (uc=0; uc<num_unique_classes; uc++) {
        clss = uc2c[uc];
        class_first = clss;
        class_last = clss + unique_class_degen[uc];
        const double norm_pfac = 1.0 / std::sqrt((double) unique_class_degen[uc]);
        
        lmax = max_angmom_class[clss];
        for (l=0; l<=lmax; l++) {
          ao_max = ioff[l+1];
          for (symop=0; symop<nirreps; symop++)
            if (class_orbit[clss][symop] == clss)
              for (irr=0; irr<nirreps; irr++)
                for (ao=0; ao<ao_max; ao++)
                  coeff_irr[irr][ao] += irr_char[irr][symop]
                      *ao_type_transmat[l][symop][ao];
          for (irr=0; irr<nirreps; irr++)
            for (ao=0; ao<ao_max; ao++)
              if (coeff_irr[irr][ao] > 0)
                for (i=class_first; i<class_last; i++)
                  num_cart_so_in_class[i][l][irr] += 1;
          
          if (puream) {
            for (irr=0; irr<nirreps; irr++)
              for (i=class_first; i<class_last; i++)
                num_pureang_so_in_class[i][l][irr]
                    = num_cart_so_in_class[i][l][irr];
            for (ao_irr=0; ao_irr<nirreps; ao_irr++)
              if (num_cart_so[l][ao_irr] != 0)
                for (ao=0; ao<ao_max; ao++)
                  if (ao_type_irr[l][ao] == ao_irr) {
                    for (irr=0; irr<nirreps; irr++)
                      if (coeff_irr[irr][ao] > 0)
                        for (i=class_first; i<class_last; i++)
                          num_pureang_so_in_class[i][l][irr]
                              -= num_redun_so[l][ao_irr];
                    break;
                  }
          }
          
          /*---------------------------------
           Remove after testing is complete
           ---------------------------------*/
          if (print_lvl >= DEBUGPRINT) {
            fprintf(
                    outfile,
                    "    Class = %d  l = %d\n     Number of cartesian SOs in symmetry blocks:",
                    clss+1, l);
            for (irr=0; irr<nirreps; irr++)
              fprintf(outfile, " %d", num_cart_so_in_class[clss][l][irr]);
            fprintf(outfile, "\n\n");
            if (puream) {
              fprintf(
                      outfile,
                      "    Class = %d  l = %d\n     Number of pure angular momentum SOs in symmetry blocks:",
                      clss+1, l);
              for (irr=0; irr<nirreps; irr++)
                fprintf(outfile, " %d", num_pureang_so_in_class[clss][l][irr]);
              fprintf(outfile, "\n\n");
            }
          }
          
          /*Compute SO coefficients and labels for ALL classes - children of a unique class "class"*/
          for (i=class_first; i<class_last; i++) {
            class_so_coeff[i][l] = init_matrix(ao_max*unique_class_degen[uc],
                                               ao_max);
            for (irr=0; irr<nirreps; irr++)
              //bzero((char *)coeff_irr[irr], sizeof(int)*ao_max);
              memset(coeff_irr[irr], '\0', sizeof(int)*ao_max);
            for (symop=0; symop<nirreps; symop++)
              if (class_orbit[class_first][symop] == i)
                for (irr=0; irr<nirreps; irr++)
                  if (num_cart_so_in_class[clss][l][irr] != 0) /*There are SOs in this class and ang. momentum type*/
                    for (ao=0; ao<ao_max; ao++)
                      coeff_irr[irr][ao] += irr_char[irr][symop]
                          *ao_type_transmat[l][symop][ao];
            so_cnt = 0;
            for (irr=0; irr<nirreps; irr++)
              if (num_cart_so_in_class[clss][l][irr] != 0)
                for (ao=0; ao<ao_max; ao++)
                  if (coeff_irr[irr][ao] != 0)
                    class_so_coeff[i][l][so_cnt++][ao] = sign(coeff_irr[irr][ao]) * norm_pfac;
          }
          for (irr=0; irr<nirreps; irr++)
            //bzero((char *)coeff_irr[irr], sizeof(int)*ao_max);
            memset(coeff_irr[irr], '\0', sizeof(int)*ao_max);
        }
      }
      
      /*---------------------------------
       Remove after testing is complete
       ---------------------------------*/
      if (print_lvl >= DEBUGPRINT)
        for (uc=0; uc<num_unique_classes; uc++) {
          clss = uc2c[uc];
          class_first = clss;
          class_last = clss + unique_class_degen[uc];
          lmax = max_angmom_class[clss];
          for (i=class_first; i<class_last; i++) {
            fprintf(outfile, "\n    Class = %d\n", i+1);
            for (l=0; l<=lmax; l++) {
              ao_max = ioff[l+1];
              fprintf(outfile, "     l = %d\n", l);
              for (j=0; j<ao_max*unique_class_degen[uc]; j++) {
                fprintf(outfile, "     ");
                for (ao=0; ao<ao_max; ao++)
                  fprintf(outfile, "    %4.1lf", class_so_coeff[i][l][j][ao]);
                fprintf(outfile, "\n");
              }
            }
          }
        }
      
      return;
    }
  
  }
} // namespace psi::input
