#ifndef IPRANGE_IPRANGE_H
#define IPRANGE_IPRANGE_H

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#if defined(HAVE_INTTYPES_H)
#include <inttypes.h>
#elif defined(HAVE_STDINT_H)
#include <stdint.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/types.h>
#include <netdb.h>
#include <pthread.h>
#include <unistd.h>

extern int cidr_use_network;
extern int default_prefix;
extern char *PROG;
extern int debug;

/*---------------------------------------------------------------------*/
/* network address type: one field for the net address, one for prefix */
/*---------------------------------------------------------------------*/
typedef struct network_addr {
    in_addr_t addr;
    in_addr_t broadcast;
} network_addr_t;

/*------------------------------------------------------------------*/
/* Set a bit to a given value (0 or 1); MSB is bit 1, LSB is bit 32 */
/*------------------------------------------------------------------*/
static inline in_addr_t set_bit(in_addr_t addr, int bitno, int val) {

    if (val)
        return (addr | (1U << (32 - bitno)));
    else
        return (addr & ~(1U << (32 - bitno)));

}				/* set_bit() */

/*--------------------------------------*/
/* Compute netmask address given prefix */
/*--------------------------------------*/
static inline in_addr_t netmask(int prefix) {

    if (prefix == 0)
        return (~((in_addr_t) - 1));
    else
        return (in_addr_t)(~((1U << (32 - prefix)) - 1U));

}				/* netmask() */

/*----------------------------------------------------*/
/* Compute broadcast address given address and prefix */
/*----------------------------------------------------*/
static inline in_addr_t broadcast(in_addr_t addr, int prefix) {

    return (addr | ~netmask(prefix));

}				/* broadcast() */

/*--------------------------------------------------*/
/* Compute network address given address and prefix */
/*--------------------------------------------------*/
static inline in_addr_t network(in_addr_t addr, int prefix) {

    return (addr & netmask(prefix));

}				/* network() */

/*-----------------------------------------------------------*/
/* Convert an A.B.C.D address into a 32-bit host-order value */
/*-----------------------------------------------------------*/
static inline in_addr_t a_to_hl(char *ipstr, int *err) {
    struct in_addr in;

    if (unlikely(!inet_aton(ipstr, &in))) {
        fprintf(stderr, "%s: Invalid address %s.\n", PROG, ipstr);
        in.s_addr = 0;
        if(err) (*err)++;
        return (ntohl(in.s_addr));
    }

    return (ntohl(in.s_addr));

}				/* a_to_hl() */

/*-----------------------------------------------------------------*/
/* convert a network address char string into a host-order network */
/* address and an integer prefix value                             */
/*-----------------------------------------------------------------*/
static inline network_addr_t str2netaddr(char *ipstr, int *err) {

    int prefix = default_prefix;
    char *prefixstr;
    network_addr_t netaddr;

    if ((prefixstr = strchr(ipstr, '/'))) {
        *prefixstr = '\0';
        prefixstr++;
        errno = 0;
        prefix = atoi(prefixstr);
        if (unlikely(errno || (*prefixstr == '\0') || (prefix < 0) || (prefix > 32))) {
            /* try the netmask format */
            in_addr_t mask = ~a_to_hl(prefixstr, err);
            /*fprintf(stderr, "mask is %u (0x%08x)\n", mask, mask);*/
            prefix = 32;
            while((likely(mask & 0x00000001))) {
                mask >>= 1;
                prefix--;
            }

            if(unlikely(mask)) {
                if(err) (*err)++;
                fprintf(stderr, "%s: Invalid netmask %s\n", PROG, prefixstr);
                netaddr.addr = 0;
                netaddr.broadcast = 0;
                return (netaddr);
            }
        }
    }

    if(likely(cidr_use_network))
        netaddr.addr = network(a_to_hl(ipstr, err), prefix);
    else
        netaddr.addr = a_to_hl(ipstr, err);

    netaddr.broadcast = broadcast(netaddr.addr, prefix);

    return (netaddr);

}

// ----------------------------------------------------------------------------
// Print out a 32-bit address in A.B.C.D/M format
//
// very fast implementation of IP address printing
// this is 30% faster than the system default (inet_ntoa() based)
// http://stackoverflow.com/questions/1680365/integer-to-ip-address-c

static inline char *ip2str_r(char *buf, in_addr_t IP) {
    int i, k;
    for(i = 0, k = 0; i < 4; i++) {
        char c0 = (char)(((((IP & (0xffU << ((3 - i) * 8))) >> ((3 - i) * 8))) / 100U) + 0x30);
        if(c0 != '0') *(buf + k++) = c0;

        char c1 = (char)((((((IP & (0xffU << ((3 - i) * 8))) >> ((3 - i) * 8))) % 100U) / 10U) + 0x30);
        if(!(c1 == '0' && c0 == '0')) *(buf + k++) = c1;

        *(buf + k) = (char)((((((IP & (0xffU << ((3 - i) * 8)))) >> ((3 - i) * 8))) % 10U) + 0x30);
        k++;

        if(i < 3) *(buf + k++) = '.';
    }
    *(buf + k) = 0;

    return buf;
}

#define IP2STR_MAX_LEN 20

#include "ipset.h"
#include "ipset_binary.h"
#include "ipset_combine.h"
#include "ipset_common.h"
#include "ipset_copy.h"
#include "ipset_diff.h"
#include "ipset_exclude.h"
#include "ipset_dns.h"
#include "ipset_load.h"
#include "ipset_merge.h"
#include "ipset_optimize.h"
#include "ipset_print.h"
#include "ipset_reduce.h"

/* operation modes — shared between iprange.c and iprange6_main.c */
#define MODE_COMBINE 1
#define MODE_COMPARE 2
#define MODE_COMPARE_FIRST 3
#define MODE_COMPARE_NEXT 4
#define MODE_COUNT_UNIQUE_MERGED 5
#define MODE_COUNT_UNIQUE_ALL 6
#define MODE_REDUCE 7
#define MODE_COMMON 8
#define MODE_EXCLUDE_NEXT 9
#define MODE_DIFF 10

#endif //IPRANGE_IPRANGE_H
