#include <time.h>
#include <string.h>

#include "macro.h"
#include "times.h"
#include "stellar_priv.h"
#include "id_generator.h"

struct id_generator
{
    uint8_t device_base;   // 5bit  [0, 31]
    uint8_t device_offset; // 7bit  [0, 127]
    uint64_t device_id;    // 12bit [0, 4095] ( base << 7 | offset )
    uint64_t thread_volatile[MAX_THREAD_NUM];
};

struct id_generator global_id_generator;

/*
 * device_base   (5bit)  : [0, 31]
 * device_offset (7bit)  : [0, 127]
 * device_id     (12bit) : (base << 7 | offset)
 *
 * return  0: success
 * return -1: failed
 */
int id_generator_init(uint8_t device_base, uint8_t device_offset)
{
    memset(&global_id_generator, 0, sizeof(struct id_generator));

    if (device_base > 31)
    {
        ID_GENERATOR_LOG_ERROR("device_base %u is invalid, range [0, 31]", device_base);
        return -1;
    }

    if (device_offset > 127)
    {
        ID_GENERATOR_LOG_ERROR("device_offset %u is invalid, range [0, 127]", device_offset);
        return -1;
    }

    global_id_generator.device_base = device_base;
    global_id_generator.device_offset = device_offset;
    global_id_generator.device_id = ((global_id_generator.device_base << 7) | global_id_generator.device_offset) & 0xFFF;

    ID_GENERATOR_LOG_DEBUG("device_base: %u, device_offset: %u, device_id: %u",
                           global_id_generator.device_base,
                           global_id_generator.device_offset,
                           global_id_generator.device_id);
    return 0;
}

/*
 * 高位 --- 低位
 *
 * +------+------------------+----------------+------------------------+---------------------------+
 * | 1bit |  12bit device_id | 8bit thread_id | 28bit timestamp in sec | 15bit sequence per thread |
 * +------+------------------+----------------+------------------------+---------------------------+
 */
uint64_t id_generator_alloc()
{
#define MAX_ID_PER_THREAD (32768)
#define MAX_ID_BASE_TIME (268435456L)

    uint64_t thr_idx = stellar_get_current_thread_index();

    uint64_t global_id = 0;
    uint64_t id_per_thread = (global_id_generator.thread_volatile[thr_idx]++) % MAX_ID_PER_THREAD;
    uint64_t id_base_time = stellar_get_real_time_sec() % MAX_ID_BASE_TIME;
    global_id = (global_id_generator.device_id << 51) |
                (thr_idx << 43) |
                (id_base_time << 15) |
                (id_per_thread);

    return global_id;
}
