// $CVSHeader: pal8e/src/helixbrk.c,v 1.5 2005/02/15 01:10:26 indraneel Rel $

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "nick_e.h"
#include "helixbrk.h"
#include "helixbrk_e.h"

#include <Python.h>

//#ifdef undef
void debugmsg(char *message, int priority)
{
  switch (priority){
  case 1: fprintf(stderr, "\033[32m"); break;
  case 2: fprintf(stderr, "\033[34m"); break;
  case 3: fprintf(stderr, "\033[31m"); break;
  default: break;
  }
  fprintf(stderr, "%s", message);
  fprintf(stderr, "\033[0m\n");
}
//#endif

/* arguments are data, datapoint counter, dimensions (3 only), point 'p'
   on the line, unit vector 'u' of the line. */
static double rmsdcalc(double *data, int dp, int dim,
		       double px, double py, double pz,
		       double ui, double uj, double uk)
{
  int i;
  double dsqr = 0;

  // for each data point
  for(i = 0; i < dp; ++i){
    double qx, qy, qz;

    // get the x y z for the point
    qx = *(data + (i * dim));
    qy = *(data + (i * dim) + 1);
    qz = *(data + (i * dim) + 2);
    // calculate the vector from given point p
    qx -= px;
    qy -= py;
    qz -= pz;
    // now find cross product between q and u
    /* i  j  k
       qx qy qz
       ui uj uk */
    {
      double crossx, crossy, crossz;

      crossx = (qy * uk) - (qz * uj);
      crossy = (qz * ui) - (qx * uk);
      crossz = (qx * uj) - (qy * ui);
      // update dsqr
      dsqr += (crossx * crossx) + (crossy * crossy) + (crossz * crossz);
    }
  }
  // mean dsqr
  dsqr /= dp;
  return sqrt(dsqr);
}

/* run rotFit and calculate rmsd from result */
static double rotrmsdcalc(double *data, int dp, int dim, VECTOR *v)
{
  double ui, uj, uk;

  // get the axis
  rotFit(data, dp, dim, v);
  {
    double m;

    // get the unit vector
    ui = v->endPoint.xcoord - v->initPoint.xcoord;
    uj = v->endPoint.ycoord - v->initPoint.ycoord;
    uk = v->endPoint.zcoord - v->initPoint.zcoord;
    m = sqrt ( (ui * ui) + (uj * uj) + (uk * uk) );
    ui /= m;
    uj /= m;
    uk /= m;
  }
  return rmsdcalc(data, dp, dim, v->initPoint.xcoord,
		  v->initPoint.ycoord, v->initPoint.zcoord,
		  ui, uj, uk );
}

static void result_cbn_init(double *data, int dp, struct result_cbn *cbn)
{
  int dim=3;
  VECTOR v;

  cbn->frags = 1;
  cbn->dp = dp;
  cbn->start[0] = data;
  cbn->len[0] = dp;
  cbn->overlap[0] = 0;
  cbn->angle[0] = 0;
  cbn->rmsd[0] = rotrmsdcalc ( data, dp, dim, &v );
  cbn->ui[0] = v.endPoint.xcoord - v.initPoint.xcoord;
  cbn->uj[0] = v.endPoint.ycoord - v.initPoint.ycoord;
  cbn->uk[0] = v.endPoint.zcoord - v.initPoint.zcoord;
  cbn->startpos[0] = 0;
  cbn->endpos[0] = dp/dim;
}

static double avrmsd(int hlen){
  return (avline_m * hlen) + avline_c;
}

static double avsigma(int hlen){
  return (sigline_m * hlen) + sigline_c;
}

static double getzee(double rmsd, int hlen)
{
  double rav, sigma, zee;
  rav = avrmsd(hlen);
  sigma = avsigma(hlen);
  zee = (rmsd - rav) / sigma;
  return zee;
}

/* return 0 if all zee scores pass cutoff, else return 1 */
static int checkzee(struct result_cbn *cbn)
{
  int i;

  for(i = 0; i < cbn->frags; ++i){
    if(getzee(cbn->rmsd[i], cbn->len[i]) > ZEE_CUTOFF) return 1;
  }
  return 0;
}

// check if total length equals total overlapping fragments
static int chklen(struct result_cbn *cbn)
{
  int i, len=0;

  for(i = 0; i < cbn->frags; ++i){
    len += cbn->len[i];
    if(i > 0) len -= cbn->overlap[i-1];
  }
  return(len - cbn->dp);
}

static void setfragrmsd(struct result_cbn *cbn, int frag)
{
  int dim=3;
  VECTOR v;

  cbn->rmsd[frag] = rotrmsdcalc(cbn->start[frag],
				cbn->len[frag], dim, &v);
  cbn->ui[frag] = v.endPoint.xcoord - v.initPoint.xcoord;
  cbn->uj[frag] = v.endPoint.ycoord - v.initPoint.ycoord;
  cbn->uk[frag] = v.endPoint.zcoord - v.initPoint.zcoord;
}

static void bsort(double *data, int ctr)
{
  int i, j;
  double buff;

  for(i = 0; i < ctr-1; ++i){
    for(j = 0; j < ctr-1-i; ++j){
      if(data[j+1] > data[j]){
	buff = data[j];
	data[j] = data[j+1];
	data[j+1] = buff;
      }
    }
  }
}

// check if new storage has a better rmax profile
static int recursermax(struct result_cbn *oldcbn,
		       struct result_cbn *newcbn )
{
  double *oldrmsd, *newrmsd;
  int ctr, i, retval=0;

  ctr = oldcbn->frags;
  oldrmsd = (double *)malloc ( ctr * sizeof ( double ) );
  newrmsd = (double *)malloc ( ctr * sizeof ( double ) );
  // copy the values
  for(i = 0; i < ctr; ++i){
    oldrmsd[i] = oldcbn->rmsd[i];
    newrmsd[i] = newcbn->rmsd[i];
  }
  // sort the two sides and compare one to one
  // using bubble sort (eeek!) (mostly <5 values)
  bsort(oldrmsd, ctr);
  bsort(newrmsd, ctr);
  // comparing
  for(i = 0; i < ctr; ++i){
    if(newrmsd[i] < oldrmsd[i]){
      retval = 1;
      break;
    } else if(oldrmsd[i] < newrmsd[i]){
      retval = 0;
      break;
    }
  }
  free(oldrmsd);
  free(newrmsd);
  return retval;
}

static void copy_result_cbn(struct result_cbn *from,
			    struct result_cbn *to)
{
  int i;

  to->frags = from->frags;
  to->dp = from->dp;
  for(i = 0; i < to->frags; ++i){
    to->start[i] = from->start[i];
    to->len[i] = from->len[i];
    if(i > 0)
      to->overlap[i-1] = from->overlap[i-1];
    to->ui[i] = from->ui[i];
    to->uj[i] = from->uj[i];
    to->uk[i] = from->uk[i];
    to->rmsd[i] = from->rmsd[i];
    if(i > 0)
      to->angle[i-1] = from->angle[i-1];
    to->startpos[i] = from->startpos[i];
    to->endpos[i] = from->endpos[i];
  }
}

static void minrmax(struct result_cbn *cbn, int ctrl)
{
  // ctrl means these
  const int RET_RES = 0;
  const int PROCESS = 1;
  const int NEW_LOOP = 2;

  static struct result_cbn stcbn;

  if(ctrl == NEW_LOOP){
    int i;

    stcbn.frags = cbn->frags;
    for(i = 0; i < stcbn.frags; ++i){
      stcbn.rmsd[i] = init;
    }
  }
  else if(ctrl == PROCESS){
    if(recursermax(&stcbn, cbn))
      copy_result_cbn(cbn, &stcbn);
  }
  else if(ctrl == RET_RES){
    copy_result_cbn(&stcbn, cbn);
  }
}

static void recursebrks(struct result_cbn *cbn, int brks)
{
  int overlap, len, dim=3;

  for(overlap = 0; overlap < 3; ++overlap){
    /* check if previous fragment has been defined
       'yes' means this is not top level of recursion */
    if(brks < (cbn->frags - 1)){
      // set start of fragment
      cbn->start[brks] = cbn->start[brks + 1] +
	(cbn->len[brks + 1] - overlap) * dim;
      cbn->overlap[brks] = overlap;
    }
    /* for topmost level of recursion. Do not loop on overlap */
    else if(overlap != 0) return;
    /* now loop over fragment length. */
    for(len = 5; len <= cbn->dp; ++len){
      cbn->len[brks] = len;
      {
	/* checks based on length */
	int chkpt = chklen(cbn);

	if(chkpt > 0) break;
	else if((chkpt == 0) && (brks > 0)) break;
	else if((chkpt < 0) && (brks == 0)){
	  len -= chkpt;
	  cbn->len[brks] = len;
	}
      }
      // set current rmsd and store vector values
      setfragrmsd(cbn, brks);
      if(brks > 0) recursebrks(cbn, brks - 1);
      // minimise rmax
      else minrmax(cbn, 1);
    }
    cbn->len[brks] = 0;
    cbn->overlap[brks] = 0;
  }
}

/* a.b = |a||b|cos_theta */
static double uvecangle(double i1, double j1, double k1,
			double i2, double j2, double k2)
{
  double dot, m1, m2, cos_theta, theta;

  dot = (i1 * i2) + (j1 * j2) + (k1 * k2);
  m1 = sqrt((i1 * i1) + (j1 * j1) + (k1 * k1));
  m2 = sqrt((i2 * i2) + (j2 * j2) + (k2 * k2));
  cos_theta = dot / (m1 * m2);
  /* remove floating errors */
  if(cos_theta > 1)
    cos_theta = 1;
  if(cos_theta < -1)
    cos_theta = -1;
  theta = fabs(acos(cos_theta) / M_PI * 180);
  /* theta from 0 to 90 */
  if(theta > 90) theta = 180 - theta;
  return theta;
}

static void anglegen(struct result_cbn *cbn)
{
  int i;

  // generate the angles
  for(i = 1; i < cbn->frags; ++i){
    cbn->angle[i-1] = uvecangle(cbn->ui[i-1], cbn->uj[i-1], cbn->uk[i-1],
				cbn->ui[i], cbn->uj[i], cbn->uk[i]);
  }
  cbn->angle[cbn->frags - 1] = 1818181.0;
}

//#ifdef undef
static void debugcbn(struct result_cbn *cbn, int frag)
{
  fprintf(stderr, "%d\t", frag);
  fprintf(stderr, "%d\t", cbn->len[frag]);
  fprintf(stderr, "%d\t", cbn->overlap[frag]);
  fprintf(stderr, "%f\t", cbn->rmsd[frag]);
  fprintf(stderr, "%f\t", getzee(cbn->rmsd[frag], cbn->len[frag]));
  fprintf(stderr, "%f\n", cbn->angle[frag]);
}
//#endif

static void checkangle(struct result_cbn *cbn)
{
  int i, j=0;
  struct result_cbn cbnbuff;

  if(cbn->frags == 1) return;
  /* reformat fragments and copy to buffer
     only when angle fails breakpoint */
  cbnbuff.dp = cbn->dp;
  cbnbuff.start[j] = cbn->start[cbn->frags - 1];
  cbnbuff.len[j] = cbn->len[cbn->frags - 1];
  cbnbuff.overlap[j] = 0;
  for(i = (cbn->frags - 2); i >= 0; --i){
    if(cbn->angle[i] > ANG_CUTOFF){
      ++j;
      cbnbuff.start[j] = cbn->start[i];
      cbnbuff.len[j] = cbn->len[i];
      cbnbuff.overlap[j] = cbn->overlap[i];
    }
    else {
      cbnbuff.len[j] += cbn->len[i] - cbn->overlap[i];
    }
  }
  cbnbuff.frags = j+1;
  if(cbnbuff.frags > 1){
    // debugmsg("Broken helix, angle cutoff", 1);
  }
  // recalculate unit vectors, rmsd, angle (probably not done)
  copy_result_cbn(&cbnbuff, cbn);
}

static void
position_brks(double *data, struct result_cbn *cbn)
{
  int i, j=0, dim=3;

  if(cbn->frags == 1) return;
  for(i = 0; i < cbn->frags; ++i){
    while((data + (j * dim)) != cbn->start[i]) ++j;
    cbn->startpos[i] = j;
    cbn->endpos[i] = j + cbn->len[i] - 1;
  }
}

//#ifdef undef
static void print_brks(struct result_cbn *cbn)
{
  int i;

  if(cbn->frags == 1) return;
  for(i = 0; i < cbn->frags; ++i){
    printf("%d %d\t", cbn->startpos[i], cbn->len[i]);
  }
}
//#endif

static struct helices *
packed_helices(struct result_cbn *cbn)
{
  int i;
  struct helices *hel;

  hel = malloc(sizeof(struct helices));
  hel->start = malloc(cbn->frags * sizeof(int));
  hel->end = malloc(cbn->frags * sizeof(int));
  hel->rmsd = malloc(cbn->frags * sizeof(double));
  hel->angle = malloc(cbn->frags * sizeof(double));
  hel->len = malloc(cbn->frags * sizeof(int));
  hel->count = cbn->frags;
  for(i=0; i < hel->count; ++i){
    hel->start[i] = cbn->startpos[i];
    hel->end[i] = cbn->endpos[i];
    hel->rmsd[i] = cbn->rmsd[i];
    hel->angle[i] = cbn->angle[i];
    hel->len[i] = cbn->len[i];
  }
  return hel;
}

struct helices *stat_helix(double *data, int dp)
{
  int brks=0, i, retval=0;
  struct result_cbn cbn;

  if(dp > 50){
    debugmsg("Helix over 50 residue length: not processed", 2);
    return NULL;
  }
  result_cbn_init(data, dp, &cbn);
  while(1){
    ++brks;
    if ((((float)dp)/brks) < 5.5)
      break;
    cbn.frags = brks + 1;
    // set initial lengths to 0
    for ( i = 0; i < cbn.frags; ++i ){
      cbn.len[i] = 0;
    }
    cbn.start[brks] = data;
    minrmax(&cbn, 2);  // initialise rmsd value
    recursebrks(&cbn, brks);
    minrmax(&cbn, 0);
    anglegen(&cbn);
    for(i = 0; i < cbn.frags; ++i){
      debugcbn(&cbn, i);
    }
    printf("done %d\n", dp);
  }
  position_brks(data, &cbn);
  //print_brks(&cbn);
  return packed_helices(&cbn);
}

struct helices *chop_helix(double *data, int dp)
{
  int brks=0, i, retval=0;
  struct result_cbn cbn;

  if(dp > 50){
    // debugmsg("Helix over 50 residue length: not processed", 2);
    return NULL;
  }
  result_cbn_init(data, dp, &cbn);
  while(checkzee(&cbn)){
    ++brks;
    cbn.frags = brks + 1;
    // set initial lengths to 0
    for ( i = 0; i < cbn.frags; ++i ){
      cbn.len[i] = 0;
    }
    cbn.start[brks] = data;
    minrmax(&cbn, 2);  // initialise rmsd value
    recursebrks(&cbn, brks);
    minrmax(&cbn, 0);
    if(brks == (int)((cbn.dp - 5) / 3)){
      // debugmsg("Lo-zee fragments too small, keeping original input", 2);
      result_cbn_init(data, dp, &cbn);
      retval = 1;
      break;
    }
    if(brks >= maxbrks){
      // debugmsg("Reached maxbrks, keeping original input", 2);
      result_cbn_init(data, dp, &cbn);
      retval = 1;
      break;
    }
  }
  anglegen(&cbn);
  //if(cbn.frags > 1) debugmsg("Multiple fragments, zee cutoff", 1);
  for(i = 0; i < cbn.frags; ++i){
    //debugcbn(&cbn, i);
  }
  checkangle(&cbn);
  position_brks(data, &cbn);
  //print_brks(&cbn);
  if(cbn.frags == 1)
    //// modified for statistics calculation -- change back to 1
    //// rmsd value incorrect for frags > 1
    //// running rotrmsd on cbn fragments recommended before return
    //retval = 1;
    retval = 0;

  if(retval)
    return NULL;
  else return packed_helices(&cbn);
}

#ifdef Py_PYTHON_H

/* mod fns */

/* splithelix (argument is a list of doubles (x,y,z,x,y,z,x,...))
return value is a list of dicts with keys 'start' and 'end' */
static PyObject *
splithelix(PyObject *self, PyObject *args)
{
  int i, dp, dim=3;
  double *data;
  PyObject *pyobj, *dict, *start, *end, *rmsd, *angle, *len;
  struct helices *result;

  if (! PyArg_ParseTuple(args, "O", &pyobj))
    return NULL;
  if(! PyList_Check(pyobj))
    return NULL;

  dp = PyList_Size(pyobj) / dim;
  data = malloc(dp * dim * sizeof(double));
  if(!data) return NULL;
  for(i = 0; i < (dp*dim); ++i){
    data[i] = PyFloat_AsDouble(PyList_GetItem(pyobj, i));
  }
  //result = stat_helix(data, dp);
  result = chop_helix(data, dp);
  free(data);
  if(!result)
    return Py_None;
  pyobj = PyList_New(result->count);
  for(i = 0; i < result->count; ++i){
    dict = PyDict_New();
    start = Py_BuildValue("i", result->start[i]);
    end = Py_BuildValue("i", result->end[i]);
    rmsd = Py_BuildValue("d", result->rmsd[i]);
    angle = Py_BuildValue("d", result->angle[i]);
    len = Py_BuildValue("i", result->len[i]);
    PyDict_SetItemString(dict, "start", start);
    PyDict_SetItemString(dict, "end", end);
    PyDict_SetItemString(dict, "rmsd", rmsd);
    PyDict_SetItemString(dict, "angle", angle);
    PyDict_SetItemString(dict, "le", len);
    PyList_SetItem(pyobj, i, dict);
  }
  free(result->start);
  free(result->end);
  free(result->rmsd);
  free(result);
  return pyobj;
}

/* reg tab */
static struct PyMethodDef helixbrk_methods[] = {
  {"splithelix", splithelix, 1},
  {NULL, NULL}
};

/* mod init */
void inithelixbrk()
{
  (void) Py_InitModule("helixbrk", helixbrk_methods);
}

#endif
