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

namespace psi {
  namespace input {
    
    /*-----------------------------------------------------------------------------------------------------------------
     This function builds unitary SO to AO matrix
     -----------------------------------------------------------------------------------------------------------------*/

    void build_usotao() {
      int clss, lmax, irr, ao, atom, l, i, j;
      int shell, shell_first, shell_last;
      int ao_max;
      int cart_cnt, pureang_cnt, so_cnt, cnt;
      int ao_offset, ao_irr;
      int ua;
      int so1, so2;
      int first_so_from_shell, num_so_from_shell;
      int cart_so_num_in_symblk;
      int pureang_so_num_in_symblk;
      int **first_cart_so_in_symblk_from_atom; /*Relative number (WRT the beginning of the symblk) of the first cart SO in each symblk from an atom*/
      int **first_cart_so_in_symblk_from_unique; /*Number of the first cart SO in each symblk from a UNIQUE atom*/
      int *cart_symblk_offset; /*Offset array that stores numbers to the cartesian SO to be added next in each irrep*/
      int *first_cart_so_in_symblk; /*Number of the first cartesian SO in each symmetry block*/
      int *pureang_symblk_offset; /*Offset array that stores numbers to the cartesian SO to be added next in each irrep*/
      int **first_pureang_so_in_symblk; /*Relative number (WRT the beginning of the symblk) of the first pureang SO in each symblk
       from an atom*/
      int **first_pureang_so_unique; /*Number of the first pureang SO in each symblk from a UNIQUE atom*/
      int *first_so_in_symblk; /*Number of the first cart/pureang SO in each symmetry block*/
      int flag, num_so_added, m;
      double *tmp_ptr;
      
      /*------------------------
       Initialize global arrays
       -------------------------*/

      num_cart_so_per_irrep = init_int_array(nirreps);
      usotao_cart = init_matrix(num_ao, num_ao);
      if (puream) {
        num_so_per_irrep = init_int_array(nirreps);
        cart_usotao_pureang = init_matrix(num_so, num_ao);
        usotbf = init_matrix(num_so, num_so);
        pureang_so_m = init_int_array(num_so);
      } else
        num_so_per_irrep = num_cart_so_per_irrep;
      
      /*------------------------
       Initialize local arrays
       -------------------------*/

      first_cart_so_in_symblk_from_atom = (int **) malloc(sizeof(int *)
          *num_atoms);
      cart_symblk_offset = init_int_array(nirreps);
      first_cart_so_in_symblk_from_unique = init_int_matrix(num_uniques,
                                                            nirreps);
      first_cart_so_in_symblk = init_int_array(nirreps);
      if (puream) {
        pureang_symblk_offset = init_int_array(nirreps);
        first_pureang_so_in_symblk = init_int_matrix(num_atoms, nirreps);
        first_pureang_so_unique = init_int_matrix(num_uniques, nirreps);
        first_so_in_symblk = init_int_array(nirreps);
      } else
        first_so_in_symblk = first_cart_so_in_symblk;
      
      /*------------------------------------------------------------------------------------------------------------------------
       Compute number of SOs per symblk and the number of the first SO in each symblk from each unique atom
       ------------------------------------------------------------------------------------------------------------------------*/
      for (ua=0; ua<num_uniques; ua++) {
        atom = u2a[ua];
        clss = atom_class[atom];
        shell_first = first_shell_on_atom[atom];
        shell_last = first_shell_on_atom[atom] + nshells_per_atom[atom];
        if (ua < num_uniques-1) /* For all but the last unique atom ... */
          for (irr=0; irr<nirreps; irr++) { /* ... count number of cartesian SOs in each irreps generated by the unique atoms and its children */
            cart_cnt = 0;
            pureang_cnt = 0;
            for (shell=shell_first; shell<shell_last; shell++)
              cart_cnt += num_cart_so_in_class[clss][shell_ang_mom[shell]][irr];
            first_cart_so_in_symblk_from_unique[ua+1][irr]
                = first_cart_so_in_symblk_from_unique[ua][irr] + cart_cnt; /* and compute the corresponding offsets*/
            num_cart_so_per_irrep[irr] += cart_cnt;
            
            if (puream) { /* Do the same for pureang SOs*/
              for (shell=shell_first; shell<shell_last; shell++)
                pureang_cnt
                    += num_pureang_so_in_class[clss][shell_ang_mom[shell]][irr];
              first_pureang_so_unique[ua+1][irr]
                  = first_pureang_so_unique[ua][irr] + pureang_cnt;
              num_so_per_irrep[irr] += pureang_cnt;
            }
            
          }
        else
          /* Do analogous stuff for the last unique atom*/
          for (irr=0; irr<nirreps; irr++) {
            cart_cnt = 0;
            pureang_cnt = 0;
            for (shell=shell_first; shell<shell_last; shell++)
              cart_cnt += num_cart_so_in_class[clss][shell_ang_mom[shell]][irr];
            num_cart_so_per_irrep[irr] += cart_cnt;
            
            if (puream) {
              for (shell=shell_first; shell<shell_last; shell++)
                pureang_cnt
                    += num_pureang_so_in_class[clss][shell_ang_mom[shell]][irr];
              num_so_per_irrep[irr] += pureang_cnt;
            }
            
          }
        for (i=0; i<unique_degen[ua]; i++) {
          atom = red_unique_orbit[ua][i];
          first_cart_so_in_symblk_from_atom[atom]
              = first_cart_so_in_symblk_from_unique[ua];
          if (puream)
            first_pureang_so_in_symblk[atom] = first_pureang_so_unique[ua];
        }
      }
      
      /* Compute number of cartesian SOs in each irrep*/
      first_cart_so_in_symblk[0] = 0;
      for (irr=1; irr<nirreps; irr++)
        first_cart_so_in_symblk[irr] = first_cart_so_in_symblk[irr-1]
            + num_cart_so_per_irrep[irr-1];
      /* ... and if needed number of pureang SOs in each irrep*/
      if (puream) {
        first_so_in_symblk[0] = 0;
        for (irr=1; irr<nirreps; irr++)
          first_so_in_symblk[irr] = first_so_in_symblk[irr-1]
              + num_so_per_irrep[irr-1];
      }
      
      /*------------------------------------------------------------------------------------------------------------------------
       Loop over each unique class, each atom within the class, each shell on the atom, each irrep and construct the AOs to
       "cartesian" SOs U-matrix
       ------------------------------------------------------------------------------------------------------------------------*/
      for (atom=0; atom<num_atoms; atom++) {
        clss = atom_class[atom];
        shell_first = first_shell_on_atom[atom];
        shell_last = first_shell_on_atom[atom] + nshells_per_atom[atom];
        for (irr=0; irr<nirreps; irr++)
          cart_symblk_offset[irr]
              = first_cart_so_in_symblk_from_atom[atom][irr]
                  + first_cart_so_in_symblk[irr];
        if (puream)
          for (irr=0; irr<nirreps; irr++)
            pureang_symblk_offset[irr] = first_pureang_so_in_symblk[atom][irr]
                + first_so_in_symblk[irr];
        for (shell=shell_first; shell<shell_last; shell++) {
          l = shell_ang_mom[shell];
          ao_max = ioff[l+1];
          ao_offset = first_ao_shell[shell];
          cnt = 0;
          for (irr=0; irr<nirreps; irr++)
            /*Go over each symmetry block*/
            if (num_cart_so_in_class[clss][l][irr] != 0) { /*If there are SO's of this irrep from this shell - proceed*/
              num_so_added = 0;
              for (i=0; i<num_cart_so_in_class[clss][l][irr]; i++) { /*Loop until all of SO's are found*/
                cart_so_num_in_symblk = cart_symblk_offset[irr] + i; /*Number of the cartesian SO being added to usotao_cart*/
                if (puream)
                  pureang_so_num_in_symblk = pureang_symblk_offset[irr]; /*Number of the pureang SO being added to cart_usotao_pureang*/
                for (ao=0; ao<ao_max; ao++)
                  /* Find the AO that contributes to the current SO ...*/
                  if (class_so_coeff[clss][l][cnt][ao] != 0.0) {
                    usotao_cart[cart_so_num_in_symblk][ao_offset+ao]
                        = class_so_coeff[clss][l][cnt][ao]; /*... and add it to the usotao_cart*/
                    
                    /*-------------------------
                     cart_usotao_pureang mess
                     -------------------------*/
                    if (puream)
                      for (m=-l; m<=l; m++)
                        /*Loop over all m's and find pureang SO's to which this AO contributes*/
                        if (cart2pureang[l][m+l][ao] != 0.0) { /*When found one ...*/
                          flag = 0;
                          for (j=pureang_so_num_in_symblk; j
                              < pureang_so_num_in_symblk+num_so_added; j++)
                            /*Check m's of already added SOs*/
                            if (pureang_so_m[j] == m) { /*****If this pureang SO has been added ...*/
                              cart_usotao_pureang[j][cart_so_num_in_symblk]
                                  = cart2pureang[l][m+l][ao]; /*... then add cart2pureang coefficient to the already existing row*/
                              flag = 1; /*Set flag indicating that SO has already been added to true*/
                              break;
                            }
                          if (flag == 0) { /*****If it has not been added yet ...*/
                            pureang_so_m[pureang_so_num_in_symblk+num_so_added]
                                = m; /*... then update list of SO's m-values,*/
                            cart_usotao_pureang[pureang_so_num_in_symblk+num_so_added][cart_so_num_in_symblk]
                                = cart2pureang[l][m+l][ao]; /*... add cart2pureang coeff it to a new row*/
                            usotbf[pureang_so_num_in_symblk+num_so_added][first_basisfn_shell[shell]+m+l]
                                = class_so_coeff[clss][l][cnt][ao];
                            num_so_added++;
                          }
                        } /* end of cart_usotao_pureang mess*/
                    
                    break; /* Get out of the ao-loop since only one AO contributes to each cartesian SO*/
                  }
                cnt++; /* Increase counter of added cartesian SO's*/
              }
              cart_symblk_offset[irr] += num_cart_so_in_class[clss][l][irr]; /*Update pointers in the symmetry blocks of cartesian ...*/
              if (puream)
                pureang_symblk_offset[irr]
                    += num_pureang_so_in_class[clss][l][irr]; /* ... and pureang SO's*/
            }
        }
      }
      
      /* Now we need to re-sort pureang SOs according to their m-values, otherwise CINTS
       would have to do too much work in C1 symmetry case */
      if (puream)
        for (ua=0; ua<num_uniques; ua++) {
          atom = u2a[ua];
          clss = atom_class[atom];
          shell_first = first_shell_on_atom[atom];
          shell_last = first_shell_on_atom[atom] + nshells_per_atom[atom];
          for (irr=0; irr<nirreps; irr++) {
            first_so_from_shell = first_so_in_symblk[irr]
                + first_pureang_so_unique[ua][irr];
            for (shell=shell_first; shell<shell_last; shell++) {
              num_so_from_shell
                  = num_pureang_so_in_class[clss][shell_ang_mom[shell]][irr];
              for (so1=first_so_from_shell; so1<first_so_from_shell
                  +num_so_from_shell-1; so1++) {
                for (so2=so1+1; so2<first_so_from_shell+num_so_from_shell; so2++)
                  if (pureang_so_m[so2] < pureang_so_m[so1]) {
                    i = pureang_so_m[so1];
                    pureang_so_m[so1] = pureang_so_m[so2];
                    pureang_so_m[so2] = i;
                    tmp_ptr = cart_usotao_pureang[so1];
                    cart_usotao_pureang[so1] = cart_usotao_pureang[so2];
                    cart_usotao_pureang[so2] = tmp_ptr;
                    tmp_ptr = usotbf[so1];
                    usotbf[so1] = usotbf[so2];
                    usotbf[so2] = tmp_ptr;
                  }
              }
              first_so_from_shell += num_so_from_shell;
            }
          }
        }
      
      if (puream) {
        if (print_lvl >= DEBUGPRINT) {
          fprintf(outfile, "    -m quantum numbers of pureang SOs:\n");
          for (i=0; i<num_so; i++)
            fprintf(outfile, "  %3d  %3d\n", i+1, pureang_so_m[i]);
          fprintf(outfile, "\n");
          fprintf(outfile, "    -Cartesian SO to AO transformation matrix:\n");
          print_mat(usotao_cart, num_ao, num_ao, outfile);
          fprintf(outfile, "\n");
          fprintf(outfile,
                  "    -Cartesian SO to Pure Angular Momentum SO matrix:\n");
          print_mat(cart_usotao_pureang, num_so, num_ao, outfile);
          fprintf(outfile, "\n");
          fprintf(outfile,
                  "    -Pure Angular Momentum AO to Pure Angular Momentum SO matrix:\n");
          print_mat(usotbf, num_so, num_so, outfile);
          fprintf(outfile, "\n");
        }
        usotao = init_matrix(num_so, num_ao);
        mmult(cart_usotao_pureang, 0, usotao_cart, 0, usotao, 0, num_so,
              num_ao, num_ao, 0);
        free_matrix(usotao_cart, num_ao);
        free_matrix(cart_usotao_pureang, num_so);
      } else
        usotao = usotao_cart;
      
      if (print_lvl >= PRINTUSOTAO) {
        fprintf(outfile, "    -Unitary SO to AO transformation matrix:\n");
        print_mat(usotao, num_so, num_ao, outfile);
        fprintf(outfile, "\n");
      }
      
      /*---------
       Clean up
       ---------*/

      free_int_matrix(first_cart_so_in_symblk_from_unique);
      free(cart_symblk_offset);
      free(first_cart_so_in_symblk);
      if (puream) {
        /*      free_int_matrix(first_pureang_so_in_symblk);*/
        free_int_matrix(first_pureang_so_unique);
        free(pureang_symblk_offset);
        free(first_so_in_symblk);
      }
      
      return;
    }
  
  }
} // namespace psi::input
