/*
** beeversion - compare bee package versionnumbers
**
** Copyright (C) 2009-2016
**       Marius Tolzmann <m@rius.berlin>
**       Tobias Dreyer <dreyer@molgen.mpg.de>
**       and other bee developers
**
** This file is part of bee.
**
** bee 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/>.
*/

#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
#include <errno.h>

#include "bee_version.h"
#include "bee_version_parse.h"
#include "bee_version_compare.h"
#include "bee_version_output.h"

#define TEST_BITS 3
#define TYPE_BITS 2

#define USED_BITS (TEST_BITS+TYPE_BITS)

#define TEST_TYPE_MASK           (((1<<TYPE_BITS)-1)<<TEST_BITS)

#define TEST_MASK                ((1<<TEST_BITS)-1)

#define TEST_FULL_MASK           (TEST_TYPE_MASK|TEST_MASK)

#define TEST_WITH_2_ARGS         (1<<TEST_BITS)
#define TEST_WITH_1_OR_MORE_ARGS (2<<TEST_BITS)
#define TEST_WITH_2_OR_MORE_ARGS (3<<TEST_BITS)

#define T_LESS_THAN     0
#define T_LESS_EQUAL    1
#define T_GREATER_THAN  2
#define T_GREATER_EQUAL 3
#define T_EQUAL         4
#define T_NOT_EQUAL     5

#define T_IS_MAX     0
#define T_IS_MIN     1
#define T_IS_NOT_MAX 2
#define T_IS_NOT_MIN 3

#define T_MAX 0
#define T_MIN 1

#define OPT_FORMAT   128
#define OPT_KEYVALUE 129
#define OPT_VERSION  130
#define OPT_HELP     131
#define OPT_FILTER_PKGFULLNAME 132

#define MODE_TEST   1
#define MODE_PARSE  2

char *filter_pkgfullname = NULL;

int compare_beeversions(struct beeversion *, struct beeversion *);
char parse_extra(struct beeversion *);

void print_version(void) {
    printf("beeversion v%d.%d.%d - "
           "by Marius Tolzmann <m@rius.berlin> 2010-2016\n", 
           BEEVERSION_MAJOR, BEEVERSION_MINOR, BEEVERSION_PATCHLVL);
}

void print_full_usage(void) {

    printf("usage:\n\n");
    
    
    printf("   test: beeversion <packageA> -{lt|le|gt|ge|eq|ne} <packageB>\n");
    printf(" filter: beeversion [filter-options] -{min|max} <package1> [.. <packageN>]\n");
    printf("  parse: beeversion [parse-options] <package>\n\n");
    
    printf("         package := <pkgfullname>-<pkgfullversion>-<pkgrevision>\n");
    printf("                  | <pkgfullname>-<pkgfullversion>\n");
    printf("                  | <pkgfullversion>\n\n");
    
    printf("     pkgfullname := <pkgname>\n");
    printf("                  | <pkgname>_<pkgsubname>\n\n");
    
    printf("  pkgfullversion := <pkgversion>\n");
    printf("                  | <pkgversion>_<pkgextraversion>\n\n");
    
    printf("     pkgrevision := <pkgrevision>\n");
    printf("                  | <pkgrevision>.<arch>\n\n");
    
    printf("   filter-options:\n\n");

    printf("      --filter-pkgfullname=<pkgfullname>\n\n");
    
}

static int ends_with(char *string,char *postfix)
{
    size_t string_len;
    size_t postfix_len;

    string_len=strlen(string);
    postfix_len=strlen(postfix);

    if (postfix_len<=string_len) {
        return strcmp(&string[string_len-postfix_len],postfix)==0;
    }
    return 0;
}

int scan_be0_version(char *filename,struct beeversion *versionsnummer)
{
    FILE *file=NULL;
    char *line=NULL;
    size_t linebufsize=0;
    int ok=0;
    int next;
    int lineno=0;
    int p;
    char *version=NULL;

    file=fopen(filename,"r");
    if (!file) {
        perror(filename);
        goto out;
    }
    while(1) {
        if (getline(&line,&linebufsize,file)<0) {
            if(errno) {
                perror(filename);
            } else {
                fprintf(stderr,"%s : BEE_VERSION missing from file\n",filename);
            }
            goto out;
        }
        lineno++;
        sscanf(line," # BEE_VERSION%ms %n",&version,&next);
        if (version) {
            if (line[next] != '\n' && line[next] != '\0') {
                fprintf(stderr,"%s line %d : syntax error (too many words after BEE_VERSION)\n",filename,lineno);
                goto out;
            }
            if((p=parse_version(version, versionsnummer))) {
                fprintf(stderr, "%s line %d :  syntax error at position %d in '%s'\n",filename,lineno,p,version);
                goto out;
            }
            ok=1;
            goto out;
        }
    }
out:
    if (version)
        free(version);
    if (line)
        free(line);
    if (file)
        fclose(file);
    return ok ? 1 : 0;
}

int parse_argument(char* text, struct beeversion *versionsnummer)
{
    int p;

    if (ends_with(text,".be0")) {
        return scan_be0_version(text,versionsnummer);
    }

    if((p=parse_version(text, versionsnummer))) {
        fprintf(stderr, "beeversion: syntax error at position %d in '%s'\n", p, text);
        return(0);
    }
    return(1);
}

static int compare_beepackages_gen(const void *a, const void *b) {
    return((int)compare_beepackages((struct beeversion *)a, (struct beeversion *)b));
}

int do_test(int argc, char *argv[], char test) {
    int i;
    
    struct beeversion v[2];
    struct beeversion *a, *b, *va;
    
    int ret;
    char t;
    
    a = &v[0];
    b = &v[1];
    
    t = (test & TEST_MASK);
    
    if((test & TEST_TYPE_MASK) == TEST_WITH_2_ARGS) {
        if(argc != 2) {
            fprintf(stderr, "usage: beeversion <packageA> -[lt|le|gt|ge|eq|ne] <packageB>\n");
            return(255);
        }
        
        for(i=0; i<2; i++) {
            if(!parse_argument(argv[i], &v[i]))
               return(0);
        }
        
        ret = compare_beeversions(a, b);

        free(a->string);
        free(b->string);

        switch(t) {
            case T_LESS_THAN:
                return(ret < 0);
            case T_LESS_EQUAL:
                return(ret <= 0);
            case T_GREATER_THAN:
                return(ret > 0);
            case T_GREATER_EQUAL:
                return(ret >= 0);
            case T_EQUAL:
                return(ret == 0);
            case T_NOT_EQUAL:
                return(ret != 0);
        }
        fprintf(stderr, "beeversion: YOU HIT A BUG #004\n");
    }
    
    /* min / max */
    if((test & TEST_TYPE_MASK) == TEST_WITH_2_OR_MORE_ARGS) {
        
        if(argc < 1) {
            fprintf(stderr, "usage: beeversion -[min|max] <package1> [<package2> .. <packageN>]\n");
            return(255);
        }
        
        if(!(va = calloc(sizeof(struct beeversion), argc))) {
            perror("va=calloc()");
            exit(255);
        }
        
        for(i=0;i<argc;i++) {
            if(!parse_argument(argv[i], va+i))
                return(0);
        }
        
        qsort(va, argc, sizeof(struct beeversion), compare_beepackages_gen);
        
        for(a=va,i=1;i<argc;i++) {
            b=va+i;
            
            /* a != b */
            if(compare_beepackage_names(a, b)) {
                print_format("%A\n", a, filter_pkgfullname);
                a = b;
            }
            
            if(t == T_MAX) 
               a = b;
        }
        print_format("%A\n", a, filter_pkgfullname);

        for(i=0;i<argc;i++) {
            free(va[i].string);
        }

        free(va);
        return(1);
    }
    
    fprintf(stderr, "beeversion: YOU HIT A BUG #006\n");
    
    return(0);
}

int do_parse(int argc, char *argv[], char *format) {
    struct beeversion v;
    
    if(argc != 1) {
        fprintf(stderr, "usage: beeversion <package>\n"); 
        return(255);
    }
    
    if(!parse_argument(argv[0], &v))
        return(0);
    
    print_format(format, &v, filter_pkgfullname);

    free(v.string);

    return(1);
}

int main(int argc, char *argv[])
{
    int option_index = 0;
    int c = 0;
        
    char test_to_do   = 0;
    char *format      = NULL;
    int  test_index   = 0;
    int  build_format = 0;
    char mode         = 0;
    
    char *keyvalue;
    
    keyvalue = "PKGNAME=%p\n"
               "PKGEXTRANAME=%x\n"
               "PKGEXTRANAME_UNDERSCORE=%_x\n"
               "PKGEXTRANAME_DASH=%-x\n"
               "PKGVERSION=( @v )\n"
               "PKGEXTRAVERSION=%e\n"
               "PKGEXTRAVERSION_UNDERSCORE=%_e\n"
               "PKGEXTRAVERSION_DASH=%-e\n"
               "PKGREVISION=%r\n"
               "PKGARCH=%a\n"
               "PKGFULLNAME=%P\n"
               "PKGFULLVERSION=%V\n"
               "PKGFULLPKG=%F\n"
               "PKGALLPKG=%A\n"
               "PKGSUFFIX=%s\n";

    struct option long_options[] = {
        /* tests  with 2 args */
        {"lt",    no_argument, 0, TEST_WITH_2_ARGS|T_LESS_THAN},
        {"le",    no_argument, 0, TEST_WITH_2_ARGS|T_LESS_EQUAL},
        {"gt",    no_argument, 0, TEST_WITH_2_ARGS|T_GREATER_THAN},
        {"ge",    no_argument, 0, TEST_WITH_2_ARGS|T_GREATER_EQUAL},
        {"eq",    no_argument, 0, TEST_WITH_2_ARGS|T_EQUAL},
        {"ne",    no_argument, 0, TEST_WITH_2_ARGS|T_NOT_EQUAL},

        /* tests with optarg and 1 or more args */
        /*
        {"ismax",    required_argument, 0, TEST_WITH_1_OR_MORE_ARGS|T_IS_MAX},
        {"ismin",    required_argument, 0, TEST_WITH_1_OR_MORE_ARGS|T_IS_MIN},
        {"isnotmax", required_argument, 0, TEST_WITH_1_OR_MORE_ARGS|T_IS_NOT_MAX},
        {"isnotmin", required_argument, 0, TEST_WITH_1_OR_MORE_ARGS|T_IS_NOT_MIN},
        */
        /* filter with 2 or more args.. */
        {"max",   no_argument, 0, TEST_WITH_2_OR_MORE_ARGS|T_MAX},
        {"min",   no_argument, 0, TEST_WITH_2_OR_MORE_ARGS|T_MIN},

        /* normal parse mode */
        {"format",   required_argument, 0, OPT_FORMAT},
        /*
        {"keyvalue",       no_argument, 0, OPT_KEYVALUE},
        */
        
        /*  */
        {"pkgfullname",    no_argument,  0, 'P'},
        {"pkgfullversion", no_argument,  0, 'V'},
        {"pkgfullpkg",     no_argument,  0, 'F'},
        {"pkgallpkg",      no_argument,  0, 'A'},

        {"pkgname",         no_argument, 0, 'p'},
        {"pkgarch",         no_argument, 0, 'a'},
        {"pkgversion",      no_argument, 0, 'v'},
        {"pkgextraversion", no_argument, 0, 'e'},
        {"pkgrevision",     no_argument, 0, 'r'},
        {"pkgsuffix",       no_argument, 0, 's'},
        
        {"pkgextraname",    no_argument, 0, 'x'},
        {"pkgsubname",      no_argument, 0, 'x'},
        
        {"filter-pkgfullname", required_argument, 0, OPT_FILTER_PKGFULLNAME},
        
        {"version",     no_argument, 0, OPT_VERSION},
        {"help",        no_argument, 0, OPT_HELP},

        {0, 0, 0, 0}
    };
    
    while ((c = getopt_long_only(argc, argv, "PAVFpaversx", long_options, &option_index)) != -1) {
    
        if( (c & TEST_TYPE_MASK) && ! (c & ~TEST_FULL_MASK)) {
            if(mode && mode == MODE_PARSE) {
                fprintf(stderr, "beeversion: skipping test-option --%s since already running in parse mode\n",
                          long_options[option_index].name);
                continue;
            }
            if(test_to_do) {
                fprintf(stderr, "beeversion: skipping test-option --%s since --%s is already set\n",
                          long_options[option_index].name, long_options[test_index].name);
                continue;
            }
            mode       = MODE_TEST;
            test_index = option_index;
            test_to_do = c;
            continue;
        }
        
        if(c == OPT_FILTER_PKGFULLNAME) {
            filter_pkgfullname = optarg;
            continue;
        }
        
        if(mode && mode == MODE_TEST) {
            fprintf(stderr, "beeversion: skipping parse-option --%s since already running in test mode\n",
                      long_options[option_index].name);
            continue;
        }
        
        mode = MODE_PARSE;
        
        /* define format */
        if((c >= 'A' && c <= 'z')) {
            if(format && ! build_format) {
                fprintf(stderr, "beeversion: --%s ignored\n", long_options[option_index].name);
                continue;
            }
            
            if(!format) {
                format = calloc(sizeof(char), argc * 3 + 2);
                if(!format) {
                    perror("calloc(format)");
                    exit(255);
                }
            }
            
            if(build_format)
                format[build_format++] = ' ';
                
            format[build_format++] = '%';
            format[build_format++] = c;
            continue;
        }
        
        if(c == OPT_FORMAT) {
            if(format) {
                fprintf(stderr, "beeversion: --%s ignored\n", long_options[option_index].name);
                continue;
            }
            format = optarg;
            continue;
        }
        
        if(c == OPT_KEYVALUE) {
            if(format) {
                fprintf(stderr, "beeversion: --%s ignored\n", long_options[option_index].name);
                continue;
            }
            format = keyvalue;
            continue;
        }
        
        if(c == OPT_HELP) {
            printf("\n");
            print_version();
            printf("\n");
            print_full_usage();
            exit(0);
        }
        
        if(c == OPT_VERSION) {
            print_version();
            exit(0);
        }
        
        if(opterr)
           continue;
        
        fprintf(stderr, "beeversion: YOU HIT A BUG #003 opterr=%d\n", opterr);
    }  /* end while getopt_long_only */
    
    if(build_format)
        format[build_format++] = '\n';
    
    if(mode == MODE_TEST) 
        return(!do_test(argc-optind, argv+optind, test_to_do));
    
    if(!mode || mode == MODE_PARSE) {
        if(!format)
            format = keyvalue;
        
        filter_pkgfullname = NULL;
        
        return(!do_parse(argc-optind, argv+optind, format));
    }
        
    return(0);
}