#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <getopt.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>

#define PACKAGENAME "i2bits"
#define VERSION     "0.1.0"

static int flags = 0;

enum {
    FLAG_NO_DOTS = 1,
    FLAG_DO_FLIP = 1 << 1
};

static void print_bits(uint64_t);
static void print_help(FILE *);
static int print_int(const char *, int);

int main(int argc, char **argv)
{
    uint64_t num = 0;
    int base = 10;
    char *input;
    char *endptr = NULL;
    int opt;

    enum {
        HELP_OPTION = CHAR_MAX + 1
    };

    static const struct option longopts[] = {
        { "bits2d",  required_argument, NULL, 'd'         },
        { "bits2h",  required_argument, NULL, 'h'         },
        { "version", no_argument,       NULL, 'V'         },
        { "nodots",  no_argument,       NULL, 'n'         },
        { "help",    no_argument,       NULL, HELP_OPTION },
        {  NULL,     0,                 NULL,  0          }
    };

    while ((opt = getopt_long(argc, argv, "nh:d:V", longopts, NULL)) != -1)
    {
         switch (opt) {
         case 'h':
             return print_int(optarg, 16);

         case 'd':
             return print_int(optarg, 10);

         case 'n':
             flags |= FLAG_NO_DOTS;
             break;

         case 'V':
             printf("%s %s\n", PACKAGENAME, VERSION);
             return 0;

         case HELP_OPTION:
             print_help(stdout);
             return 0;

         default:
             print_help(stderr);
             return 1;
         }
    }

    argc -= optind;
    argv += optind;

    if (argc == 0)
    {
        print_help(stderr);
        return 1;
    }

    input = argv[0];

    if (*input == '-' || *(input + 1) == '-')
    {
        fprintf(stderr, "Signed number is not supported\n");
        return 1;
    }

    if (*input == '~')
    {
         input++;
         flags |= FLAG_DO_FLIP;
    }

    if (*input == '0' && tolower(*(input + 1)) == 'x')
        base = 16;

    num = strtoull(input, &endptr, base);
    if (*input  != '\0' && endptr &&
        *endptr == '\0' && errno != ERANGE)
    {
        print_bits(num);
        return 0;
    }

    fprintf(stderr, "Incorrect input\n");

    return 1;
}

static void print_bits(uint64_t num)
{
    int n = 7;

    if (num > UINT8_MAX)
        n = 15;
    if (num > UINT16_MAX)
        n = 31;
    if (num > UINT32_MAX)
        n = 63;

    if (flags & FLAG_DO_FLIP)
         num = ~num;

    for (; n >= 0; n--)
    {
        (num & (1ULL << n)) ? putchar('1') : putchar('0');

        if (flags & FLAG_NO_DOTS)
            continue;

        if ((n % 8 == 0) && (n != 0))
            putchar('.');
    }

    putchar('\n');
}

static int print_int(const char *bits, int base)
{
    uint64_t res = 0;
    size_t blen, idx, n;

    blen = strlen(bits);
    idx = blen - 1;

    if (blen > 64)
    {
        fprintf(stderr, "error: bits length must be <= 64\n");
        return 1;
    }

    for (n = 0; n != blen; n++, idx--)
    {
        if (*(bits + idx) == 0x31)      /* 1 */
            res += 1ULL << n;
        else if (*(bits + idx) == 0x30) /* 0 */
            continue;
        else
        {
            fprintf(stderr, "error: incorrect bit: %c\n", *(bits + idx));
            return 1;
        }
    }

    switch (base) {
    case 10:
        printf("%" PRIu64 "\n", res);
        break;
    case 16:
        printf("0x%" PRIx64 "\n", res);
        break;
    default:
        fprintf(stderr, "Unsupported base\n");
        return 1;
    }

    return 0;
}

static void print_help(FILE *out)
{
    fprintf(out, "Usage:\n  %s <decimal|hexadecimal number>\n\n", PACKAGENAME);
    fputs("Options:\n", out);
    fputs(" -d bits, --bits2d: convert bits to decimal\n", out);
    fputs(" -h bits, --bits2h: convert bits to hexadecimal\n", out);
    fputs(" -n,      --nodots: do not print byte separator (.)\n", out);
    fputs(" -V,   --version:   print version\n", out);
    fputs("       --help:      print help and exit\n", out);
}
