/**
 *    WHAM - high-throughput sequence aligner
 *    Copyright (C) 2011  WHAM Group, University of Wisconsin
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*	$Id: builder.cpp 165 2012-11-26 10:23:16Z yinan $ */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>
#ifndef WIN32
#include <sys/time.h>
#endif

#include "error.h"
#include "aligner.h"
#include "sequence.h"
#include "short.h"
#include "edit_distance.h"

#define BUILD_MODE_PIPELINE 1
#define BUILD_MODE_ALL		2

extern int ELOG_LEVEL;

char pgversion[] = "0.1.5";
char * pgcommand;

char ** commaList(char * str, int & num) {
  int i;
  char * pch;
  char ** tok;

  num = 1;
  pch = strchr(str, ',');
  while (pch != NULL) {
    num++;
    pch = strchr(pch + 1, ',');
  }

  tok = new char *[num];
  i = 0;
  pch = strtok(str, ",");
  while (pch != NULL) {
    tok[i] = new char[strlen(pch) + 1];strcpy(tok[i], pch);
    i++;
    pch = strtok (NULL, ",");
  }

  return tok;
}

char * getCommand(int argc, char * argv[]) {
  int i, len = 0;
  char * str;

  for (i = 0; i < argc; i++)
    len += strlen(argv[i]) + 1;

  str = new char[len];

  for (i = 0; i < argc; i++) {
    if (i > 0)
      strcat(str, " ");
    strcat(str, argv[i]);
  }

  return str;
}

int getArguVal(char * str) {
  char * c;
  int base;

  c = strchr(str, 'k');
  if (c == NULL
    )
    c = strchr(str, 'K');
  if (c == NULL
    )
    c = strchr(str, 'm');
  if (c == NULL
    )
    c = strchr(str, 'M');
  if (c == NULL
    )
    c = strchr(str, 'g');
  if (c == NULL
    )
    c = strchr(str, 'G');

  if (c == NULL
    )
    return atoi(str);

  if (*c == 'k' || *c == 'K')
    base = 1024;
  else if (*c == 'm' || *c == 'M')
    base = 1024 * 1024;
  else if (*c = 'g' || *c == 'G')
    base = 1024 * 1024 * 1024;
  else
    base = 1;

  *c = '\0';
  return base * atoi(str);
}

int printversion() {
  printf("WHAM Version %s\n", pgversion);
  return SUCCESS;
}

int printhelp() {
  printf("Usage:\n");
  printf("  wham-build [options]* -l <int> <ref_sequence> <basepath>\n");
  printf("  -l <int>         specify the length of short reads\n");
  printf("  <ref_sequence>   comma-separated list of fiels with ref sequences\n");
  printf("  <basepath>       write wham data to files with this dir/basename\n");

  printf("Options:\n");
  printf("  -v <int>         report hits with <=v errors (0-5), ignore qualities\n");
  printf("  -p <int>         specify the number of fragments for alignments\n");
  printf("  -m <int>         discard subsequences appearing more than <int> times (default: 100).\n");
  printf("  -b <int>         specify the number of buckets\n");
  printf("  -a               find all valid matches (need much more memory)\n");

  //	printf("  --pipeline       build and save hash tables one by one\n");
  printf("  --mask           keep masked characters in the sequences (default: on)\n");
  printf("  --unmask         discard masked characters in the sequences. Masks are treated as Ns\n");
  printf("  --version        print version information\n");
  printf("  -h/--help        print this usage message\n");

  return SUCCESS;
}

int main(int argc, char* argv[]) {
  int i, j;
  int len, nPartition, nMismatch, nBucket;
  bool isEmbedHashTable;
  char ** fname = NULL;
  int nSeq = 1;
  int ret;
  int build_mode = BUILD_MODE_ALL;
  int skipMask = false;
  int maxRepeat = 100;
  CompactSequence * sequence;
  char basepath[128] = "";

  srand((unsigned int) time(NULL));
  len = 0;
  nPartition = 0;
  nMismatch = 2;
  nBucket = 0;
  isEmbedHashTable = false;

  for (i = 1, j = 0; i < argc; i++) {
    if (strcmp(argv[i], "-l") == 0)
      len = getArguVal(argv[++i]);
    else if (strcmp(argv[i], "-p") == 0)
      nPartition = getArguVal(argv[++i]);
    else if (strcmp(argv[i], "-v") == 0)
      nMismatch = getArguVal(argv[++i]);
    else if (strcmp(argv[i], "-m") == 0)
      maxRepeat = getArguVal(argv[++i]);
    else if (strcmp(argv[i], "-a") == 0)
      isEmbedHashTable = true;
    else if (strcmp(argv[i], "-b") == 0)
      nBucket = getArguVal(argv[++i]);
    else if (strcmp(argv[i], "--mask") == 0)
      skipMask = false;
    else if (strcmp(argv[i], "--unmask") == 0)
      skipMask = true;
//		else if (strcmp(argv[i], "--pipeline") == 0)
//			build_mode = BUILD_MODE_PIPELINE;
    else if (strcmp(argv[i], "--version") == 0) {
      printversion();
      return SUCCESS;
    } else if (strcmp(argv[i], "--info") == 0) {
      i++;
      if (strcmp(argv[i], "ERROR") == 0)
        ELOG_LEVEL = ERROR;
      else if (strcmp(argv[i], "WARNING") == 0)
        ELOG_LEVEL = WARNING;
      else if (strcmp(argv[i], "INFO") == 0)
        ELOG_LEVEL = INFO;
      else if (strcmp(argv[i], "DEBUG1") == 0)
        ELOG_LEVEL = DEBUG1;
    } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
      printhelp();
      return SUCCESS;
    } else {
      if (argv[i][0] == '-') {
        printf("Invalid option %s.\n", argv[i]);
        printhelp();
        return ERR_PARA;
      }

      if (j == 0)
        fname = commaList(argv[i], nSeq);
      else if (j == 1)
        strcpy(basepath, argv[i]);
      else {
        printf("Invalid option %s.\n", argv[i]);
        printhelp();
        return ERR_PARA;
      }
      j++;
    }
  }

  if (len == 0) {
    elog(ERROR, "specify the length of reads using -l\n");
    return ERR_PARA;
  }
  if (basepath[0] == '\0') {
    elog(ERROR, "specify the base name.\n");
    return ERR_PARA;
  }
  if (fname == NULL)
  {
    elog(ERROR, "specify the read files.\n");
    return ERR_PARA;
  }
  if (len > 128) {
    elog(ERROR, "WHAM supports read up to 128bps.\n");
    return ERR_PARA;
  }
  if (nMismatch > 5) {
    elog(ERROR, "specify the number of errors in range (0,5)\n");
    return ERR_PARA;
  }
//	if (nPartition == 0)
//		nPartition = nMismatch + 1;

  if (nPartition > 0 && nPartition <= nMismatch) {
    elog(ERROR,
        "Specify a greater value of nPartition (>nMismatch) using -p\n");
    return ERR_PARA;
  }

  /* save the command line */
  pgcommand = getCommand(argc, argv);
  /*
   if (skipMask)
   {
   maxRepeat = 0;
   }
   */
  elog(INFO, "length: %d, #mismatch: %d, #partition: %d\n", len, nMismatch,
      nPartition);

  elog(INFO, "loading reference sequences...\n");
  sequence = new CompactSequence(skipMask);
  ret = sequence->build(fname, nSeq, len, nMismatch);
  if (ret != SUCCESS)
  {
    elog(ERROR, "failed to load reference sequences.\n");
    return ret;
  }

  int nChar = sequence->getNum();

  elog(INFO, "saving the reference sequences...\n");
  if (sequence->save(basepath) != SUCCESS)
  {
    elog(ERROR, "failed to save reference sequences.\n");
    return ret;
  }

  Aligner * aligner = new Aligner();
  ret = aligner->init(sequence, len, nPartition, nBucket, nMismatch, 0, 0,
      maxRepeat, isEmbedHashTable);
  if (ret != SUCCESS)
  {
    elog(ERROR, "failed to initialize the aligner.\n");
    return ret;
  }

  elog(INFO, "building WHAM index...\n");
  ret = aligner->build(basepath);
  if (ret != SUCCESS)
  {
    if (ret == ERR_MEM)
      elog(ERROR, "No enough memory.\n");
    else
      elog(ERROR, "failed to build WHAM index.\n");
    return ret;
  }

  elog(INFO, "Complete.\n");

  return SUCCESS;
}
