The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
//
//
//  Created by Alexander Borisov on 22.07.14.
//  Copyright (c) 2014 Alexander Borisov. All rights reserved.
//

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include <yauid.h>

#ifdef ENVIRONMENT32
#error Only 64 bit system
#else

unsigned long yauid_get_inc_id(hkey_t key)
{
    key <<= (BIT_LIMIT_TIMESTAMP + BIT_LIMIT_NODE);
    key >>= (BIT_LIMIT - BIT_LIMIT_INC);
    
    return (unsigned long)(key);
}

unsigned long yauid_get_node_id(hkey_t key)
{
    key <<= BIT_LIMIT_TIMESTAMP;
    key >>= (BIT_LIMIT - BIT_LIMIT_NODE);
    
    return (unsigned long)(key);
}

unsigned long yauid_get_timestamp(hkey_t key)
{
    key >>= (BIT_LIMIT_NODE + BIT_LIMIT_INC);
    
    return (unsigned long)(key);
}

unsigned long long int yauid_get_max_inc()
{
    return NUMBER_LIMIT;
}

unsigned long long int yauid_get_max_node_id()
{
    return NUMBER_LIMIT_NODE;
}

unsigned long long int yauid_get_max_timestamp()
{
    return NUMBER_LIMIT_TIMESTAMP;
}

hkey_t yauid_get_key(yauid* yaobj)
{
    hkey_t key = (hkey_t)(0);
    unsigned int count = 0;
	
    for(;;)
    {
        if((key = yauid_get_key_once(yaobj)) == (hkey_t)(0))
        {
            if(yaobj->error == YAUID_ERROR_KEYS_ENDED)
            {
                count++;
                
                if(yaobj->try_count && count >= yaobj->try_count)
                {
                    yaobj->error = YAUID_ERROR_TRY_COUNT_KEY;
                    break;
                }
                
                usleep(yaobj->sleep_usec);
                continue;
            }
        }
        
        break;
    }
    
    return key;
}

hkey_t yauid_get_key_once(yauid* yaobj)
{
    hkey_t key = (hkey_t)(0), tmp = (hkey_t)(1), ltime = (hkey_t)(0);
    
    yaobj->error = YAUID_OK;
    
    if(flock(yaobj->i_lockfile, LOCK_EX) == -1)
    {
        yaobj->error = YAUID_ERROR_FILE_LOCK;
        return (hkey_t)(0);
    }
    
    if(fseek(yaobj->h_lockfile, 0, SEEK_SET) != 0)
    {
        yaobj->error = YAUID_ERROR_FILE_SEEK;
        return (hkey_t)(0);
    }
    
    if(fread((void *)(&key), sizeof(hkey_t), 1, yaobj->h_lockfile) != 1)
    {
        if(fseek(yaobj->h_lockfile, 0L, SEEK_END) != 0)
        {
            yaobj->error = YAUID_ERROR_FILE_SEEK;
            return (hkey_t)(0);
        }
        
        long h_size = ftell(yaobj->h_lockfile);
        if(h_size > 0)
        {
            yaobj->error = YAUID_ERROR_READ_KEY;
            return (hkey_t)(0);
        }
        
        if(fseek(yaobj->h_lockfile, 0, SEEK_SET) != 0)
        {
            yaobj->error = YAUID_ERROR_FILE_SEEK;
            return (hkey_t)(0);
        }
    }
    
    ltime = time(NULL);
    
    if(key)
    {
        tmp = key >> (BIT_LIMIT_NODE + BIT_LIMIT_INC);
        key <<= (BIT_LIMIT_TIMESTAMP + BIT_LIMIT_NODE);
        key >>= (BIT_LIMIT - BIT_LIMIT_INC);
        
        key++;
        
        if(tmp == ltime)
        {
            if(key > (hkey_t)(NUMBER_LIMIT))
            {
                flock(yaobj->i_lockfile, LOCK_UN);
                
                yaobj->error = YAUID_ERROR_KEYS_ENDED;
                return (hkey_t)(0);
            }
            
            tmp = key;
        }
        else
            tmp = (hkey_t)(1);
    }
    
    key = ltime;
    key <<= BIT_LIMIT_NODE;
    
    key |= yaobj->node_id;
    key <<= BIT_LIMIT_INC;
    
    key |= tmp;
    
    if(fseek(yaobj->h_lockfile, 0, SEEK_SET) != 0)
    {
        yaobj->error = YAUID_ERROR_FILE_SEEK;
        return (hkey_t)(0);
    }
    
    if(fwrite((const void *)(&key), sizeof(hkey_t), 1, yaobj->h_lockfile) != 1)
    {
        yaobj->error = YAUID_ERROR_WRITE_KEY;
        return (hkey_t)(0);
    }
    
    if(fflush(yaobj->h_lockfile) != 0)
    {
        yaobj->error = YAUID_ERROR_FLUSH_KEY;
        return (hkey_t)(0);
    }
    
    if(flock(yaobj->i_lockfile, LOCK_UN) == -1)
    {
        yaobj->error = YAUID_ERROR_FILE_LOCK;
        return (hkey_t)(0);
    }
    
	yaobj->error = YAUID_OK;
	
    return key;
}

yauid * yauid_init(const char *filepath_key, const char *filepath_node_id)
{
    yauid* yaobj = (yauid *)malloc(sizeof(yauid));
    
    if(yaobj)
    {
        yaobj->node_id    = 0;
        yaobj->error      = YAUID_OK;
        yaobj->c_lockfile = filepath_key;
        yaobj->i_lockfile = 0;
        yaobj->h_lockfile = NULL;
        yaobj->try_count  = 0;
        yaobj->sleep_usec = (useconds_t)(35000L);
        
        if(filepath_node_id != NULL)
        {
            if(access( filepath_node_id, F_OK ) != -1)
            {
                FILE* h_node_id;
                if((h_node_id = fopen(filepath_node_id, "rb")))
                {
                    fseek(h_node_id, 0L, SEEK_END);
                    
                    long h_size = ftell(h_node_id);
                    if(h_size <= 0)
                    {
                        fclose(h_node_id);
                        yaobj->error = YAUID_ERROR_FILE_NODE_ID;
                        return yaobj;
                    }
                    
                    fseek(h_node_id, 0L, SEEK_SET);
                    
                    char *text = (char *)malloc(sizeof(char) * (h_size + 1));
                    if(text == NULL)
                    {
                        fclose(h_node_id);
                        yaobj->error = YAUID_ERROR_FILE_NODE_MEM;
                        return yaobj;
                    }
                    
                    fread(text, sizeof(char), h_size, h_node_id);
                    fclose(h_node_id);
                    
                    long i = 0;
                    for(i = 0; i < h_size; i++)
                    {
                        if(text[i] >= '0' && text[i] <= '9')
                            yaobj->node_id = (text[i] - '0') + (yaobj->node_id * 10);
                    }
                    
                    free(text);
                }
            }
            else {
                yaobj->error = YAUID_ERROR_FILE_NODE_EXT;
                return yaobj;
            }
        }
        
        if(access( yaobj->c_lockfile, F_OK ) == -1)
        {
            if((yaobj->h_lockfile = fopen(yaobj->c_lockfile, "ab")) == 0)
            {
                yaobj->error = YAUID_ERROR_CREATE_KEY_FILE;
                return yaobj;
            }
            
            fclose(yaobj->h_lockfile);
        }
        
        if((yaobj->h_lockfile = fopen(yaobj->c_lockfile, "rb+")) == 0)
        {
            yaobj->error = YAUID_ERROR_OPEN_LOCK_FILE;
            return yaobj;
        }
        
        setbuf(yaobj->h_lockfile, NULL);
        
        yaobj->i_lockfile = fileno(yaobj->h_lockfile);
    }
    
    return yaobj;
}

void yauid_destroy(yauid* yaobj)
{
    if(yaobj == NULL)
        return;
    
    if(yaobj->h_lockfile)
        fclose(yaobj->h_lockfile);
    
    free(yaobj);
}

char * yauid_get_error_text_by_code(enum yauid_status error)
{
    if((YAUID_ERROR_TRY_COUNT_KEY - YAUID_OK) < error)
        return NULL;
    
    return error_text[error];
}

void yauid_set_node_id(yauid* yaobj, unsigned long node_id)
{
    yaobj->error = YAUID_OK;
    
    if(node_id < NUMBER_LIMIT_NODE)
    {
        yaobj->node_id = node_id;
        return;
    }
    
    yaobj->error = YAUID_ERROR_LONG_NODE_ID;
}

void yauid_set_sleep_usec(yauid* yaobj, useconds_t sleep_usec)
{
    yaobj->error = YAUID_OK;
    yaobj->sleep_usec = sleep_usec;
}

void yauid_set_try_count(yauid* yaobj, unsigned int try_count)
{
    yaobj->error = YAUID_OK;
    yaobj->try_count = try_count;
}

#endif


typedef yauid * Number__YAUID;

MODULE = Number::YAUID  PACKAGE = Number::YAUID

PROTOTYPES: DISABLE

Number::YAUID
init(perl_class, filepath_key, filepath_node_id)
	char *perl_class;
	SV *filepath_key;
	SV *filepath_node_id;
	
	CODE:
		if(SvOK(filepath_key) && SvOK(filepath_node_id))
		{
			RETVAL = yauid_init((char *)SvPV_nolen(filepath_key), (char *)SvPV_nolen(filepath_node_id));
		}
		else if(SvOK(filepath_key))
		{
			RETVAL = yauid_init((char *)SvPV_nolen(filepath_key), NULL);
		}
		else {
			RETVAL = NULL;
		}
		
	OUTPUT:
		RETVAL

SV*
get_key(obj)
	Number::YAUID obj;
	
	CODE:
		RETVAL = newSViv(yauid_get_key(obj));
		
	OUTPUT:
		RETVAL

SV*
get_key_once(obj)
	Number::YAUID obj;
	
	CODE:
		RETVAL = newSViv(yauid_get_key_once(obj));
		
	OUTPUT:
		RETVAL

void
DESTROY(obj)
	Number::YAUID obj;
	
	CODE:
		yauid_destroy(obj);

SV*
get_error_text_by_code(error_id)
	int error_id;
	
	CODE:
		char * text = yauid_get_error_text_by_code((enum yauid_status)(error_id));
		if(text)
		{
			RETVAL = newSVpv(text, 0);
		}
		else {
			RETVAL = &PL_sv_undef;
		}
		
	OUTPUT:
		RETVAL

SV*
get_error_code(obj)
	Number::YAUID obj;
	
	CODE:
		RETVAL = newSViv(obj->error);
	OUTPUT:
		RETVAL

SV*
set_node_id(obj, node_id)
	Number::YAUID obj;
	unsigned long node_id;
	
	CODE:
		yauid_set_node_id(obj, node_id);
		
		RETVAL = newSViv(YAUID_OK);
	OUTPUT:
		RETVAL

SV*
set_sleep_usec(obj, sleep_usec = 35000)
	Number::YAUID obj;
	size_t sleep_usec;
	
	CODE:
		yauid_set_sleep_usec(obj, (useconds_t)(sleep_usec));
		
		RETVAL = newSViv(YAUID_OK);
	OUTPUT:
		RETVAL

SV*
set_try_count(obj, try_count = 0)
	Number::YAUID obj;
	unsigned int try_count;
	
	CODE:
		yauid_set_try_count(obj, try_count);
		
		RETVAL = newSViv(YAUID_OK);
	OUTPUT:
		RETVAL

SV*
get_timestamp_by_key(obj, hkey)
	Number::YAUID obj;
	SV* hkey;
	
	CODE:
		RETVAL = newSViv( yauid_get_timestamp( (hkey_t)(SvIV(hkey)) ) );
	OUTPUT:
		RETVAL

SV*
get_node_id_by_key(obj, hkey = 0)
	Number::YAUID obj;
	SV* hkey;
	
	CODE:
		RETVAL = newSViv( yauid_get_node_id( (hkey_t)(SvIV(hkey)) ) );
	OUTPUT:
		RETVAL

SV*
get_inc_id_by_key(obj, hkey = 0)
	Number::YAUID obj;
	SV* hkey;
	
	CODE:
		RETVAL = newSViv( yauid_get_inc_id( (hkey_t)(SvIV(hkey)) ) );
	OUTPUT:
		RETVAL

SV*
get_max_inc()
	CODE:
		RETVAL = newSViv( yauid_get_max_inc() );
	OUTPUT:
		RETVAL

SV*
get_max_node_id()
	CODE:
		RETVAL = newSViv( yauid_get_max_node_id() );
	OUTPUT:
		RETVAL

SV*
get_max_timestamp()
	CODE:
		RETVAL = newSViv( yauid_get_max_timestamp() );
	OUTPUT:
		RETVAL