/*!
  K M Briggs 2004 Oct 24
  TODO:
*/

#define TIMING

#include "fps.h"

const char* fps_version="fps compiled" " "__DATE__" "__TIME__;

#ifdef TIMING
#include <unistd.h>
#include <sys/times.h>
#endif

int min(int x, int y) {
  return x<y?x:y;
}

void fps_timing(int k) {
  #ifdef TIMING
  long tps=0;
  struct tms t;
  clock_t t0=0,t1=0;
  if (k==0) { /* first call */
    tps=sysconf(_SC_CLK_TCK); // ticks per second
    fprintf(stderr,"%ld ticks per second\n",tps);
    times(&t);
    t0=t.tms_utime;
    fprintf(stderr,"start: t0=%ld\n",t0);
    return;
  }
  times(&t);
  t1=t.tms_utime;
  fprintf(stderr,"end: t1=%ld\n",t1);
  fprintf(stderr,"%f seconds user time elapsed\n",(double)(t1-t0)/tps);
  #endif
  return;
}

//struct _fps {
//  scalar *f;
//  int n;
//};

/*!
  initialize an fps with space for terms 0..n
*/
fps_t fps_init(int n) {
  int i;
  struct _fps* f=malloc(sizeof(struct _fps));
  f->f=malloc((n+1)*sizeof(scalar));
  f->n=n;
  for (i=0; i<=n; i++) {
    scalar_init(f->f[i]);
  }
  return f;
}

void fps_zero(fps_t f) {
  int i;
  for (i=0; i<=f->n; i++) {
    scalar_set_si(f->f[i],0,1);
  }
}

void fps_clear(fps_t f) {
  int i;
  for (i=f->n; i>=0; i--) {
    scalar_clear(f->f[i]);
  }
  free(f->f);
}

void fps_setterm_z(fps_t f, const int i, mpz_t x) {
  mpz_set(mpq_numref(f->f[i]),x);
  mpz_set_ui(mpq_denref(f->f[i]),1);
}

void fps_setterm_q(fps_t f, const int i, long fin, long fid) {
  scalar_set_si(f->f[i],fin,fid);
}

void fps_setterm_si(fps_t z, const int i, int w) {
  scalar_set_si(z->f[i],w,1);
}

void fps_setterm(fps_t z, const int i, scalar w) {
  scalar_set(z->f[i],w);
}

void fps_set(fps_t h, fps_t f) {
  int i;
  for (i=0; i<=f->n; i++) {
    scalar_set(h->f[i],f->f[i]);
  }

}

void fps_getterm(scalar w, fps_t z, const int i) {
  scalar_set(w,z->f[i]);
}

unsigned int fps_len(fps_t z) {
  return z->n;
}

void fps_space(fps_t h, fps_t f, unsigned int k) { // h(x)=f(x^k)
  int i;
  fps_zero(h);
  for (i=0; i<=f->n; i++) {
    if (i*k>h->n) return;
    scalar_set(h->f[i*k],f->f[i]);
  }
}

void fps_show(fps_t f) {
  int i;
  scalar_out_str(stdout,f->f[0]);
  for (i=1; i<=f->n; i++) {
    if (scalar_cmp_zero(f->f[i])>=0) {
      printf(" +");
      scalar_out_str(stdout,f->f[i]);
    } else {
      printf(" ");
      scalar_out_str(stdout,f->f[i]);
    }
    printf("*x^%d",i);
  }
  printf("\n");
}

void fps_list(fps_t f) {
  int i;
  for (i=0; i<=f->n; i++) {
    printf("%d ",i);
    scalar_out_str(stdout,f->f[i]);
    printf("\n");
  }
}

void fps_list_f(FILE* f, fps_t x) {
  int i;
  for (i=0; i<=x->n; i++) {
    fprintf(f,"%d ",i);
    scalar_out_str(f,x->f[i]);
    fprintf(f,"\n");
  }
}

void fps_list_egf(fps_t g) { // list k!*f[k]
  int i;
  scalar f,q,u;
  scalar_init(q);
  scalar_init(f);
  scalar_init(u);
  scalar_set_ui(f,1,1);
  for (i=0; i<=g->n; i++) {
    printf("%d ",i);
    scalar_mul(q,f,g->f[i]);
    scalar_out_str(stdout,q);
    printf(" /%d!\n",i);
    if (i<g->n) { // i! for next term
      scalar_set_ui(u,i+1,1);
      scalar_mul(f,f,u);
    }
  }
  scalar_init(u);
  scalar_init(f);
  scalar_init(q);
}

void fps_egf_to_ogf(fps_t h, fps_t g) { // h[k]=k!*g[k]
  int i;
  scalar f,q,u;
  scalar_init(q);
  scalar_init(f);
  scalar_init(u);
  scalar_set_ui(f,1,1);
  for (i=0; i<=g->n; i++) {
    scalar_mul(h->f[i],f,g->f[i]);
    if (i<g->n) { // i! for next term
      //scalar_set_ui(u,i+1,1); scalar_mul(f,f,u);
      scalar_mulby_ui(f,i+1)
    }
  }
  scalar_init(u);
  scalar_init(f);
  scalar_init(q);
}

void fps_neg(fps_t h,fps_t f) {
  // h=-f, assumes h is allocated
  int i;
  for (i=0; i<=f->n; i++) {
    scalar_neg(h->f[i],f->f[i]);
  }
}

void fps_add(fps_t h,fps_t f,fps_t g) {
  // h=f+g, assumes h is allocated
  int i,n;
  n=min(f->n,g->n);
  for (i=0; i<=n; i++) {
    scalar_add(h->f[i],f->f[i],g->f[i]);
  }
}

void fps_sub(fps_t h,fps_t f,fps_t g) {
  // h=f-g, assumes h is allocated
  int i,n;
  n=min(f->n,g->n);
  for (i=0; i<=n; i++) {
    scalar_sub(h->f[i],f->f[i],g->f[i]);
  }
}

/*!
  h=f*g, assumes h is allocated
  Output may be input
*/
void fps_mul(fps_t h,fps_t f,fps_t g) {
  int i,k,n;
  scalar t;
  scalar_init(t);
  n=min(f->n,g->n); // FIXME check h allocation, realloc if needed
  for (i=n; i>=0; i--) {
    scalar_set_zero(h->f[i]);
    for (k=0; k<=i; k++) {
      scalar_mul(t,f->f[k],g->f[i-k]);
      scalar_add(h->f[i],h->f[i],t);
    }
  }
  scalar_clear(t);
}

/*!
  h=f*g, assumes h is allocated
  Kartasuba
  Output may NOT be input
*/
void _add(scalar *h,scalar *f,scalar *g,int n,scalar t) { // low-level add
  int i;
  for (i=n; i>=0; i--) {
    scalar_add(h[i],f[i],g[i]);
  }
  return;
}

void _mul(scalar *h,scalar *f,scalar *g,int n,scalar t) { // low-level mul
  int i,k;
  for (i=n; i>=0; i--) {
    scalar_set_zero(h[i]);
    for (k=0; k<=i; k++) {
      scalar_mul(t,f[k],g[i-k]);
      scalar_add(h[i],h[i],t);
    }
  }
  return;
}

void fps_mul_karatsuba(fps_t h,fps_t f,fps_t g) {
  int i,k,n,fn=f->n,gn=g->n;
  scalar t;
  fps_t f0g1,f1g0,f1g1
  n=min(f->n,g->n); // FIXME check h allocation, realloc if needed
  if (n<6) {
    fps_mul(h,f,g);
    return;
  }
  scalar_init(t);
  k=n/2+1;
  f0g1=fps_init(k+1);
  f1g0=fps_init(k+1);
  f1g1=fps_init(k+1);
  _mul(h->f,f->f,g->f,k,t);  // f0*g0
  _mul(f1g1->f,f->f+k+1,g->f+k+1,n-k,t);  // f1*g1
  _add(f0g1->f,f->f,g->f+k+1,n-k,t);  // f0+g1
  _add(f1g0->f,f->f+k+1,g->f,n-k,t);  // f1+g0
  scalar_clear(t);
}

void fps_div_i(fps_t h,fps_t f,int k) { // FIXME wrong???
  int i,n;
  n=min(h->n,f->n);
  for (i=0; i<=n; i++) {
    scalar_div_si(h->f[i],f->f[i],k)
  }
}

/*!
  h=f/g, assumes h is allocated
  Output may NOT be input
*/
void fps_div(fps_t h,fps_t f,fps_t g) {
  int i,k,n;
  scalar s,t;
  scalar_init(s);
  scalar_init(t);
  n=min(f->n,g->n);
  for (i=0; i<=n; i++) {
    scalar_set_zero(s);
    for (k=0; k<i; k++) {
      scalar_mul(t,h->f[k],g->f[i-k]);
      scalar_add(s,s,t);
    }
    scalar_sub(s,f->f[i],s);
    scalar_div(h->f[i],s,g->f[0]);
  }
  scalar_clear(t);
  scalar_clear(s);
}

void fps_mul_q(fps_t h,fps_t f,int a,int b) {
  // h=(a/b)*f, assumes h is allocated
  int i;
  scalar t;
  scalar_init(t);
  scalar_set_si(t,a,b);
  for (i=0; i<=f->n; i++) {
    scalar_mul(h->f[i],f->f[i],t);
  }
  scalar_clear(t);
}

void fps_mul_si(fps_t h,fps_t f,int a) {
  // h=(a)*f, assumes h is allocated
  int i;
  scalar t;
  scalar_init(t);
  scalar_set_si(t,a,1);
  for (i=0; i<=f->n; i++) {
    scalar_mul(h->f[i],f->f[i],t);
  }
  scalar_clear(t);
}

void fps_div_ui(fps_t h,fps_t f,unsigned int a) {
  // h=f/a, assumes h is allocated
  int i;
  scalar t;
  scalar_init(t);
  scalar_set_ui(t,a,1);
  for (i=0; i<=f->n; i++) {
    scalar_div(h->f[i],f->f[i],t);
  }
  scalar_clear(t);
}

void fps_div_si(fps_t h,fps_t f,int a) {
  // h=f/a, assumes h is allocated
  int i;
  scalar t;
  scalar_init(t);
  scalar_set_si(t,a,1);
  for (i=0; i<=f->n; i++) {
    scalar_div(h->f[i],f->f[i],t);
  }
  scalar_clear(t);
}

void fps_divexact_ui(fps_t h,fps_t f,unsigned int a) {
  // h=f/a, assumes h is allocated
  int i;
  for (i=0; i<=f->n; i++) {
    scalar_divexact_ui(h->f[i],f->f[i],a);
  }
}

void fps_powi(fps_t h,fps_t f,int a) {
  // h=f^a, assumes h is allocated
  // needs f[0]==1
  int i,k,n=f->n;
  scalar s,t,u;
  if (scalar_cmp_one(f->f[0])!=0) {
    printf("f[0]!=1 in fps_pow!\n");
    exit(1);
  }
  scalar_init(s);
  scalar_init(t);
  scalar_init(u);
  fps_setterm_si(h,0,1);
  for (i=1; i<=n; i++) {
    scalar_set_si(s,0,1);
    for (k=1; k<=i; k++) {
      scalar_mul(t,f->f[k],h->f[i-k]);
      scalar_set_si(u,(a+1)*k-i,1);
      scalar_mul(t,t,u);
      scalar_add(s,s,t);
    }
    scalar_set_ui(u,1,i);
    scalar_mul(h->f[i],s,u);
  }
  scalar_clear(u);
  scalar_clear(t);
  scalar_clear(s);
}

void fps_powq(fps_t h,fps_t f,scalar a) {
  // h=f^a, assumes h is allocated
  // needs f[0]==1
  int i,k,n=f->n;
  scalar qi,ap1,s,t,u;
  if (scalar_cmp_one(f->f[0])!=0) {
    printf("f[0]!=1 in fps_pow!\n");
    exit(1);
  }
  scalar_init(qi);
  scalar_init(s);
  scalar_init(t);
  scalar_init(u);
  scalar_init(ap1);
  scalar_set(ap1,a);
  scalar_pp(ap1);
  fps_setterm_si(h,0,1);
  for (i=1; i<=n; i++) {
    scalar_set_ui(qi,i,1);
    scalar_sub(u,ap1,qi);
    scalar_set_si(s,0,1);
    for (k=1; k<=i; k++) {
      scalar_mul(t,f->f[k],h->f[i-k]);
      scalar_mul(t,t,u);
      scalar_add(s,s,t);
      if (k<i) scalar_add(u,u,ap1);
    }
    scalar_set_ui(u,1,i);
    scalar_mul(h->f[i],s,u);
  }
  scalar_clear(u);
  scalar_clear(t);
  scalar_clear(s);
  scalar_clear(ap1);
  scalar_clear(qi);
}

void fps_log(fps_t w, fps_t v) { // FIXME w==v case
  // w=log(v), assumes v(0)==1
  int i,k,n=v->n;
  scalar s,t,u;
  if (scalar_cmp_one(v->f[0])!=0) {
    printf("f[0]!=1 in fps_log!\n");
    exit(1);
  }
  scalar_init(s);
  scalar_init(t);
  scalar_init(u);
  for (k=1; k<=n; k++) {
    scalar_set_ui(t,k,1);
    scalar_mul(s,t,v->f[k]);
    for (i=1; i<k; i++) {
      scalar_mul(t,w->f[k-i],v->f[i]);
      scalar_set_si(u,i-k,1); scalar_mul(t,t,u);
      scalar_add(s,s,t);
      // or...
      //mpz_mul_ui(scalar_numref(t),scalar_numref(t),k-i); // FIXME canonicalize?
      //scalar_sub(s,s,t);
    }
    //scalar_set_ui(u,1,k); scalar_mul(w->f[k],s,u);
    scalar_div_ui(w->f[k],s,k);
  }
  scalar_clear(u);
  scalar_clear(t);
  scalar_clear(s);
  //fps_set_z(w,0,0);
  fps_setterm_si(w,0,0);
}

void fps_log_1mfxk(fps_t w, fps_t v, unsigned k) {
  // w=log(1-v(x^k)), quietly assumes v(0)==1
  int i,j,n=v->n;
  scalar s,t,u;
  scalar_init(s);
  scalar_init(t);
  scalar_init(u);
  for (j=1; j<=n;j++) {
    if (j%k==0) {
      scalar_set_si(t,-j,1);
      scalar_mul(s,t,v->f[j/k]);
    } else 
      scalar_set_zero(s);
    for (i=k; i<j; i+=k) {
      scalar_mul(t,w->f[j-i],v->f[i/k]);
      scalar_set_si(u,j-i,1);
      scalar_mul(t,t,u);
      scalar_add(s,s,t);
    }
    scalar_set_ui(u,1,j);
    scalar_mul(w->f[j],s,u);
  }
  scalar_clear(u);
  scalar_clear(t);
  scalar_clear(s);
  fps_setterm_si(w,0,0);
}

void fps_set_T(fps_t T, int n) {
  // egf for rooted labelled trees
  //> lprint(series(sum(n^(n-1)*z^n/n!,n=1..infinity),z=0,10));
  //series(1*z+1*z^2+3/2*z^3+8/3*z^4+125/24*z^5+54/5*z^6+16807/720*z^7+16384/315*z^8+531441/4480*z^9+O(z^10),z,10)
  int i;
  mpz_t t,f;
  scalar q;
  mpz_init_set_ui(f,1);
  scalar_init(q);
  mpz_init(t);
  for (i=0; i<=n; i++) {
    mpz_set_ui(t,i);
    mpz_pow_ui(t,t,i-1);
    //scalar_set_num(T->f[i],t);
    //scalar_set_den(T->f[i],f);
    //scalar_canonicalize(T->f[i]);
    scalar_set_zonz(T->f[i],t,f);
    if (i<n) mpz_mul_ui(f,f,i+1); // i! for next term
  }
  mpz_clear(f);
  mpz_clear(t);
  scalar_clear(q);
}

void fps_set_Wm1(fps_t Wm1, int n) {
  // egf for unrooted labelled trees
  // Wm1[i]=i^(i-2)/i!
  // See notes at top of this file
  int i;
  mpz_t t,f;
  scalar q;
  mpz_init_set_ui(f,1);
  scalar_init(q);
  mpz_init(t);
  for (i=0; i<=n; i++) {
    mpz_set_ui(t,i);
    mpz_pow_ui(t,t,i-2);
    //scalar_set_num(Wm1->f[i],t);
    //scalar_set_den(Wm1->f[i],f);
    //scalar_canonicalize(Wm1->f[i]);
    scalar_set_zonz(Wm1->f[i],t,f);
    if (i<n) mpz_mul_ui(f,f,i+1); // i! for next term
  }
  mpz_clear(f);
  mpz_clear(t);
  scalar_clear(q);
}


void fps_get_W(fps_t Wm1,fps_t W0,fps_t W1,int n) {
  // compute terms 0..n of W_{-1} and W_0
  // maple checks...
  //printf("T...\n"); fps_show(T);
  //> T:=series(sum(n^(n-1)*z^n/n!,n=1..infinity),z=0,10);
  //> lprint(series(T-T^2/2,z));
  //series(1*z+1/2*z^2+1/2*z^3+2/3*z^4+25/24*z^5+O(z^6),z,6)
  //> lprint(series(T-T^2/2,z=0,10));
  //series(1*z+1/2*z^2+1/2*z^3+2/3*z^4+25/24*z^5+9/5*z^6+2401/720*z^7+2048/315*z^8+59049/4480*z^9+O(z^10),z,10)
  //printf("W_{-1}...\n"); fps_show(Wm1); fps_list(Wm1);
  //> lprint(series(-log(1-T)/2-T/2-T^2/4,z=0,10));
  //series(1/6*z^3+5/8*z^4+37/20*z^5+61/12*z^6+4553/336*z^7+8551/240*z^8+26809/288*z^9+O(z^10),z,10)
  fps_t t1,t2,t3,t4,T,T2;
  t1=fps_init(n);
  t2=fps_init(n);
  t3=fps_init(n);
  t4=fps_init(n);
  T =fps_init(n);
  T2=fps_init(n);
  fprintf(stderr,"fps_get_W start at "); fps_timing(0);
  fps_set_T(T,n);
  fprintf(stderr,"got T after "); fps_timing(1);
  fps_set_Wm1(Wm1,n);
  // was...
  //fps_mul(T2,T,T);
  //fps_div_si(t1,T2,2);
  //fps_sub(Wm1,T,t1);   // Wm1=T-T^2/2
  fprintf(stderr,"got Wm1 after "); fps_timing(1);
  fps_mul(T2,T,T);
  fps_neg(t1,T);
  fps_setterm_si(t1,0,1);   // t1=1-T (since T[0]=0)
  fps_log(t4,t1);
  fps_div_si(t4,t4,2); // t4=log(1-T)/2
  fps_div_si(t2,T,2);  // t2=T/2
  fps_div_si(t3,T2,4); // t3=T^2/4
  fps_add(t1,t4,t2);
  fps_add(W0,t1,t3);
  fps_neg(W0,W0);
  fprintf(stderr," got W0 after "); fps_timing(1);
  fps_mul(t1,T2,T2);    // t1=T^4
  fps_mul(t2,T,t1);     // t2=T^5
  fps_div_si(t1,t1,4);  // t1=T^4/4
  fps_div_si(t2,t2,24); // t2=T^5/24
  fps_sub(t1,t1,t2);    // t1=T^4/4-T^5/24
  fps_neg(t3,T);
  fps_setterm_si(t3,0,1);    // t3=1-T (since T[0]=0)
  fps_powi(t4,t3,3);
  // ...or fps_mul(t2,t3,t3); fps_mul(t4,t2,t3);
  fps_div(W1,t1,t4);    // W1=(T^4/4-T^5/24)/(1-T)^3
  fprintf(stderr," got W1 after "); fps_timing(1);
  fps_clear(T2);
  fps_clear(T);
  fps_clear(t4);
  fps_clear(t3);
  fps_clear(t2);
  fps_clear(t1);
}

