#include "common.h"

/* `vector_fprint(fp, x)` prints to file `fp`
 * the components of a gsl_vector.
 */
void vector_fprint(FILE *fp, const gsl_vector *x) {
  fprintf(fp, "[");
  for (size_t i = 0; i < x->size; i++) {
    fprintf(fp, i < x->size -1 ? "%10.4g, " : "%10.4g",
            gsl_vector_get(x, i));
  }
  fprintf(fp, "]\n");
}


/* `matrix_fprint(fp, x)` prints to file `fp`
 * the components of a gsl_vector.
 */
void matrix_fprint(FILE *fp, const gsl_matrix *x) {
  gsl_vector row;
  for (size_t i = 0; i < x->size1; i++) {
    row = gsl_matrix_const_row(x, i).vector;
    fputs("    ", fp);
    vector_fprint(fp, &row);
  }
}


/* `vector_map(f, v)` applies the function
 * `f` to each element of the vector `v`, inplace.
 * The function return a GSL exit code to either
 * report success or a failure.
 */
int vector_map(double (*f)(double x), gsl_vector *v) {
  double y;
  for (size_t i = 0; i < v->size; i++ ) {
    y = (*f)(gsl_vector_get(v, i));
    gsl_vector_set(v, i, y);
  }
  return GSL_SUCCESS;
}


/* Angular distribution function F(θ,φ; α,β,γ)
 * Takes three parameters α,β,γ and the angles θ,φ.
 */
double distr(const gsl_vector *par,
             const struct event *event){
  double a = gsl_vector_get(par, 0),
         b = gsl_vector_get(par, 1),
         c = gsl_vector_get(par, 2);

  double th = event->th,
         ph = event->ph;

  double A = 3 /(4 * M_PI);
  double B = 0.5 * (1 - a);
  double C = 0.5 * (3 * a - 1) * pow(cos(th), 2);
  double D = b * pow(sin(th), 2) * cos(2 * ph);
  double E = sqrt(2) * c * sin(2 * th) * cos(ph);

  return A * (B + C - D - E);
}

/* `grad_distr(par, event, grad)`
 * computes the gradient of F(α,β,γ; α,β) as:
 *
 * ⎡ 3/2⋅cos²(θ) - 1/2 ⎤
 * ⎢                   ⎥ 
 * ⎢  sin²(θ)⋅cos(2φ)  ⎥ 3/(4π) 
 * ⎢                   ⎥
 * ⎣ √2⋅sin(2θ)⋅cos(φ) ⎦
 *
 * where `par` is [α,β,γ], `event` is the angle
 * structure. The results is stored in the
 * gsl_vector `grad`
 */
void grad_distr(const gsl_vector *par,
                const struct event *event,
                gsl_vector *grad) {
  double th = event->th,
         ph = event->ph;

  gsl_vector_set(grad, 0, 1.5*pow(cos(th), 2) - 0.5);
  gsl_vector_set(grad, 1, -pow(sin(th), 2) * cos(2*ph));
  gsl_vector_set(grad, 2, -sqrt(2)*sin(2*th) * cos(ph));
  gsl_vector_scale(grad, 3.0/(4.0 * M_PI));
}


/* Test the compatibility between the original
 * parameters `def` and the estimates `est.par`
 * with standard errors `est.err`.
 */
void compatibility(gsl_vector *def,
            min_result est) {
 
  char* names[] = { "α", "β", "γ" };
  double a, b;
  double t, sigma, alpha;

  fprintf(stderr, "| par | σ        | α-value  |\n");
  fprintf(stderr, "|-----|----------|----------|\n");
  for (size_t i = 0; i < def->size; i++) {
    // discrepancy
    a = gsl_vector_get(def, i);
    b = gsl_vector_get(est.par, i);
    sigma = gsl_vector_get(est.err, i);

    // normal t-statistic
    t = fabs(a - b)/sigma;
    // α-value / significance
    alpha = 1 - erf(t/sqrt(2));

    fprintf(stderr, "|  %s  | %-8.2g | %-8.2g |\n",
            names[i], sigma, alpha);
  }
}


/* Free a min_result structure.
 */
void min_result_free(min_result x) {
  gsl_vector_free(x.par);
  gsl_matrix_free(x.cov);
}
