#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "cngeoip.h"


// #define CNGEOIP_USE_1251

#define MAX_LEVEL_SIZE_COUNT 33
#define MAX_SPLITS 7


int cngeoip4array(char *dbfile, struct cngeoip4result *res) {
    int c4t_res;
    char info[CNGEOIP_MAX_INFO_LEN];
    char *next_part, *part;
    char *splits[MAX_SPLITS];
    char *pipe;
    int i;
    
    res->cnt = 0;
    res->error = cngeoip4text(res->ip, dbfile, info, CNGEOIP_MAX_INFO_LEN);
    if ( res->error ) {
#ifdef DEBUG
        printf("returning error %i.\n", res->error);
#endif
        return res->error;
    }
    
#ifdef DEBUG
    printf("Info:(%s)\n", info);
#endif
    
    next_part = strchr(info, '!');
    if ( next_part )
        *next_part = 0;
    strncpy(res->special, info, CNGEOIP_STR_LEN);
    for ( res->cnt=0; next_part && res->cnt<MAX_PLACES_PER_IP; res->cnt++) {
        part = next_part + 1;
        next_part = strchr(part, '!');
        if ( next_part )
            *next_part = 0;
#ifdef DEBUG	
	printf("part #%i:(%s)\n", res->cnt, part);
#endif
	for(splits[0] = part, i=1; i<MAX_SPLITS; i++) {
	    pipe = strchr(splits[i-1], '|');
	    if ( pipe ) {
	        *pipe = 0;
	        splits[i] = pipe+1;
	    }
	    else {
	        splits[i] = splits[i-1] + strlen(splits[i-1]);
	    }
#ifdef DEBUG	
	    printf("split #%i (%s)\n", i, splits[i]);
#endif
	}
	res->places[res->cnt].type = splits[0][0];
	strncpy(res->places[res->cnt].name_nat, splits[1], CNGEOIP_STR_LEN);
	strncpy(res->places[res->cnt].name_eng, splits[2], CNGEOIP_STR_LEN);
	strncpy(res->places[res->cnt].name_rus, splits[3], CNGEOIP_STR_LEN);
	res->places[res->cnt].geo_lon = atof(splits[4]);
	res->places[res->cnt].geo_lat = atof(splits[5]);
	strncpy(res->places[res->cnt].name_code, splits[6], CNGEOIP_STR_SHORT_LEN);
    }
    return 0;
}


int cngeoip4text(unsigned long ip, char *dbfile, char *text, int text_len) {
    FILE *fp;
    int shift, l, length, i;
    unsigned long offset, index, tell, unp, gxor;
    unsigned char data[4];
    int LEVEL_SIZE[MAX_LEVEL_SIZE_COUNT];
    unsigned long first_xor_code;
    unsigned char metric[4];
    char info[CNGEOIP_MAX_INFO_LEN];
    unsigned char *ufp, *utp;

    fp = fopen(dbfile, "r");
    if ( !fp )
	return ENOENT;

    fread(metric, 4, 1, fp);
    if ( metric[0]*0x10000 + metric[1]*0x100 + metric[2] != 0xcd301b )
        return EBADMSG;
    if ( metric[3] != 0x01 )
        return EBADMSG;
    
    for (i=0; i<MAX_LEVEL_SIZE_COUNT-1; i+=2) {
        fread(metric,1,1,fp);
        LEVEL_SIZE[i] = metric[0] / 0x10;
        LEVEL_SIZE[i+1] = metric[0] & 0x0f;
        if ( !LEVEL_SIZE[i] || !LEVEL_SIZE[i+1] )
            break;
    }
    
    first_xor_code = 1;
    
    for(i=0, l=32; LEVEL_SIZE[i]; i++) {
	first_xor_code <<= LEVEL_SIZE[i];
	first_xor_code += l;
	first_xor_code = ((first_xor_code >> 11) & 0x001fffff) + ( first_xor_code << 11);
	l -= LEVEL_SIZE[i];
    }
    
    shift = 32;
    offset = ftell(fp);
    
    for ( l=0; LEVEL_SIZE[l]; l++ ) {
	shift -= LEVEL_SIZE[l];
	index = ((ip>>shift)) & ((1<<LEVEL_SIZE[l])-1);
	tell = offset+index*4;
	fseek(fp, tell, 0);
	fread(data, 4, 1, fp);
	unp = data[3] + (data[2]<<8) + (data[1]<<16) + (data[0]<<24);
	offset = unp ^ first_xor_code ^ tell;
	if ( offset & 0x80000000 ) {
	    tell = offset & 0x7fffffff;
	    fseek(fp, tell, 0);
	    fread(data, 2, 1, fp);
	    unp = data[1] + (data[0]<<8);
	    length = ( unp ^ first_xor_code ^ tell ) &0xffff;
	    tell += 2;
	    if ( length > 0 ) {
		if ( length > CNGEOIP_MAX_INFO_LEN-1 )
		    length = CNGEOIP_MAX_INFO_LEN-1;
		fread(info, length, 1, fp);
		info[length] = 0;
		gxor = tell ^ first_xor_code;
		for ( i=0; i<length; i++ ) {
		    info[i] = info[i] ^ gxor;
		    gxor = ( (gxor >> 3) &0x1fffffff) ^ ( (gxor & 0x1fffffff) << 3);
		}
#ifdef CNGEOIP_USE_1251
		for ( ufp=(unsigned char*)info, utp=(unsigned char*)info; *ufp; ufp++, utp++) {
		    if ( *ufp < 0x80 ) {
			*utp = *ufp;
		    }
		    else {
			if ( ufp[0] == 0xd0 ) *utp = ufp[1] + 0x30;
			else if ( *ufp == 0xd1 ) *utp = ufp[1] + 0x70;
			else *utp = '_';
			ufp++;
		    }
		}
		*utp = 0;
#endif
	    }
	    else
		info[0] = 0;
	    strncpy(text, info, text_len);
	    break;
	}
    }

    fclose(fp);

    return 0;
}


int cngeoip_str2ip(char *ipstr, unsigned long *ip) {
//
//	returns:
//		-1 if one of octets is more than 255
//		-2 if symbol other than 0-9 and dot is detected
//
    int i;
    *ip = 0;
    int octet = 0;
    
    for ( i=0; ipstr[i]; i++ ) {
	if ( ipstr[i]>='0' && ipstr[i]<='9' ) {
	    octet *= 10;
	    octet += ipstr[i] - '0';
	    if ( octet > 255 ) {
		return -1;
	    }
	}
	else if ( ipstr[i] == '.' ) {
	    *ip <<= 8;
	    *ip += octet;
	    octet = 0;
	}
	else return -2;
    }
    *ip <<= 8;
    *ip += octet;

    return 0;
}


int cngeoip_lookup(char *dbfile, struct cngeoip_result *oldres) {
    struct cngeoip4result res;
    int i;
    
    res.ip = oldres->ip;
    *oldres->city_rus = 0;
    *oldres->city_eng = 0;
    *oldres->country_code = 0;
    *oldres->country_rus = 0;
    *oldres->country_eng = 0;
    *oldres->region_rus = 0;
    *oldres->region_eng = 0;
    oldres->geo_lon = 0;
    oldres->geo_lat = 0;
    cngeoip4array(dbfile, &res);
    
    if ( res.cnt < 0 )
        return 0;
    
    for (i=0; i<res.cnt; i++) {
        switch ( res.places[i].type ) {
            case 'c':
		strncpy(oldres->country_code, res.places[i].name_code, CNGEOIP_STR_LEN);
		strncpy(oldres->country_eng, res.places[i].name_eng, CNGEOIP_STR_LEN);
		strncpy(oldres->country_rus, res.places[i].name_rus, CNGEOIP_STR_LEN);
                break;
            case 'r':
		strncpy(oldres->region_eng, res.places[i].name_eng, CNGEOIP_STR_LEN);
		strncpy(oldres->region_rus, res.places[i].name_rus, CNGEOIP_STR_LEN);
                break;
            case 't':
		strncpy(oldres->city_eng, res.places[i].name_eng, CNGEOIP_STR_LEN);
		strncpy(oldres->city_rus, res.places[i].name_rus, CNGEOIP_STR_LEN);
		oldres->geo_lon = res.places[i].geo_lon;
		oldres->geo_lat = res.places[i].geo_lat;
                break;
        }
    }
    return 0;
}

