#include <ctype.h>
#include <getopt.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "bwa.h"
#include "bwamem.h"

// The options that we parse, some options removed (mostly paired end things)
const char valid_opts[] =
    "aMYCjk:c:v:s:r:A:B:O:E:U:w:L:d:T:Q:D:m:N:W:x:G:h:y:X:I:K:";

static void update_a(mem_opt_t* opt, const mem_opt_t* opt0) {
  if (opt0->a) {  // matching score is changed
    if (!opt0->b) opt->b *= opt->a;
    if (!opt0->T) opt->T *= opt->a;
    if (!opt0->o_del) opt->o_del *= opt->a;
    if (!opt0->e_del) opt->e_del *= opt->a;
    if (!opt0->o_ins) opt->o_ins *= opt->a;
    if (!opt0->e_ins) opt->e_ins *= opt->a;
    if (!opt0->zdrop) opt->zdrop *= opt->a;
    if (!opt0->pen_clip5) opt->pen_clip5 *= opt->a;
    if (!opt0->pen_clip3) opt->pen_clip3 *= opt->a;
    if (!opt0->pen_unpaired) opt->pen_unpaired *= opt->a;
  }
}

mem_opt_t* get_opts(int argc, char* argv[], bwaidx_t* idx) {
  // Modified from fastmap.c:main
  //
  // The index is required only to mutate it in accordance
  //   with the ignore_alt option.

  mem_opt_t *opt, opt0;
  int c, i, ignore_alt = 0;
  char* p;
  const char* mode = 0;

  // Change default verbosity to stop most stuff being written to stderr
  bwa_verbose = 0;

  // Reset optind to allow multiple calls to get_opts in the same process
  // (critical for Python library usage where multiple BwaAligner objects are created)
  optind = 1;

  opt = mem_opt_init();
  memset(&opt0, 0, sizeof(mem_opt_t));
  while ((c = getopt(argc, argv, valid_opts)) >= 0) {
    if (c == 'k')
      opt->min_seed_len = atoi(optarg), opt0.min_seed_len = 1;
    else if (c == 'K')
      opt->chunk_size = atoi(optarg);
    else if (c == 'x')
      mode = optarg;
    else if (c == 'w')
      opt->w = atoi(optarg), opt0.w = 1;
    else if (c == 'A')
      opt->a = atoi(optarg), opt0.a = 1;
    else if (c == 'B')
      opt->b = atoi(optarg), opt0.b = 1;
    else if (c == 'T')
      opt->T = atoi(optarg), opt0.T = 1;
    else if (c == 'U')
      opt->pen_unpaired = atoi(optarg), opt0.pen_unpaired = 1;
    else if (c == 'a')
      opt->flag |= MEM_F_ALL;
    else if (c == 'M')
      opt->flag |= MEM_F_NO_MULTI;
    else if (c == 'Y')
      opt->flag |= MEM_F_SOFTCLIP;
    else if (c == 'C') { /* append FASTA/FASTQ comment to SAM output; no-op here
                          */
    } else if (c == 'c')
      opt->max_occ = atoi(optarg), opt0.max_occ = 1;
    else if (c == 'd')
      opt->zdrop = atoi(optarg), opt0.zdrop = 1;
    else if (c == 'v')
      bwa_verbose = atoi(optarg);
    else if (c == 'j')
      ignore_alt = 1;
    else if (c == 'r')
      opt->split_factor = atof(optarg), opt0.split_factor = 1.;
    else if (c == 'D')
      opt->drop_ratio = atof(optarg), opt0.drop_ratio = 1.;
    else if (c == 'm')
      opt->max_matesw = atoi(optarg), opt0.max_matesw = 1;
    else if (c == 's')
      opt->split_width = atoi(optarg), opt0.split_width = 1;
    else if (c == 'G')
      opt->max_chain_gap = atoi(optarg), opt0.max_chain_gap = 1;
    else if (c == 'N')
      opt->max_chain_extend = atoi(optarg), opt0.max_chain_extend = 1;
    else if (c == 'W')
      opt->min_chain_weight = atoi(optarg), opt0.min_chain_weight = 1;
    else if (c == 'y')
      opt->max_mem_intv = atol(optarg), opt0.max_mem_intv = 1;
    else if (c == 'X')
      opt->mask_level = atof(optarg);
    else if (c == 'h') {
      opt0.max_XA_hits = opt0.max_XA_hits_alt = 1;
      opt->max_XA_hits = opt->max_XA_hits_alt = strtol(optarg, &p, 10);
      if (*p != 0 && ispunct(*p) && isdigit(p[1]))
        opt->max_XA_hits_alt = strtol(p + 1, &p, 10);
    } else if (c == 'Q') {
      opt0.mapQ_coef_len = 1;
      opt->mapQ_coef_len = atoi(optarg);
      opt->mapQ_coef_fac = opt->mapQ_coef_len > 0 ? log(opt->mapQ_coef_len) : 0;
    } else if (c == 'O') {
      opt0.o_del = opt0.o_ins = 1;
      opt->o_del = opt->o_ins = strtol(optarg, &p, 10);
      if (*p != 0 && ispunct(*p) && isdigit(p[1]))
        opt->o_ins = strtol(p + 1, &p, 10);
    } else if (c == 'E') {
      opt0.e_del = opt0.e_ins = 1;
      opt->e_del = opt->e_ins = strtol(optarg, &p, 10);
      if (*p != 0 && ispunct(*p) && isdigit(p[1]))
        opt->e_ins = strtol(p + 1, &p, 10);
    } else if (c == 'L') {
      opt0.pen_clip5 = opt0.pen_clip3 = 1;
      opt->pen_clip5 = opt->pen_clip3 = strtol(optarg, &p, 10);
      if (*p != 0 && ispunct(*p) && isdigit(p[1]))
        opt->pen_clip3 = strtol(p + 1, &p, 10);
    } else if (c == 'I') {
      // Insert size distribution - this is handled by the calling code
      // We just need to recognize it as a valid option
    } else
      return NULL;
  }

  if (mode) {
    if (strcmp(mode, "intractg") == 0) {
      if (!opt0.o_del) opt->o_del = 16;
      if (!opt0.o_ins) opt->o_ins = 16;
      if (!opt0.b) opt->b = 9;
      if (!opt0.pen_clip5) opt->pen_clip5 = 5;
      if (!opt0.pen_clip3) opt->pen_clip3 = 5;
    } else if (
        strcmp(mode, "pacbio") == 0 || strcmp(mode, "pbref") == 0 ||
        strcmp(mode, "ont2d") == 0) {
      if (!opt0.o_del) opt->o_del = 1;
      if (!opt0.e_del) opt->e_del = 1;
      if (!opt0.o_ins) opt->o_ins = 1;
      if (!opt0.e_ins) opt->e_ins = 1;
      if (!opt0.b) opt->b = 1;
      if (opt0.split_factor == 0.) opt->split_factor = 10.;
      if (strcmp(mode, "ont2d") == 0) {
        if (!opt0.min_chain_weight) opt->min_chain_weight = 20;
        if (!opt0.min_seed_len) opt->min_seed_len = 14;
        if (!opt0.pen_clip5) opt->pen_clip5 = 0;
        if (!opt0.pen_clip3) opt->pen_clip3 = 0;
      } else {
        if (!opt0.min_chain_weight) opt->min_chain_weight = 40;
        if (!opt0.min_seed_len) opt->min_seed_len = 17;
        if (!opt0.pen_clip5) opt->pen_clip5 = 0;
        if (!opt0.pen_clip3) opt->pen_clip3 = 0;
      }
    } else {
      return NULL;  // FIXME memory leak
    }
  } else
    update_a(opt, &opt0);
  bwa_fill_scmat(opt->a, opt->b, opt->mat);

  if (ignore_alt)
    for (i = 0; i < idx->bns->n_seqs; ++i) idx->bns->anns[i].is_alt = 0;

  return opt;
}
