#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "ip_to_geo.h"

#include "ip_data.c"

static const uint8_t* ip_to_geo_rec(uint8_t* ip, unsigned level, const ip_level* root)
{
    uint8_t cur_average;
    const ip_level* cur_ip;
    uint8_t cur_addr;

    while (1)
    {
    start_loop:
        cur_ip = root;
        cur_addr = ip[level];

        // Optimistic search
        if (cur_addr && level != 1)
        {
            cur_average = cur_addr >> root->average;

            while (cur_average-- && cur_ip->next)
                cur_ip = cur_ip->next;
        }
    
#define IP_TEST                                                         \
        {                                                               \
            if (cur_addr >= cur_ip->start && cur_addr <= cur_ip->end)   \
            {                                                           \
                if (cur_ip->childs)                                     \
                {                                                       \
                    level++;                                            \
                    root = cur_ip->childs;                              \
                    goto start_loop;                                    \
                }                                                       \
                else                                                    \
                    return &cur_ip->code;                               \
            }                                                           \
        }
    
        if (cur_addr < cur_ip->start)
        {
            for (cur_ip = cur_ip->prev; cur_ip; cur_ip = cur_ip->prev)
                IP_TEST;
        }
        else if (cur_addr > cur_ip->end)
        {
            for (cur_ip = cur_ip->next; cur_ip; cur_ip = cur_ip->next)
                IP_TEST;
        }
        else
            IP_TEST;

        break;
    }
    return NULL;
}


const uint8_t* ip_to_geo(uint8_t* ip, unsigned ip_size)
{
    const ip_level* first_level;

    if (ip_size == 4)
        first_level = s_root_ipv4[ip[0]];
    else if (ip_size == 16)
        first_level = s_root_ipv6[ip[0]];
    else
        return NULL;

    if (!first_level) return NULL;

    return ip_to_geo_rec(ip, 1, first_level);
}

const uint8_t* get_country_code(const uint8_t* idx)
{
    if (!idx || *idx >= sizeof(country_codes)/sizeof(country_codes[0]))
        return NULL;

    return country_codes[*idx];
}

int interactive(struct gengetopt_args_info* params)
{
    uint8_t ip[16];
    const uint8_t* cc;
    int ret, ip_size=4;

    ret = inet_pton(AF_INET, params->ip_arg, ip);
    if (ret != 1)
    {
        ip_size = 16;
        ret = inet_pton(AF_INET6, params->ip_arg, ip);
        if (ret != 1)
        {
            if (!params->quiet_flag)
                fprintf(stderr, "Invalid IP %s\n", params->ip_arg);
            return -1;
        }
    }

    cc = ip_to_geo(ip, ip_size);

    if (params->quiet_flag)
        printf("%s\n", (cc)?(char*)get_country_code(cc):"<none>");
    else
        printf("IP %s : %s\n", params->ip_arg, (cc)?(char*)get_country_code(cc):"<none>");
    
    return 0;
}

int main(int argc, char** argv)
{
    int ret;
    struct gengetopt_args_info params;
    
    ret = cmdline_parser (argc, argv, &params);

    if (ret) return ret;

    /* self_test(); */

    if (params.ip_given)
        return interactive(&params);
    else if (params.daemon_flag)
        return daemonize(&params);
    else
        cmdline_parser_print_help();

    return 0;
}
