#define EXTERN
#include <stdio.h>
#include <stdlib.h>
#include <libciomr/libciomr.h>
#include <math.h>
#include "input.h"
#include "global.h"
#include "defines.h"


/*-----------------------------------------------------------------------------------------------------------------
  This function builds unitary SO to AO matrix
 -----------------------------------------------------------------------------------------------------------------*/

void build_usotao()
{
  int class,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];
      class = 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[class][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[class][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[class][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[class][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++) {
      class = 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[class][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[class][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[class][l][cnt][ao] != 0.0) {   
		  usotao_cart[cart_so_num_in_symblk][ao_offset+ao] = class_so_coeff[class][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[class][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[class][l][irr];  /*Update pointers in the symmetry blocks of cartesian ...*/
	    if (puream)
	      pureang_symblk_offset[irr] += num_pureang_so_in_class[class][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];
	class = 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[class][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,num_uniques);
    free(cart_symblk_offset);
    free(first_cart_so_in_symblk);
    if (puream) {
/*      free_int_matrix(first_pureang_so_in_symblk,num_atoms);*/
      free_int_matrix(first_pureang_so_unique,num_uniques);
      free(pureang_symblk_offset);
      free(first_so_in_symblk);
    }
    
    return;
}

