#include "lua-timer.h"

#include <time.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>

#if defined(__APPLE__)
#include <AvailabilityMacros.h>
#include <sys/time.h>
#include <mach/task.h>
#include <mach/mach.h>
#endif

#include "lua.h"
#include "lauxlib.h"

#include "skynet_malloc.h"

typedef void (*timer_execute_func)(void *ud, void *arg);

#define TIME_NEAR_SHIFT 8
#define TIME_NEAR (1 << TIME_NEAR_SHIFT)
#define TIME_LEVEL_SHIFT 6
#define TIME_LEVEL (1 << TIME_LEVEL_SHIFT)
#define TIME_NEAR_MASK (TIME_NEAR - 1)
#define TIME_LEVEL_MASK (TIME_LEVEL - 1)

struct timer_event
{
	short rear;
	int64_t level;
	int64_t near;
};

struct timer_node
{
	struct timer_node *next;
	uint32_t expire;
	uint64_t id;
};

struct link_list
{
	struct timer_node head;
	struct timer_node *tail;
};

struct timer
{
	struct link_list near[TIME_NEAR];
	struct link_list t[4][TIME_LEVEL];
	uint32_t time;
	uint32_t starttime;
	uint64_t current;
	uint64_t current_point;
	uint64_t cnt;
};

static inline struct timer_node *
link_clear(struct link_list *list)
{
	struct timer_node *ret = list->head.next;
	list->head.next = 0;
	list->tail = &(list->head);

	return ret;
}

static inline void
link(struct link_list *list, struct timer_node *node)
{
	list->tail->next = node;
	list->tail = node;
	node->next = 0;
}

static inline void
add_node(struct timer *T, struct timer_node *node)
{
	struct timer_event *tevent = (struct timer_event *)(node + 1);
	// 添加节点
	uint32_t time = node->expire;
	uint32_t current_time = T->time;

	if ((time | TIME_NEAR_MASK) == (current_time | TIME_NEAR_MASK))
	{
		link(&T->near[time & TIME_NEAR_MASK], node);
		tevent->near = time & TIME_NEAR_MASK;
		// printf("near ----%d,%d,%d,%d\n",time,current_time,time|TIME_NEAR_MASK,current_time|TIME_NEAR_MASK);
	}
	else
	{
		int i;
		uint32_t mask = TIME_NEAR << TIME_LEVEL_SHIFT;
		for (i = 0; i < 3; i++)
		{
			if ((time | (mask - 1)) == (current_time | (mask - 1)))
			{
				break;
			}
			mask <<= TIME_LEVEL_SHIFT;
		}
		int64_t level = (time >> (TIME_NEAR_SHIFT + i * TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK;
		tevent->rear = i;
		tevent->level = level;
		// printf("REAR ----%d,%d,| %d，%d\n",time,current_time,level,i);
		link(&T->t[i][level], node);
	}
}

static inline void *
timer_add(struct timer *T, void *arg, size_t sz, int time)
{
	struct timer_node *node = (struct timer_node *)skynet_malloc(sizeof(*node) + sz);
	memcpy(node + 1, arg, sz);
	node->expire = time + T->time;
	// printf(" add == %d, t->time=%d , expire =%d\n",time,T->time,node->expire);
	add_node(T, node);
	return node;
}

static inline void
move_list(struct timer *T, int level, int idx)
{
	struct timer_node *current = link_clear(&T->t[level][idx]);
	while (current)
	{
		struct timer_node *temp = current->next;
		add_node(T, current);
		current = temp;
	}
}

static inline void
timer_shift(struct timer *T)
{
	int mask = TIME_NEAR;
	uint32_t ct = ++T->time;
	if (ct == 0)
	{
		move_list(T, 3, 0);
	}
	else
	{
		uint32_t time = ct >> TIME_NEAR_SHIFT;
		int i = 0;

		while ((ct & (mask - 1)) == 0)
		{
			int idx = time & TIME_LEVEL_MASK;
			if (idx != 0)
			{
				move_list(T, i, idx);
				break;
			}
			mask <<= TIME_LEVEL_SHIFT;
			time >>= TIME_LEVEL_SHIFT;
			++i;
		}
	}
}

static inline void
dispatch_list(lua_State *L, struct timer_node *current, int *tidx)
{
	do
	{
		// 获取数据
		*tidx = *tidx + 1;
		lua_pushinteger(L, current->id);
		lua_rawseti(L, -2, *tidx);
		// printf("dispatch tidx=%d=%d\n ",*tidx,current->id);

		struct timer_node *temp = current;
		current = current->next;
		skynet_free(temp);
	} while (current);
}

static inline void
timer_execute(lua_State *L, struct timer *T, short *hasdata, int *tidx)
{
	int idx = T->time & TIME_NEAR_MASK;
	// printf("exectue ==%d,idx=%d \n",T->time,idx);
	while (T->near[idx].head.next)
	{
		if (*hasdata == 0)
		{
			*hasdata = 1;
			lua_newtable(L);
		}
		struct timer_node *current = link_clear(&T->near[idx]);
		dispatch_list(L, current, tidx);
	}
}

static inline struct timer *
timer_create_timer()
{
	struct timer *r = (struct timer *)skynet_malloc(sizeof(struct timer));
	memset(r, 0, sizeof(*r));

	int i, j;

	for (i = 0; i < TIME_NEAR; i++)
	{
		link_clear(&r->near[i]);
	}

	for (i = 0; i < 4; i++)
	{
		for (j = 0; j < TIME_LEVEL; j++)
		{
			link_clear(&r->t[i][j]);
		}
	}

	r->current = 0;

	return r;
}

// centisecond: 1/100 second
static inline void
systime(uint32_t *sec, uint32_t *cs)
{
#if !defined(__APPLE__) || defined(AVAILABLE_MAC_OS_X_VERSION_10_12_AND_LATER)
	struct timespec ti;
	clock_gettime(CLOCK_REALTIME, &ti);
	*sec = (uint32_t)ti.tv_sec;
	*cs = (uint32_t)(ti.tv_nsec / 10000000);
#else
	struct timeval tv;
	gettimeofday(&tv, NULL);
	*sec = tv.tv_sec;
	*cs = tv.tv_usec / 10000;
#endif
}

static inline uint64_t
gettime()
{
	uint64_t t;
#if !defined(__APPLE__) || defined(AVAILABLE_MAC_OS_X_VERSION_10_12_AND_LATER)
	struct timespec ti;
	clock_gettime(CLOCK_MONOTONIC, &ti);
	t = (uint64_t)ti.tv_sec * 100;
	t += ti.tv_nsec / 10000000;
#else
	struct timeval tv;
	gettimeofday(&tv, NULL);
	t = (uint64_t)tv.tv_sec * 100;
	t += tv.tv_usec / 10000;
#endif
	return t;
}

// for profile

#define NANOSEC 1000000000
#define MICROSEC 1000000

uint64_t
skynet_thread_time(void)
{
#if !defined(__APPLE__) || defined(AVAILABLE_MAC_OS_X_VERSION_10_12_AND_LATER)
	struct timespec ti;
	clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ti);

	return (uint64_t)ti.tv_sec * MICROSEC + (uint64_t)ti.tv_nsec / (NANOSEC / MICROSEC);
#else
	struct task_thread_times_info aTaskInfo;
	mach_msg_type_number_t aTaskInfoCount = TASK_THREAD_TIMES_INFO_COUNT;
	if (KERN_SUCCESS != task_info(mach_task_self(), TASK_THREAD_TIMES_INFO, (task_info_t)&aTaskInfo, &aTaskInfoCount))
	{
		return 0;
	}

	return (uint64_t)(aTaskInfo.user_time.seconds) + (uint64_t)aTaskInfo.user_time.microseconds;
#endif
}

static inline struct timer *
_to_timer(lua_State *L)
{
	struct timer **t = lua_touserdata(L, 1);
	if (t == NULL)
	{
		luaL_error(L, "must be timer object");
	}
	return *t;
}

#define INVALID_VALUE -1
static inline int
_add(lua_State *L)
{
	struct timer *pt = _to_timer(L);
	int second = luaL_checkinteger(L, 2);

	pt->cnt++;
	// 添加计时器
	struct timer_event event;
	// 初始赋值
	event.near = INVALID_VALUE;
	event.level = INVALID_VALUE;
	event.rear = INVALID_VALUE;

	struct timer_node *ptnode = timer_add(pt, &event, sizeof(event), second);
	ptnode->id = pt->cnt; //记录ID

	lua_pushinteger(L, pt->cnt);
	lua_pushlightuserdata(L, ptnode);
	return 2;
}

static inline int
_del(lua_State *L)
{
	struct timer *pt = _to_timer(L);
	struct timer_node *ptnode = lua_touserdata(L, 2);
	struct timer_event *pevent = (struct timer_event *)(ptnode + 1);
	struct link_list *list = 0;
	if (pevent->near != INVALID_VALUE)
	{
		list = &pt->near[pevent->near];
	}
	else if (pevent->rear != INVALID_VALUE && pevent->level != INVALID_VALUE)
	{
		list = &pt->t[pevent->rear][pevent->level];
	}
	short found = 0;
	// 头节点
	struct timer_node *current = link_clear(list);
	while (current)
	{
		if (current == ptnode)
		{ //去掉当前节点,并跳过当前节点,直接遍历下个节点
			found = 1;
			struct timer_node *temp = current->next;
			struct timer_node *removenode = current;
			skynet_free(removenode);
			current = temp;
		}
		else
		{
			struct timer_node *temp = current->next;
			add_node(pt, current);
			current = temp;
		}
	}
	lua_pushboolean(L, found);
	return 1;
}

static int
_new(lua_State *L)
{
	struct timer *pt = timer_create_timer();
	uint32_t current = 0;
	systime(&pt->starttime, &current);
	pt->current = current;
	pt->current_point = gettime();
	pt->cnt = 0;

	struct timer **t = (struct timer **)lua_newuserdata(L, sizeof(struct timer *));
	*t = pt;
	lua_pushvalue(L, lua_upvalueindex(1));
	lua_setmetatable(L, -2);
	return 1;
}

static inline int
_release(lua_State *L)
{
	struct timer *pt = _to_timer(L);
	// printf("collect pt:%p\n", pt);
	skynet_free(pt);
	return 0;
}

static inline int
_inc(lua_State *L)
{
	struct timer *pt = _to_timer(L);
	pt->cnt++;
	lua_pushinteger(L, pt->cnt);
	return 1;
}

static inline int
_update(lua_State *L)
{
	struct timer *pt = _to_timer(L);

	// 返回数据
	int tidx = 0;
	uint64_t cp = gettime();
	if (cp < pt->current_point)
	{
		// printf("time diff error: change from %lld to %lld", cp, pt->current_point);
		pt->current_point = cp;
	}
	else if (cp != pt->current_point)
	{
		uint32_t diff = (uint32_t)(cp - pt->current_point);
		pt->current_point = cp;
		pt->current += diff;
		// printf("current=%d,cpoint=%d,diff=%d,cp=%d\n",pt->current,pt->current_point,diff,cp);
		int i;
		short hasdata = 0;
		for (i = 0; i < diff; i++)
		{
			timer_execute(L, pt, &hasdata, &tidx);
			timer_shift(pt);
			timer_execute(L, pt, &hasdata, &tidx);
		}
	}
	if (tidx == 0)
	{
		lua_pushnil(L);
	}
	return 1;
}

int luaopen_shiftimer_c(lua_State *L)
{
	luaL_checkversion(L);

	luaL_Reg l[] = {
		{"add", _add},
		{"del", _del},
		{"nextid", _inc},
		{"update", _update},
		{NULL, NULL}};

	lua_createtable(L, 0, 2);

	luaL_newlib(L, l);
	lua_setfield(L, -2, "__index");
	lua_pushcfunction(L, _release);
	lua_setfield(L, -2, "__gc");

	lua_pushcclosure(L, _new, 1);
	return 1;
}
