/*
	file:		main.c
	purpose: 	calculate atomic accessible surface area

	author: beomsoo han
	data: 	21 September 2010
	place:	University of Alberta
*/ 

#include "area.h"
#include "accessc.h"


void setup_access(), calc_access(), qsortTagIndex();
int  save_access_result();
// int  read_converted_pdb();

char inpdbFname[MAX_FNAME_LEN], resultFname[MAX_FNAME_LEN];
// ATOM_REC atomArr[MAX_INDEX_PDB];


/*
void main(argc, argv)
int argc;
char **argv;
{
    int lastObs;

    setup_access(argc, argv);
    lastObs = read_converted_pdb(atomArr, inpdbFname);
    calc_access(atomArr, lastObs);
    save_access_result(atomArr, resultFname, lastObs);
}
*/

/*
void setup_access(argc, argv)
int argc;
char *argv[];
{
    int i;

    strcpy(inpdbFname, "");
    for (i=1; i<argc; i++) {
        if(strcmp(argv[i], FILE_FLAG) == 0)  {
            strcpy(inpdbFname, argv[i+1]);
            break;
        }
    }

    if (strcmp(inpdbFname, "") == 0) {
        printf("Error in [ACCESS]\n   accessc -f infile [-o outfile] \n\n");
    }

    strcpy(resultFname, "");
    for (i=1; i<argc; i++) {
        if(strcmp(argv[i], RESULT_FLAG) == 0)  {
            strcpy(resultFname, argv[i+1]);
            break;
        }
    }
}
*/


int save_access_result(atoms, resultfile, lastObs)
ATOM_REC *atoms;
char *resultfile;
int lastObs;
{
   FILE *fp;
   int i; 
   int lastResNum = -99999;
   char lastInsCode = '-';
   int status = 1; 
    
   fp = fopen(resultfile, "w");
   i = 1;
   while( i <= lastObs ) {
       // fprintf(fp,"%5d  %5d  %4s  %4s  %.3f \n", 
       //          i, atoms[i].resNum, atoms[i].resName, atoms[i].atomName, atoms[i].contactArea); 

       if (atoms[i].origResNum == lastResNum) {
           if (atoms[i].insCode == lastInsCode) {
                status = 1;
           }    
           else status = 0;
       } else {
           lastResNum = atoms[i].origResNum;
           lastInsCode = atoms[i].insCode;
           status = 1;
       }
       
       if(status) {    
           // do not print out Insertion Code (atoms[i].insCode), because it only print first insertion case
           // 2011.08.30 remove index i
           // fprintf(fp,"%5d %5d  %4s  %4s  %.3f \n", 
           //          i, atoms[i].origResNum, atoms[i].resName, atoms[i].atomName, atoms[i].contactArea); 
           fprintf(fp,"%d  %4s  %4s  %.3f \n", 
                     atoms[i].origResNum, atoms[i].resName, atoms[i].atomName, atoms[i].contactArea); 
       }

       i++;
   };

   if(i < lastObs+1) {
      printf("ERROR: saving results to file [%s]\n", resultfile);
   }

   fclose(fp); 
   return(i);
}

/*
int read_converted_pdb(atoms, pdbfile)
ATOM_REC *atoms;
char *pdbfile;
{
    int nobs, ret;
    // to read pdb atom's coordinates --> replace with Array from main
    FILE *fp;
   
    fp = fopen(pdbfile, "r");

    // read each line & assin to the structures 
    //   0 N   MET     1     27.34000  24.43000   2.61400      1.5000

    nobs = 1;
    ret = fscanf(fp, "%s%s%d%f%f%f%f",  atoms[nobs].atomName, atoms[nobs].resName,
                                         &atoms[nobs].resNum, &atoms[nobs].x, &atoms[nobs].y, &atoms[nobs].z,
                                         &atoms[nobs].rad[VAN]);
    while( ret > 0 ) {
       nobs++;
       ret = fscanf(fp, "%s%s%d%f%f%f%f", atoms[nobs].atomName, atoms[nobs].resName,
                                         &atoms[nobs].resNum, &atoms[nobs].x, &atoms[nobs].y, &atoms[nobs].z,
                                         &atoms[nobs].rad[VAN]);
    }
    fclose(fp);

    return(nobs-1);
}
*/


void calc_access(atoms, lastObs)
ATOM_REC *atoms;
int lastObs; // number of reading instances from pdb
{

  int i, ib = 0;
  int karc = ICT_MAX;  
  int iter, j, k, kji, n, idim, jidim, kjidim;
  float dmax, rmax = 0.0; 
  float xmin, ymin, zmin, xmax, ymax, zmax; 
  float radsq[MAX_INDEX_PDB];
  int itab[MAX_INDEX_PDB] = {0}; // init all atom index table for each cube
  int natm[250][MAX_ATOMS_IN_A_CUBE], cube[MAX_ATOMS_IN_A_CUBE];  // 2001

  int io, kk, jj, m, mkji, nm, in, nzp; 
  float xr, yr, zr, rr, rrx2, rrsq, area, b;
  float zres, zgrid, rsecr, rsec2r, rsec2n, rsecn, value, alpha, beta;
  float ti, tf, parea, arcsum, t, tt;  
  float dx[ICT_MAX], dy[ICT_MAX], d[ICT_MAX], dsq[ICT_MAX], inov[ICT_MAX], arcf[ICT_MAX]; 
  // int tag[ICT_MAX]; 

  int flag_go;
 
  xmin = ymin = zmin =  9999;
  xmax = ymax = zmax = -9999; 
  float rh2o = PROBE_RADIUS;

  for(i=1; i <= lastObs; i++) {
      atoms[i].rad[VAN] += PROBE_RADIUS;   // + rh2o
      radsq[i] = pow(atoms[i].rad[VAN], 2);
      if (atoms[i].rad[VAN] > rmax) rmax = atoms[i].rad[VAN];
      if(xmin > atoms[i].x) xmin = atoms[i].x;
      if(ymin > atoms[i].y) ymin = atoms[i].y;
      if(zmin > atoms[i].z) zmin = atoms[i].z;
      if(xmax < atoms[i].x) xmax = atoms[i].x;
      if(ymax < atoms[i].y) ymax = atoms[i].y;
      if(zmax < atoms[i].z) zmax = atoms[i].z;
  } 

  dmax = rmax * 2; 
  if (IACC != 0) rh2o = 0;  // IACC is defined as 1, then rh2o should be 0

  // Cubical containing the atoms are setup

  idim = (xmax - xmin) / dmax + 1;
  if ( idim < 3 ) idim = 3;
  jidim = (ymax - ymin) / dmax + 1;
  if ( jidim < 3 ) jidim = 3;
  jidim = idim * jidim;
  kjidim = (zmax - zmin) / dmax + 1;
  if ( kjidim < 3 ) kjidim = 3;
  kjidim = jidim * kjidim;

  if (kjidim > MAX_CUBES) 
      printf(">> increase M(%d)\n", MAX_CUBES);

  // prepare upto ncube(=M=1,000,000) cubes each containing uptp NAC(=N=2000) atoms
  // at line 166

  // kji is cube index

  // DO 4
  for ( iter = 1; iter <= lastObs; iter++) {
      i = (atoms[iter].x - xmin) / dmax + 1;
      j = (atoms[iter].y - ymin) / dmax;
      k = (atoms[iter].z - zmin) / dmax;
      kji = k * jidim + j * idim + i ; 
      n = itab[kji] + 1 ;
    
      if ( n >= MAX_ATOMS_IN_A_CUBE )  // =NAC
              printf(">> increase N (%d) - %d\n", MAX_ATOMS_IN_A_CUBE, n);

      itab[kji] = n;
      natm[n][kji] = iter;  // natm[51,MAX_ATOMS_IN_A_CUBE]

  // printf("natm[%d][%d]=%d   %d \n", n, kji, iter, temp);

      cube[iter] = kji;     // cube[MAX_ATOMS_IN_A_CUBE]
  } // end 4 continue

  // begin do 5
  for(iter = 1; iter <= lastObs; iter++) {
      kji = cube[iter];

      // because no input KEY field from input pdb (key 1: access, 0: contact area, opposit with IACC) 
      // if (key[iter] == 1)  continue; // go to 5 continue

      io = 0;
      area = 0.0;
      xr = atoms[iter].x;
      yr = atoms[iter].y;
      zr = atoms[iter].z;
      rr = atoms[iter].rad[VAN];
      rrx2 = rr * 2;
      rrsq = radsq[iter];

      // do 6: find the mkji cubes neighboring the kji cube
      flag_go = 0; // init flag

      for (kk=1; kk <= 3; kk++) {
          k = kk - 2;
          for (jj=1; jj <= 3; jj++) {
              j = jj - 2;
              for (i=1; i <= 3; i++)   {
                  mkji = kji + k * jidim + j * idim + i - 2; 

                  if (mkji < 0) { 
                       // printf("CONTINUE because of mkji: %d < 1 (kk,jj,i)=(%d,%d,%d), \n", mkji, kk, jj, i); 
                      continue;  
                  } else {
                      // printf("LOOP mkji: %d >= 0 (kk,jj,i)=(%d,%d,%d), \n", mkji, kk, jj, i); 
                  }

                  if (mkji > kjidim)    // exit all three for loops
                  {
                      // printf("EXIT because mkji(%d) > kjidim(%d) \n\n", mkji, kjidim);
                      flag_go = 6;  // out of do 6 loop  
                      break;
                  } 

                  nm = itab[mkji];

                  if (nm >= 1) {

                      // do 12
                      for (m=1; m <= nm; m++) {
                         // printf("> INSIDE do 12 --> m: %d\n", m);

                          in = natm[m][mkji];
//  printf("HERE natm : %d \n", natm[1][32]);
// int temp =  natm[m][mkji];

// exit(1);

                          if (in != iter) {
                              io++;
                              if ( io <= ICT_MAX ) {
                                  dx[io] = xr - atoms[in].x;
                                  dy[io] = yr - atoms[in].y;
                                  dsq[io] = pow(dx[io],2) + pow(dy[io],2);
                                  d[io] = sqrt( dsq[io] ); 
                                  inov[io] = in;
                              } else {
                                  // go to 2 and go to 5
                                  printf(">> increase ICT for atom no: %d (IR)\n", iter);
                              }  
                          }  

                      }
                  } // end of if


              }         
              if(flag_go == 6) {   break;   }  // exit jj loop
          }
          if(flag_go == 6) {   break;   }  // exit kk loop

      } // end of kk for (6 continue) : from line 219


      flag_go = 0; // init flag

      //14
      if (io == 0) {

          area = PIX2 * rrx2;
      }
      else {

          //17
          nzp = 1.0 / FRAC_ERROR + 0.5;   // FRAC_ERROR = P = 0.05
          zres = rrx2 / nzp;
          zgrid = atoms[iter].z - rr - zres / 2.0;

          // section atom spheres perpendicular to the z axis
          // 9 do loop
          for (i=1; i <= nzp; i++) {
              flag_go = 0;

              zgrid += zres; 

              // find the radius of the circle of intersection of the ir sphere on the current z-plane
              rsec2r = rrsq - pow((zgrid - zr),2); 
              // rsecr  = sqrt(rsec2r);
              rsecr  = rsec2r;

              //do 34 
              float arci[ICT_MAX] = {0.0};
              karc = 0;

              //do 10
              for (j=1; j <= io; j++) {
                  in = inov[j];

                  // find radius of circle locus
                  rsec2n = radsq[in] - pow((zgrid - atoms[in].z),2);
                  if (rsec2n <= 0.0) continue;  // go to 10 continue
                  rsecn = sqrt(rsec2n);
                  
                  // find intersections of n circles with ir circles in section
                  if ( d[j] >= rsecr + rsecn ) continue; // go to 10 continue 

                  // do the circle intersect, or is one circle completely inside the other?
                  b = rsecr - rsecn;
                  if (d[j] <= fabsf(b)) { 
                      if (b <= 0.0) { 
                          //go to 9
                          flag_go = 9;
                          break; // exit 10 loop 
                      }
                      else { 
                          continue; // go to 10 continue
                      }
                  }
                   
                  // 20
                  // if the circle intersect, find the points of intersection 
                  karc++;

                  if (karc >= ICT_MAX) { 
                      printf(">> increase ICT for atom no: %d\n", iter);   
                      flag_go = 5; // go to 5 
                      break; // NEW APPEND
                  } 

                   
                  //   Initial and final arc endpoints are found for the iter circle 
                  //   intersected by a neighboring circle contained in the same plane.
                  //   The initial endpoint of the enclosed arc is stored in arci, 
                  //   and the final arc in arcf law of cosines 
                  
                  //45 
                  value = (dsq[j] + rsec2r - rsec2n) / (2.0 * d[j] * rsecr);
                  if (value > 1.0) value = 1.0;
                  if (value < -1.0) value = -1.0;
                  alpha = acos(value); ///????? function  

                  //   alpha is the angle between a line containing a point of intersection 
                  //   and the erference circle center and the line containing both circle centers
                  
                  beta = atan2(dy[j],dx[j]) + PI;  // atan2

                  // beta is the angle between the line containing both circle centers and the x-axis 
                  ti = beta - alpha;
                  tf = beta + alpha;
                  if (ti < 0.0)  ti = ti + PIX2;
                  if (tf > PIX2) tf = tf - PIX2;
                  arci[karc] = ti; 
                  if (tf < ti) {
                      // if the arc crosses zero, then it is broken into two segments
                      // the first ends at pix2 and the second begins at zero
                      arcf[karc] = PIX2;
                      karc++; 
                  } 
                  arcf[karc] = tf; 

              } // end 10 continue 

              if (flag_go == 9) continue;  
              if (flag_go == 5) break;  // to exit loop 9 and to go to 5

              // find the accessible contact surface surface area for the sphere ir on this section 
              if (karc == 0) {
                  arcsum = PIX2;   
                  // go to 25
              } else {
                  //19
                  // the arc endpoints are sorted on the value of the initial arc endpoint 
                  //sortag(arci, karc, tag);   // karc: # of instances, arci & tag : value & index array

                  int tag[karc+1]; 
                  for (k=1; k<=karc; k++) tag[k] = k;                 

                 
                 // printf(">> %d:%d )  CALL sortag(arci, karc, tag)\n", iter, i);
                 // printf("\t BF - karc:%d \n\t arci: ", karc);
                 // for (k=1; k<=karc; k++)  printf(" %d) %f ",k, arci[k]) ;
                 // printf("\n\t tag: ");
                 // for (k=1; k<=karc; k++)  printf(" %d ", tag[k]) ;
                 // printf("\n");
 
                  qsortTagIndex(arci, 1, karc, tag);

                 // printf("\n\t AF - karc:%d \n\t arci: ", karc);
                 // for (k=1; k<=karc; k++)  printf(" %d) %f ",k, arci[k]) ;
                 // printf("\n\t tag: ");
                 // for (k=1; k<=karc; k++)  printf(" %d ", tag[k]) ;
                 // printf("\n");

                  // calculate the accessible area
                  arcsum = arci[1];
                  t = arcf[tag[1]];
                  if (karc != 1) {
                      // do 27
                      for (k=2; k <= karc; k++) {
                          if (t < arci[k]) arcsum += arci[k] - t;  
                          tt = arcf[tag[k]];
                          if (tt > t) t = tt; 
                      }
                  }
                  //11
                  arcsum = arcsum + PIX2 - t;
              }  

              // The area/radius is equal to the accessible arc length x the section thickness
              //25   
              parea = arcsum * zres;

              // add the accessible area for this atom in this section to the area for this
              // atom for all the section encountered thus far
              area += parea;

          } // end 9 continue   

//          if (flag_go == 5) continue; // go to 5 loop


      } // end of 14 endif



      // scale area to vdw shell
      //18
      b = area * pow((rr - rh2o),2) / rr;
      if(b < 0.0) b = 0.0;
      atoms[iter].contactArea = b;

      // output atom identifiers and accessible contact surface area
      /*
      ib++;
      printf("%5d    %5d    %4s    %4s    %.9f \n", 
                 ib, atoms[iter].resNum, atoms[iter].resName, atoms[iter].atomName, atoms[iter].contactArea); 
      */
  } // end 5


}


// need to sort accessending order
void qsortTagIndex(arci, lowIndex, highIndex, tag)
float *arci;
int lowIndex, highIndex;
int *tag;
{
   int i,j,temp_tag;
   float temp_arci, mid_arci;  
   
   i = lowIndex;
   j = highIndex;
   mid_arci = arci[(lowIndex + highIndex) / 2];   

/*
   int k;
   printf("\n\t\t> Inside sort \n\t\t");
   for (k=lowIndex; k<=highIndex; k++)  printf(" arci(%d):%f ",k, arci[k]) ;
   printf("\n\t\t");
   for (k=lowIndex; k<=highIndex; k++)  printf(" tag(%d) :%d ",k, tag[k]) ;
   printf("\n");
*/

   do {
       while (arci[i] < mid_arci) i++;
       while (arci[j] > mid_arci) j--;

       if(i <= j) {
           temp_arci = arci[i];
           arci[i]   = arci[j];
           arci[j]   = temp_arci;
           temp_tag  = tag[i];
           tag[i]    = tag[j];
           tag[j]    = temp_tag;
           i++;
           j--; 
       }
   } while(i <= j);

   if(lowIndex < j) qsortTagIndex(arci, lowIndex, j, tag);
   if(i < highIndex) qsortTagIndex(arci, i, highIndex, tag);

}

