/*
  sga2bed.c

  Convert SGA file to BED format.

  # Arguments:
  #   SGA File
  # Options:
  #   Set Path to locate chr_NC_gi file
  #   (used for NCBI id to chromosome name conversion)
  #   Set Read length  (def=0)
  #   Set Score factor for BED score field (5th field) (def=1)
  #   Create BED format without annotation track header lines
  #   Transfer SGA optional field(s) to BED field(s)
  #   Expand SGA lines into multiple BED lines
  #   Set BED track name
  #   Set BED track description
  #   Set BED track color

  Giovanna Ambrosini, EPFL/ISREC, Giovanna.Ambrosini@epfl.ch

  Copyright (c) 2014 EPFL and SIB Swiss Institute of Bioinformatics.

  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/>.

*/
/*
#define DEBUG
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <ctype.h>
#include <getopt.h>
#include <assert.h>
#include <math.h>
#include <sys/stat.h>
#include <limits.h>
#include "hashtable.h"
#ifdef DEBUG
#include <mcheck.h>
#endif

#include "version.h"

#define BUF_SIZE 8192
#define LINE_SIZE 1024
#define FT_MAX  64
#define SEQ_ID  64
#define CHR_NB 64
#define AC_MAX 64
#define POS_MAX 16
#define CNT_MAX 16
#define EXT_MAX 256
#define FIELD_MAX 64

typedef struct _options_t {
    char *dbPath;
    char *extField;
    int help;
    int debug;
    int db;
    int readLen;
    int bkExt;
    int fwExt;
    int scoreFact;
    int noHdr;
    int expand;
    int extend;
    char *trackName;
    char *trackDesc;
    char *trackColor;
} options_t;

static options_t options;

static hash_table_t *ac_table = NULL;
static hash_table_t *ext_fields = NULL;

int extIdx[10];
int extLen = 0;
char *progname = NULL;

void show_help(void) {
    fprintf(stderr, "Usage: %s [options] [<] <SGA file|stdin>\n"
            "      - version %s\n"
            "      where options are:\n"
            "  \t\t -d|--debug                Produce Debug information\n"
            "  \t\t -h|--help                 Show this Help text\n"
            "  \t\t -i|--db <dir>             Directory where the chr_size file is found (default: CWD)\n"
            "  \t\t -5|--bk_ext <len>         Set 5p-end extension <len>(bp)\n"
            "  \t\t -3|--fw_ext <len>         Set 3p-end extension <len>(bp)\n"
            "  \t\t -l|--readlen <len>        Set Read length <len>(bp)\n"
            "  \t\t                           Unoriented SGA files are extended by +/-<len>/2\n"
            "  \t\t -n|--score <score>        Normalisation factor for BED score field (5th) [score=1]\n"
            "  \t\t -X|--extend f1:F1[,f2:F2] Set SGA optional field(s) f1(,f2,...) to BED field(s) F1,(F2,..)\n"
            "  \t\t                           Fields are specified by column numbers\n"
            "  \t\t                           Accepted BED field values are 4, 5, and 7\n"
            "  \t\t                           Except BED field 5 (score field), BED fields 4 and 7\n"
            "  \t\t                           can be used to set multiple extension values from SGA\n"
            "  \t\t                           Fields 5 and 7 convert into numerical values whereas\n"
            "  \t\t                           BED field 4 takes character strings as they are\n"
            "  \t\t -H|--nohdr                BED format without annotation track header lines\n"
            "  \t\t -r                        Deprecated, please use -H\n"
            "  \t\t -x|--expand               Expand SGA lines into multiple BED lines\n"
            "  \t\t --name <name>             Set name for track name field [def. name=SGA-feature]\n"
            "  \t\t --desc <desc>             Set track description field [def. desc=\"ChIP-Seq Custom data\"]\n"
            "  \t\t --color <col>             Define the track color in comma-separated RGB values [def. 100,100,100]\n"
            "\n\tConvert SGA format into BED format.\n\n", progname,
            VERSION);
}

int process_ac(void) {
    FILE *input;
    //int c;
    char buf[LINE_SIZE];
    char *chrFile;
    char chrom[18];
    int cLen;

    if (options.db) {
        cLen = (int) strlen(options.dbPath) + 12;
        if ((chrFile = (char *) malloc(cLen * sizeof(char))) == NULL) {
            perror("process_ac: malloc");
            exit(1);
        }
        strcpy(chrFile, options.dbPath);
    } else {
        cLen = 21 + 12;
        if ((chrFile = (char *) malloc(cLen * sizeof(char))) == NULL) {
            perror("process_ac: malloc");
            exit(1);
        }
        /*
         * Replace /home/local/db/genome (which cannot be assumed to exist on users'
         * machines) with $CWD.
         strcpy(chrFile, "/home/local/db/genome");
         */
        strcpy(chrFile, ".");
    }
    strcat(chrFile, "/chr_NC_gi");

    input = fopen(chrFile, "r");
    if (input == NULL) {
        fprintf(stderr,
                "ERROR: Could not open file %s - %s. See option -i.\n\n",
                chrFile, strerror(errno));
        show_help();
        return 1;
    }

    /*
     * Commented out for the same reasons as for bed2sga (q.v., l. 168ff).
     * Note however that I do not have test cases for this.
     do {
     c = fgetc(input);
     } while(c != '\n');
     */

    ac_table = hash_table_new(MODE_COPY);
    while (fgets(buf, LINE_SIZE, input) != NULL) {
        char *s;
        char chr_nb[CHR_NB] = "";
        char ncbi_ac[AC_MAX] = "";
        int i = 0;
        int nb_len = 0;
        int ac_len = 0;
        /*int valid = 1; */
        s = buf;
        /* Check line */
        /* Get first character: if # skip line */
        if (*s == '#')
            continue;
        /* Chrom NB */
        while (*s != 0 && !isspace(*s)) {
            if (i >= CHR_NB) {
                fprintf(stderr, "sga2bed(191) AC too long in %s\n", buf);
                fclose(input);
                exit(1);
            }
            chr_nb[i++] = *s++;
        }
        if (i < CHR_NB)
            chr_nb[i] = 0;
        nb_len = i + 1;
        while (isspace(*s))
            s++;
        /* Chromosome NCBI AC */
        i = 0;
        while (*s != 0 && !isspace(*s)) {
            if (i >= AC_MAX) {
                fprintf(stderr, "AC too long \"%s\" \n", s);
                fclose(input);
                exit(1);
            }
            ncbi_ac[i++] = *s++;
        }
        if (i < AC_MAX)
            ncbi_ac[i] = 0;
        ac_len = i + 1;
        /* Check if chrom contains valid numeric characters */
        /*
           for (i = 0; i < strlen(chr_nb); ++i) {
           if (!isdigit(chr_nb[i])) {
           valid = 0;
           break;
           }
           }
           if (valid) {
           strcpy(chrom, "chr");
           strcat(chrom, chr_nb);
           } else {
           strcpy(chrom, chr_nb);
           } */
        strcpy(chrom, "chr");
        strcat(chrom, chr_nb);
        nb_len = (int) strlen(chrom) + 1;
        /* printf("adding key %s (len = %d) value %s (ac) (len = %d) to hash table\n", chrom, nb_len, ncbi_ac, ac_len); */
        /* Store both NCBI identifier to chrom number and chrom number to chrom number keys */
        hash_table_add(ac_table, ncbi_ac, (size_t) ac_len, chrom,
                       (size_t) nb_len);
        if (options.debug) {
            char *cn =
                hash_table_lookup(ac_table, ncbi_ac, (size_t) ac_len);
            fprintf(stderr,
                    " AC Hash table: %s (len = %d) -> %s (len = %d)\n",
                    ncbi_ac, ac_len, cn, nb_len);
        }
        hash_table_add(ac_table, chrom, (size_t) nb_len, chrom,
                       (size_t) nb_len);
        if (options.debug) {
            char *cn = hash_table_lookup(ac_table, chrom, (size_t) nb_len);
            fprintf(stderr,
                    " AC Hash table: %s (len = %d) -> %s (len = %d)\n",
                    chrom, nb_len, cn, nb_len);
        }
    }
    return 0;
}

char **str_split(char *a_str, const char a_delim) {
    char **result = 0;
    size_t count = 0;
    char *tmp = a_str;
    char *last_comma = 0;
    char delim[2];
    delim[0] = a_delim;
    delim[1] = 0;
    /* Count how many elements will be extracted. */
    while (*tmp) {
        if (a_delim == *tmp) {
            count++;
            last_comma = tmp;
        }
        tmp++;
    }
    /* Add space for trailing token. */
    count += last_comma < (a_str + strlen(a_str) - 1);
    /* Add space for terminating null string so caller
       knows where the list of returned strings ends. */
    count++;
    result = malloc(sizeof(char *) * count);

    if (result) {
        size_t idx = 0;
        char *token = strtok(a_str, delim);

        while (token) {
            assert(idx < count);
            *(result + idx++) = strdup(token);
            token = strtok(0, delim);
        }
        assert(idx == count - 1);
        *(result + idx) = 0;
    }
    return result;
}

int process_sga(FILE *input, char *iFile) {
    unsigned long start, end;
    unsigned long pos;
    int cnt;
    char *name = NULL;
    int score;
    int value;
    char *ext_str = NULL;
    int first = 1;
    char *s, *res, *buf;
    size_t bLen = LINE_SIZE;

    if (options.debug)
        fprintf(stderr, " Processing SGA file %s\n", iFile);
    if ((s = malloc(bLen * sizeof(char))) == NULL) {
        perror("process_sga: malloc");
        exit(1);
    }
#ifdef DEBUG
    int c = 1;
#endif
    while ((res = fgets(s, (int) bLen, input)) != NULL) {
        size_t cLen = strlen(s);
        char seq_id[SEQ_ID] = "";
        char ft[FT_MAX] = "";
        char position[POS_MAX] = "";
        char count[CNT_MAX] = "";
        char strand = '\0';
        char ext[EXT_MAX];
        char sga_ext_f[12][FIELD_MAX];
        int first_name_ext = 1;
        int first_val_ext = 1;
        int i = 0;
        int id_len = 0;
        char *cn;
        char **tokens;
        int no_strand = 0;

        memset(ext, 0, (size_t) EXT_MAX);
        while (cLen + 1 == bLen && s[cLen - 1] != '\n') {
            bLen *= 2;
            if ((s = realloc(s, bLen)) == NULL) {
                perror("process_file: realloc");
                exit(1);
            }
            res = fgets(s + cLen, (int) (bLen - cLen), input);
            cLen = strlen(s);
        }
        if (s[cLen - 1] == '\n')
            s[cLen - 1] = 0;

        buf = s;
        /* Get SGA fields */
        /* SEQ ID */
        while (*buf != 0 && !isspace(*buf)) {
            if (i >= SEQ_ID) {
                fprintf(stderr, "Seq ID is too long \"%s\" \n", buf);
                exit(1);
            }
            seq_id[i++] = *buf++;
        }
        while (isspace(*buf))
            buf++;
        /* FEATURE */
        i = 0;
        while (*buf != 0 && !isspace(*buf)) {
            if (i >= FT_MAX) {
                fprintf(stderr, "Feature is too long \"%s\" \n", buf);
                exit(1);
            }
            ft[i++] = *buf++;
        }
        while (isspace(*buf))
            buf++;
        /* Position */
        i = 0;
        while (isdigit(*buf)) {
            if (i >= POS_MAX) {
                fprintf(stderr, "Position is too large \"%s\" \n", buf);
                exit(1);
            }
            position[i++] = *buf++;
        }
        position[i] = 0;
        pos = atoi(position);
        while (isspace(*buf))
            buf++;
        /* Strand */
        strand = *buf++;
        while (isspace(*buf))
            buf++;
        /* Counts */
        i = 0;
        while (isdigit(*buf)) {
            if (i >= CNT_MAX) {
                fprintf(stderr, "Count is too large \"%s\" \n", buf);
                exit(1);
            }
            count[i++] = *buf++;
        }
        count[i] = 0;
        cnt = atoi(count);
        while (isspace(*buf))
            buf++;
        /* SGA Extension */
        i = 0;
        while (*buf != 0) {
            if (i >= EXT_MAX) {
                fprintf(stderr, "Extension is too long \"%s\" \n", buf);
                exit(1);
            }
            ext[i++] = *buf++;
        }
#ifdef DEBUG
        printf
            (" [%d] seq ID: %s   Feat: %s (%c)  Pos: %d  Cnts: %d Ext: %s\n",
             c++, seq_id, ft, strand, pos, cnt, ext);
#endif
        //printf(" seq ID: %s   Feat: %s (%c)  Pos: %d  Cnts: %d Ext: %s\n", seq_id, ft, strand, pos, cnt, ext);
        /* Set BED annotation track line */
        if (!options.noHdr) {
            if (first) {
                if (options.trackName == NULL) {
                    if ((options.trackName =
                         malloc((strlen(ft) + 1) * sizeof(char))) ==
                        NULL) {
                        perror("process_sga: malloc Feature");
                        exit(1);
                    }
                    strcpy(options.trackName, ft);
                }
                printf
                    ("track name=\"%s\" description=\"%s\" visibility=1 color=%s\n",
                     options.trackName, options.trackDesc,
                     options.trackColor);
                first = 0;
            }
        }
        /* Set BED start/end positions and strand */
        if (options.readLen > 0) {
            if (strand == '+') {
                start = pos - 1;
                end = start + options.readLen;
            } else if (strand == '-') {
                start = pos - options.readLen;
                end = pos;
            } else {
                start =
                    pos - 1 -
                    (unsigned long) ((long double) options.readLen / 2 +
                                     0.5);
                end =
                    pos - 1 +
                    (unsigned long) ((long double) options.readLen / 2 +
                                     0.5);
            }
        } else if ((options.bkExt < 0) && (options.fwExt == 0)) {
            if (strand == '-') {
                start = pos - 1;
                end = pos - options.bkExt;
            } else {
                start = pos - 1 + options.bkExt;
                end = pos;
            }
        } else if ((options.fwExt > 0) && (options.bkExt == 0)) {
            if (strand == '-') {
                start = pos - 1 - options.fwExt;
                end = pos;
            } else {
                start = pos - 1;
                end = pos + options.fwExt;
            }
        } else if ((options.bkExt < 0) && (options.fwExt > 0)) {
            if (strand == '-') {
                start = pos - 1 - options.fwExt;
                end = pos - options.bkExt;
            } else {
                start = pos - 1 - options.fwExt;
                end = pos + options.fwExt;
            }
        } else {
            start = pos - 1;
            end = pos;
        }
        if (strand == '0') {
            no_strand = 1;
            strand = '.';
        }
        /* Set BED name field (4th) with SGA feature */
        if ((name = malloc((strlen(ft) + 1) * sizeof(char))) == NULL) {
            perror("process_sga: malloc Name field");
            exit(1);
        }
        strcpy(name, ft);
        /* Set BED score field */
        score = cnt * options.scoreFact;
        /* Check extension flag - if present only redefine BED fields 4 and/or 5 and/or 7 */
        if (options.extend) {
            /* Extract SGA extended fields */
            tokens = str_split(ext, '\t');
            if (tokens) {
                for (i = 0; *(tokens + i); i++) {
                    strcpy(sga_ext_f[i], *(tokens + i));
                    //printf(" SGA ext field %s\n", sga_ext_f[i]);
                    free(*(tokens + i));
                }
                free(tokens);
            }
            /* Extract extension keys from ext_fields hash table */
            table_keys_t table_keys = hash_table_get_keys_as_str(ext_fields);
	    if (NULL == table_keys.keys) {
		    perror("ext_fields hash table has no keys");
		    exit(1);
	    }
            for (i = table_keys.key_num - 1; i >= 0; i--) {
                hash_table_element_t *el = table_keys.keys[i];
                int *sga_f = el->key;
                int *bed_f = NULL;
                bed_f = (int *) HT_LOOKUP(ext_fields, sga_f);
                //fprintf(stderr, " Ext fields: found SGA field %d -> BED field %d\n", *sga_f, *bed_f);
                if (*bed_f == 4) {
                    if (*sga_f > 5) {
                        if ((name =
                             realloc(name,
                                     (strlen(sga_ext_f[*sga_f - 6]) +
                                      1) * sizeof(char))) == NULL) {
                            perror("process_sga: malloc SGA field");
                            exit(1);
                        }
                    } else {
                        perror
                            ("process_sga: SGA extension field must be > 5");
                        exit(1);
                    }
                    if (first_name_ext) {
                        strcpy(name, sga_ext_f[*sga_f - 6]);
                        first_name_ext = 0;
                    } else {
                        strcat(name, " ");
                        strcat(name, sga_ext_f[*sga_f - 6]);
                    }
                } else if (*bed_f == 5) {       /* Set BED score field  */
                    if (*sga_f > 5) {
                        score =
                            round(atof(sga_ext_f[*sga_f - 6])) *
                            options.scoreFact;
                    } else {
                        perror
                            ("process_sga: SGA extension field must be > 5");
                        exit(1);
                    }
                } else if (*bed_f == 7) {       /* Set Extention fields, from 7 and on...  */
                    if (*sga_f > 5) {
                        value = round(atof(sga_ext_f[*sga_f - 6]));
                        if (value == INT_MIN || value == INT_MAX)
                            value = 0;
                        //printf("value = %d  \n", value);
                        if (first_val_ext) {
                            if ((ext_str =
                                 malloc((strlen(sga_ext_f[*sga_f - 6]) +
                                         1) * sizeof(char))) == NULL) {
                                perror("process_sga: malloc ext String");
                                exit(1);
                            }
                            sprintf(ext_str, "%d", value);
                            first_val_ext = 0;
                            //printf("Extension String: %s\n", ext_str);
                        } else {
                            if ((ext_str =
                                 realloc(ext_str,
                                         (strlen(sga_ext_f[*sga_f - 6]) +
                                          1) * sizeof(char))) == NULL) {
                                perror("process_sga: malloc SGA field");
                                exit(1);
                            }
                            // Determine the maximum length of the resulting string
                            int max_length = strlen(ext_str) + 12;      // Assuming the maximum length of an integer is 12 characters
                            // Allocate memory for the result array dynamically
                            char *result = malloc((max_length + 1) * sizeof(char));     // Add 1 for the null terminator
                            // Check if memory allocation was successful
                            if (result == NULL) {
                                perror
                                    ("Failed to allocate memory for result/ext_str");
                                exit(1);
                            }
                            // Use sprintf to format the string and store it in the result array
                            sprintf(result, "%s\t%d", ext_str, value);
                            // Copy the contents of the result array back into ext_str
                            strcpy(ext_str, result);
                            // Free the dynamically allocated memory for result
                            free(result);
                        }
                    } else {
                        perror
                            ("process_sga: SGA extension field must be > 5");
                        exit(1);
                    }
                } else {
                    perror
                        ("process_sga: Allowed BED extension fields are 4, 5, and 7");
                    exit(1);
                }
            }
	    free(table_keys.keys);
        }

        id_len = (int) strlen(seq_id) + 1;
        cn = hash_table_lookup(ac_table, seq_id, (size_t) id_len);
        if (cn == NULL)
            continue;
        /* Print out BED line */
        if (options.expand) {
            for (i = 0; i < cnt; i++) {
                if (ext_str == NULL) {
                    if (no_strand)
                        printf("%s\t%lu\t%lu\t%s\t%d\n", cn, start, end,
                               name, 0);
                    else
                        printf("%s\t%lu\t%lu\t%s\t%d\t%c\n", cn, start,
                               end, name, 0, strand);
                } else {
                    printf("%s\t%lu\t%lu\t%s\t%d\t%c\t%s\n", cn, start,
                           end, name, 0, strand, ext_str);
                }
            }
        } else {
            if (ext_str == NULL) {
                if (no_strand)
                    printf("%s\t%lu\t%lu\t%s\t%d\n", cn, start, end, name,
                           score);
                else
                    printf("%s\t%lu\t%lu\t%s\t%d\t%c\n", cn, start, end,
                           name, score, strand);
            } else {
                printf("%s\t%lu\t%lu\t%s\t%d\t%c\t%s\n", cn, start, end,
                       name, score, strand, ext_str);
            }
        }
    }                           /* End of While */
    if (input != stdin) {
        fclose(input);
    }
    return 0;
}

int main(int argc, char *argv[]) {
    progname = argv[0];
    char **tokens_1;
    char **tokens_2;
#ifdef DEBUG
    mcheck(NULL);
    mtrace();
#endif
    FILE *input;
    int i = 0;
    int j = 0;
    int usage = 0;

    options.scoreFact = 1;

    static struct option long_options[] = {
        /* These options may or may not set a flag.
           We distinguish them by their indices. */
        { "debug", no_argument, 0, 'd' },
        { "help", no_argument, 0, 'h' },
        { "db", required_argument, 0, 'i' },
        { "readlen", required_argument, 0, 'l' },
        { "score", required_argument, 0, 'n' },
        { "extend", required_argument, 0, 'X' },
        { "bkext", required_argument, 0, '5' },
        { "fwext", required_argument, 0, '3' },
        { "nohdr", no_argument, 0, 'H' },
        { "expand", no_argument, 0, 'x' },
        { "name", required_argument, 0, 0 },
        { "desc", required_argument, 0, 0 },
        { "color", required_argument, 0, 0 },
        { 0, 0, 0, 0 }
    };

    int option_index = 0;

    while (1) {
        int c = getopt_long(argc, argv, "d5:3:hi:l:n:X:Hrx", long_options,
                            &option_index);
        if (c == -1)
            break;
        switch (c) {
        case 'd':
            options.debug = 1;
            break;
        case 'h':
            options.help = 1;
            break;
        case '5':
            options.bkExt = atoi(optarg);
            break;
        case '3':
            options.fwExt = atoi(optarg);
            break;
        case 'i':
            options.dbPath = optarg;
            options.db = 1;
            break;
        case 'l':
            options.readLen = atoi(optarg);
            break;
        case 'n':
            options.scoreFact = atoi(optarg);
            break;
        case 'X':
            options.extField = optarg;
            options.extend = 1;
            break;
        case 'r':
            fprintf(stderr, "WARNING: option -r is deprecated; please use -H.\n");
            options.noHdr = 1;
            break;
        case 'H':
            options.noHdr = 1;
            break;
        case 'x':
            options.expand = 1;
            break;
        case 0:
            /* This option is to set the annotation track line */
            if (strcmp(long_options[option_index].name, "name") == 0) {
                options.trackName = optarg;
            }
            if (strcmp(long_options[option_index].name, "desc") == 0) {
                options.trackDesc = optarg;
            }
            if (strcmp(long_options[option_index].name, "color") == 0) {
                options.trackColor = optarg;
            }
            break;
        default:
            printf("?? getopt returned character code 0%o ??\n", c);
            usage = 1;
        }
    }
    /*printf("optind: %d argc: %d\n", optind, argc); */
    if (optind > argc || options.help == 1 || usage) {
        show_help();
        return 1;
    }
    /*printf("argc: %d optind: %d\n", argc, optind); */
    if (argc > optind) {
        if (!strcmp(argv[optind], "-")) {
            input = stdin;
        } else {
            input = fopen(argv[optind], "r");
            if (NULL == input) {
                fprintf(stderr, "Unable to open '%s': %s(%d)\n",
                        argv[optind], strerror(errno), errno);
                exit(EXIT_FAILURE);
            }
            if (options.debug)
                fprintf(stderr, "Processing file %s\n", argv[optind]);
        }
    } else {
        input = stdin;
    }
    if (options.trackDesc == NULL) {
        if ((options.trackDesc = malloc(32 * sizeof(char))) == NULL) {
            perror("main: malloc trackDesc");
            exit(1);
        }
        strcpy(options.trackDesc, "ChIP-Seq Custom data");
    }
    if (options.trackColor == NULL) {
        if ((options.trackColor = malloc(12 * sizeof(char))) == NULL) {
            perror("main: malloc trackColor");
            exit(1);
        }
        strcpy(options.trackColor, "100,100,100");
    }
    if (options.extend) {
        int ht_el[2];
        ext_fields = hash_table_new(MODE_COPY);
        if (options.debug)
            fprintf(stderr, " Extend SGA specs: %s\n", options.extField);
        tokens_1 = str_split(options.extField, ',');
        if (tokens_1) {
            for (i = 0; *(tokens_1 + i); i++) {
                tokens_2 = str_split(*(tokens_1 + i), ':');
                if (tokens_2) {
                    for (j = 0; *(tokens_2 + j); j++) {
                        ht_el[j] = atoi(*(tokens_2 + j));
                    }
                    HT_ADD(ext_fields, &ht_el[0], &ht_el[1]);
                    free(*(tokens_2 + j));
                }
                free(*(tokens_1 + i));
            }
            free(tokens_1);
        }
        if (options.debug) {
            table_keys_t table_keys = hash_table_get_keys_as_str(ext_fields);
	    if (NULL == table_keys.keys) {
		    fprintf(stderr, "Hash table has no keys");
	    } else {
		    for (size_t i = 0; i < table_keys.key_num; i++) {
			hash_table_element_t *el = table_keys.keys[i];
			int *key = el->key;
			int *val = NULL;
			val = (int *) HT_LOOKUP(ext_fields, key);
			fprintf(stderr,
				" Ext fields: found (key) SGA field %d -> (val) BED field %d\n",
				*key, *val);
		    }
		    free(table_keys.keys);
	    }
        }
    }
    if (options.debug) {
        fprintf(stderr, " Arguments:\n");
        fprintf(stderr, " Read length (bp): %d\n", options.readLen);
        fprintf(stderr, " 5p entension length (bp): %d\n", options.bkExt);
        fprintf(stderr, " 3p entension length (bp): %d\n", options.fwExt);
        fprintf(stderr, " Score factor : %d\n", options.scoreFact);
        fprintf(stderr, " Extend SGA [on/off]: %d\n", options.extend);
        fprintf(stderr, " Expand SGA [on/off]: %d\n", options.expand);
        fprintf(stderr, " Track name: %s\n", options.trackName);
        fprintf(stderr, " Track description: %s\n", options.trackDesc);
        fprintf(stderr, " Track color: %s\n", options.trackColor);
        fprintf(stderr, "\n");
    }
    /* Build AC hash unless both optind == 1 AND optind == argc */
    /* IOW, unless optind == argc == 1, we build the hash.
       So when does argc equal 1? When the program is called without arguments
       (which in fact implies optind = 1 at this point).
       So the effect is: if called with arguments, build the hash, otherwise exit.

       I'm not quite sure exactly when the AC hash should be built (the help page says
       nothing about that, so I'm inclined to think it should _always_ be built
       (otherwise what sense would it make to build it when a file is passed as
       arg but not when that same file is redirected to stdin?)), but it does seem
       wrong to exit when no args are passed.
     */
    // if (!((optind == 1) && (optind == argc))) {
    if (process_ac() == 0) {
        if (options.debug)
            fprintf(stderr,
                    " HASH Table for chromosome access identifier initialized\n");
    } else {
        return 1;
    }
    //}
    /* This seemed wrong
       else {
       return 1;
       }
     */
    if (process_sga(input, argv[optind++]) != 0) {
        return 1;
    }
    return 0;
}
