/*
  Copyright(C) 2007-2012 National Institute of Information and Communications Technology
*/

/*
  svmtools
  Common module
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "exception.h"
#include "split.h"
#include "svm_common.h"
#include "svm_kernel.h"


#define BUF_SIZE (32 * 1024)


static void sortsv(SVM_SV *sv);
static int gettkn(char *buf, int size, FILE *fp);


int svm_verbose = 1;	/* 詳細な出力を行うかどうか */
int svm_invidx = 1;	/* 内積計算に転置インデックスを使用するかどうか */


/*
  訓練データの読み込み
*/
SVM_EXM *svm_readexm(FILE *fp) {
  int i;
  int buf_size;
  char *buf;
  int nelem;
  char **elem;
  int exm_size;
  SVM_EXM *exm;
  int line;
  int isbinary;
  int label;
  int sv_size;
  int sv_len;
  int sv_num;
  int *sv_idx;
  float *sv_val;
  char *s;
  int len;

  /* メモリ確保 */
  exm = smalloc(sizeof(SVM_EXM));
  exm_size = 0;
  exm->num = 0;
  exm->label = NULL;
  exm->sv = NULL;
  buf_size = BUF_SIZE;
  buf = smalloc(sizeof(char) * buf_size);
  elem = smalloc(sizeof(char *) * (buf_size / 2));
  sv_size = 0;
  sv_len = 0;
  sv_idx = NULL;
  sv_val = NULL;

  isbinary = 1;
  for (line = 1; ; line++) {
    /* ベクトルの読み込み(メモリの動的確保) */
    if (fgets(buf, buf_size, fp) == NULL) break;
    while (buf[(len = strlen(buf)) - 1] != '\n') {
      buf_size = 2 * buf_size + 1;
      buf = srealloc(buf, sizeof(char) * buf_size);
      elem = srealloc(elem, sizeof(char *) * (buf_size / 2));
      if (fgets(buf + len, buf_size - len, fp) == NULL) {
	if (svm_verbose) fprintf(stderr, "<%d: unexpected EOF> ", line);
	return NULL;
      }
    }
    buf[--len] = '\0';
    if (len > 0 && buf[len - 1] == '\r') buf[--len] = '\0';
    if (len > 0 && buf[len - 1] == ' ') buf[--len] = '\0';

    /* コメント */
    if (buf[0] == '#') continue;

    /* 分割 */
    nelem = split(buf, ' ', elem, buf_size / 2);

    /* ラベル */
    label = atoi(elem[0]);

    /* ベクトル */
    while (sv_len + nelem - 1 > sv_size) {
      sv_size = 2 * sv_size + 1;
      sv_idx = srealloc(sv_idx, sizeof(int) * sv_size);
      sv_val = srealloc(sv_val, sizeof(float) * sv_size);
      if (exm->num > 0) {
	exm->sv[0]->idx = sv_idx;
	exm->sv[0]->val = sv_val;
      }
      for (i = 1; i < exm->num; i++) {
	exm->sv[i]->idx = exm->sv[i - 1]->idx + exm->sv[i - 1]->num;
	exm->sv[i]->val = exm->sv[i - 1]->val + exm->sv[i - 1]->num;
      }
    }
    for (i = 1, sv_num = 0; i < nelem; i++) {
      if (elem[i][0] == '\0') continue;	/* 2つ以上スペースがある場合 */
      s = rindex(elem[i], ':');
      if (s == NULL) {
	if (svm_verbose) fprintf(stderr, "<%d: invalid data (no colon)> ", line);
	return NULL;
      }

      *(s++) = '\0';
      (sv_idx + sv_len)[sv_num] = atoi(elem[i]);
      (sv_val + sv_len)[sv_num] = (float)atof(s);
      if ((sv_val + sv_len)[sv_num] == 0.0f) continue;
      if ((sv_val + sv_len)[sv_num] != 1.0f) isbinary = 0;
      sv_num++;
    }

    /* メモリを確保 */
    if (exm->num + 1 > exm_size) {
      exm_size = 2 * exm_size + 1;
      exm->label = srealloc(exm->label, sizeof(int) * exm_size);
      exm->sv = srealloc(exm->sv, sizeof(SVM_SV *) * exm_size);
      exm->sv[exm_size / 2] = smalloc(sizeof(SVM_SV) * (exm_size / 2 + 1));
      for (i = exm_size / 2 + 1; i < exm_size; i++) exm->sv[i] = exm->sv[i - 1] + 1;
    }
    /* コピー */
    exm->label[exm->num] = label;
    exm->sv[exm->num]->num = sv_num;
    exm->sv[exm->num]->idx = sv_idx + sv_len;
    exm->sv[exm->num]->val = sv_val + sv_len;
    sv_len += sv_num;
    exm->num++;
  }
  if (!feof(fp)) {
    if (svm_verbose) fprintf(stderr, "<file read error> ");
    return NULL;
  }

  /* メモリを切り詰める */
  if (sv_len > 0) {
    sv_size = sv_len;
    sv_idx = srealloc(sv_idx, sizeof(int) * sv_size);
    sv_val = srealloc(sv_val, sizeof(float) * sv_size);
    if (exm->num > 0) {
      exm->sv[0]->idx = sv_idx;
      exm->sv[0]->val = sv_val;
    }
    for (i = 1; i < exm->num; i++) {
      exm->sv[i]->idx = exm->sv[i - 1]->idx + exm->sv[i - 1]->num;
      exm->sv[i]->val = exm->sv[i - 1]->val + exm->sv[i - 1]->num;
    }
  }
  if (exm->num > 0) {
    exm->sv[exm_size / 2] = srealloc(exm->sv[exm_size / 2], sizeof(SVM_SV) * (exm->num - exm_size / 2));
    exm_size = exm->num;
    exm->label = srealloc(exm->label, sizeof(int) * exm_size);
    exm->sv = srealloc(exm->sv, sizeof(SVM_SV *) * exm_size);
  }

  /* メモリ開放 */
  free(buf);
  free(elem);

  /* binary vectorかどうかの判定とbinaryへの変換処理 */
  if (isbinary) {
    free(sv_val);
    for (i = 0; i < exm->num; i++) {
      exm->sv[i]->val = NULL;
    }
  }

  return exm;
}


/*
  モデルファイルの作成と書き込み(SVM^light(V.3.02)と同じフォーマット)
*/
int svm_createmdl(SVM_KPRM *kprm, SVM_EXM *exm, double *alpha, double b, FILE *fp) {
  int i, j;
  int num;

  /* サポートベクター数のカウント */
  num = 0;
  for (i = 0; i < exm->num; i++) if (alpha[i] > 0.0) num++;
  if (svm_verbose) fprintf(stderr, "(#SV=%d) ", num);

  /* モデル書き込み */
  fprintf(fp, "SVM-light Version V3.02\n");
  fprintf(fp,"%d # kernel type\n", kprm->ktype);
  fprintf(fp,"%.7g # kernel parameter -d \n", kprm->degree);
  fprintf(fp,"%.7g # kernel parameter -g \n", kprm->gamma);
  fprintf(fp,"%.7g # kernel parameter -s \n", kprm->gamma);
  fprintf(fp,"%.7g # kernel parameter -r \n", kprm->coef);
  fprintf(fp,"%s # kernel parameter -u \n", "empty");
  fprintf(fp,"%d # number of support vectors \n", num + 1);
  fprintf(fp,"%.7g # threshold b \n", -b);
  for (i = 0; i < exm->num; i++) {
    if (alpha[i] > 0.0) {
      fprintf(fp,"%.7g", alpha[i] * exm->label[i]);
      for (j = 0; j < exm->sv[i]->num; j++) {
	fprintf(fp," %d:%.7g", exm->sv[i]->idx[j], (exm->sv[i]->val == NULL) ? 1.0 : exm->sv[i]->val[j]);
      }
      fprintf(fp,"\n");
    }
  }

  return 0;
}


/*
  モデルファイルの読み込み(フォーマットはSVM^light(V.3.02)と同じ)
*/
SVM_MDL *svm_readmdl(FILE *fp) {
  int i;
  int buf_size;
  char *buf;
  int nelem;
  char **elem;
  int mdl_size;
  SVM_MDL *mdl;
  int line;
  int isbinary;
  double beta;
  int sv_size;
  int sv_len;
  int sv_num;
  int *sv_idx;
  float *sv_val;
  char *s;
  int len;
  int p_ktype;
  float p_d, p_g, p_s, p_r;
  int num;
  double b;

  /* メモリ確保 */
  mdl = smalloc(sizeof(SVM_MDL));
  buf_size = BUF_SIZE;
  buf = smalloc(sizeof(char) * buf_size);
  elem = smalloc(sizeof(char *) * (buf_size / 2));
  sv_size = 0;
  sv_len = 0;
  sv_idx = NULL;
  sv_val = NULL;

  /* ID */
  if (fgets(buf, buf_size, fp) == NULL) {
    if (svm_verbose) fprintf(stderr, "<%d: file read error> ", 1);
    return NULL;
  }
  if (strncmp("SVM-light Version ", buf, 18) != 0) {
    if (svm_verbose) fprintf(stderr, "<wrong ID> ");
    return NULL;
  }

  /* パラメータ */
  fscanf(fp,"%d%*[^\n]\n", &p_ktype);
  fscanf(fp,"%f%*[^\n]\n", &p_d);
  fscanf(fp,"%f%*[^\n]\n", &p_g);
  fscanf(fp,"%f%*[^\n]\n", &p_s);
  fscanf(fp,"%f%*[^\n]\n", &p_r);
  fscanf(fp,"%*s%*[^\n]\n");
  fscanf(fp,"%d%*[^\n]\n", &num);
  fscanf(fp,"%lf%*[^\n]\n", &b);

  /* 変換 */
  if (p_ktype >= 4) {
    if (svm_verbose) fprintf(stderr, "<unsupported kernel type> ");
    return NULL;
  }
  mdl->kprm.ktype = p_ktype;
  mdl->kprm.degree = p_d;
  mdl->kprm.gamma = (p_ktype == 2) ? p_g : p_s;
  mdl->kprm.coef = p_r;
  mdl->b = -b;

  /* メモリ確保 */
  mdl_size = num - 1;
  if (mdl_size > 0) {
    mdl->beta = smalloc(sizeof(double) * mdl_size);
    mdl->sv = smalloc(sizeof(SVM_SV *) * mdl_size);
    mdl->sv[0] = smalloc(sizeof(SVM_SV) * mdl_size);
    for (i = 1; i < mdl_size; i++) mdl->sv[i] = mdl->sv[i - 1] + 1;
    mdl->reg = smalloc(sizeof(float) * mdl_size);
  }
  mdl->num = 0;

  /* サポートベクターの読み込み */
  isbinary = 1;
  for (line = 10; line - 10 < num - 1; line++) {
    /* ベクトルの読み込み(メモリの動的確保) */
    if (fgets(buf, buf_size, fp) == NULL) break;
    while (buf[(len = strlen(buf)) - 1] != '\n') {
      buf_size = 2 * buf_size + 1;
      buf = srealloc(buf, sizeof(char) * buf_size);
      elem = srealloc(elem, sizeof(char *) * (buf_size / 2));
      if (fgets(buf + len, buf_size - len, fp) == NULL) {
	if (svm_verbose) fprintf(stderr, "<%d: unexpected EOF> ", line);
	return NULL;
      }
    }
    buf[--len] = '\0';
    if (len > 0 && buf[len - 1] == '\r') buf[--len] = '\0';
    if (len > 0 && buf[len - 1] == ' ') buf[--len] = '\0';

    /* 分割 */
    nelem = split(buf, ' ', elem, buf_size);

    /* サポートベクターの重み */
    beta = (double)atof(elem[0]);

    /* ベクトル */
    while (sv_len + nelem - 1 > sv_size) {
      sv_size = 2 * sv_size + 1;
      sv_idx = srealloc(sv_idx, sizeof(int) * sv_size);
      sv_val = srealloc(sv_val, sizeof(float) * sv_size);
      if (mdl->num > 0) {
	mdl->sv[0]->idx = sv_idx;
	mdl->sv[0]->val = sv_val;
      }
      for (i = 1; i < mdl->num; i++) {
	mdl->sv[i]->idx = mdl->sv[i - 1]->idx + mdl->sv[i - 1]->num;
	mdl->sv[i]->val = mdl->sv[i - 1]->val + mdl->sv[i - 1]->num;
      }
    }
    for (i = 1, sv_num = 0; i < nelem; i++) {
      s = rindex(elem[i], ':');
      if (s == NULL) {
	if (svm_verbose) fprintf(stderr, "<%d: invalid data (no colon)> ", line);
	return NULL;
      }

      *(s++) = '\0';
      (sv_idx + sv_len)[sv_num] = atoi(elem[i]);
      (sv_val + sv_len)[sv_num] = (float)atof(s);
      if ((sv_val + sv_len)[sv_num] == 0.0f) continue;
      if ((sv_val + sv_len)[sv_num] != 1.0f) isbinary = 0;
      sv_num++;
    }

    /* コピー */
    mdl->beta[mdl->num] = beta;
    mdl->sv[mdl->num]->num = sv_num;
    mdl->sv[mdl->num]->idx = sv_idx + sv_len;
    mdl->sv[mdl->num]->val = sv_val + sv_len;
    sv_len += sv_num;
    mdl->num++;
  }

  /* メモリの切り詰め */
  if (sv_len > 0) {
    sv_size = sv_len;
    sv_idx = srealloc(sv_idx, sizeof(int) * sv_size);
    sv_val = srealloc(sv_val, sizeof(float) * sv_size);
    if (mdl->num > 0) {
      mdl->sv[0]->idx = sv_idx;
      mdl->sv[0]->val = sv_val;
    }
    for (i = 1; i < mdl->num; i++) {
      mdl->sv[i]->idx = mdl->sv[i - 1]->idx + mdl->sv[i - 1]->num;
      mdl->sv[i]->val = mdl->sv[i - 1]->val + mdl->sv[i - 1]->num;
    }
  }

  /* メモリ開放 */
  free(buf);
  free(elem);

  /* binary vectorかどうかの判定とbinaryへの変換処理 */
  if (isbinary) {
    free(sv_val);
    for (i = 0; i < mdl->num; i++) {
      mdl->sv[i]->val = NULL;
    }
  }

  /* 作業用データの初期化 */
  if (svm_invidx) {
    mdl->kwrk = svm_knl_kwrkinit_inv(&mdl->kprm, mdl->num, mdl->sv);
  } else {
    mdl->kwrk = svm_knl_kwrkinit(&mdl->kprm, mdl->num, mdl->sv);
  }

  return mdl;
}


/*
  決定関数
*/
double svm_decision(SVM_MDL *mdl, SVM_SV *x) {
  int i;
  double d = 0.0;

  if (mdl->num == 0) return mdl->b;

  svm_knl_kwrkkernel(&mdl->kprm, mdl->kwrk, x, mdl->reg);

  for (i = 0; i < mdl->num; i++) {
    d += mdl->beta[i] * mdl->reg[i];
  }
  d += mdl->b;

  return d;
}


/*
  事例を読み込む(メモリ確保の効率化のためバッファを受け渡す)
  戻り値: 0=事例を読み込んだ, 1=エラー, -1=EOF
*/
int svm_readvec(FILE *fp, int *label, SVM_SV *svbuf, int *svbuf_size, SVM_SV *sv) {
  int i;
  char buf[BUF_SIZE];
  int isbinary;

  isbinary = 1;
  if (gettkn(buf, BUF_SIZE, fp)) return -1;
  *label = atoi(buf);
  for (svbuf->num = 0; ; ) {
    char *idx, *val;

    if (gettkn(buf, BUF_SIZE, fp)) break;

    idx = buf;
    val = rindex(buf, ':');
    if (val == NULL) {
      if (svm_verbose) fprintf(stderr, "<invalid data (no colon) [%s]> ", buf);
      return 1;
    }
    *val++ = '\0';

    if (svbuf->num + 1 > *svbuf_size) {
      *svbuf_size = 2 * *svbuf_size + 1;
      svbuf->idx = srealloc(svbuf->idx, sizeof(int) * *svbuf_size);
      svbuf->val = srealloc(svbuf->val, sizeof(float) * *svbuf_size);
    }

    svbuf->val[svbuf->num] = (float)atof(val);
    if (svbuf->val[svbuf->num] == 0.0f) continue;
    if (svbuf->val[svbuf->num] != 1.0f) isbinary = 0;
    svbuf->idx[svbuf->num] = atoi(idx);
    svbuf->num++;
  }

  /* データ作成 */
  sv->num = svbuf->num;
  sv->idx = svbuf->idx;
  sv->val = svbuf->val;

  /* binary vectorかどうかの判定とbinaryへの変換処理 */
  if (isbinary) {
    sv->val = NULL;
  }

  /* ソート */
  sortsv(sv);

  /* 重複する素性を削除 */
  for (i = 0; i < sv->num - 1; i++) {
    if (sv->idx[i] == sv->idx[i + 1]) {
      int j;

      for (j = i + 1; j < sv->num - 1; j++) {
	sv->idx[j] = sv->idx[j + 1];
	if (sv->val != NULL) sv->val[j] = sv->val[j + 1];
      }
      sv->num--;
      i--;
    }
  }

  return 0;
}


/*
  トークンを1つ読み込んで返す
  戻り値: 0=トークンを読み込んだ, 1=EOS, -1=EOF
*/
static int gettkn(char *buf, int size, FILE *fp) {
  int i;
  int c;

  c = getc(fp);
  if (c == '\r') c = getc(fp);
  if (c == '\n') return 1;
  if (c == EOF) return -1;

  for (i = 0; i < size - 1; i++) {
    if (c == '\r' || c == '\n') {
      ungetc(c, fp);
      break;
    }
    if (c == ' ') break;
    buf[i] = c;
    c = getc(fp);
  }
  buf[i] = '\0';

  return 0;
}


/*
  ベクトルのインデックスを単純に昇順にソート
*/
static void sortsv(SVM_SV *sv) {
  int i, j;

  if (sv->val != NULL) {
    for (i = 1; i < sv->num; i++) {
      int tmp_idx;
      float tmp_val;

      tmp_idx = sv->idx[i];
      tmp_val = sv->val[i];
      for (j = i; j > 0 && sv->idx[j - 1] > tmp_idx; j--) {
	sv->idx[j] = sv->idx[j - 1];
	sv->val[j] = sv->val[j - 1];
      }
      sv->idx[j] = tmp_idx;
      sv->val[j] = tmp_val;
    }
  } else {
    for (i = 1; i < sv->num; i++) {
      int tmp_idx;

      tmp_idx = sv->idx[i];
      for (j = i; j > 0 && sv->idx[j - 1] > tmp_idx; j--) {
	sv->idx[j] = sv->idx[j - 1];
      }
      sv->idx[j] = tmp_idx;
    }
  }

  return;
}
