/**
 * TelEngine.cpp
 * This file is part of the YATE Project http://YATE.null.ro
 *
 * Yet Another Telephony Engine - a fully featured software PBX and IVR
 * Copyright (C) 2004-2014 Null Team
 *
 * This software is distributed under multiple licenses;
 * see the COPYING file in the main directory for licensing
 * information for this specific distribution.
 *
 * This use of this software may be subject to additional restrictions.
 * See the LEGAL file in the main directory for details.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

#include "yateclass.h"

#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <time.h>


#ifdef _WINDOWS

#ifndef HAVE_GMTIME_S
#include <errno.h>

static int _gmtime_s(struct tm* _tm, const time_t* time)
{
    static TelEngine::Mutex m(false,"_gmtime_s");
    struct tm* tmp;
    if (!_tm)
	return EINVAL;
    _tm->tm_isdst = _tm->tm_yday = _tm->tm_wday = _tm->tm_year = _tm->tm_mon = _tm->tm_mday =
	_tm->tm_hour = _tm->tm_min = _tm->tm_sec = -1;
    if (!time)
	return EINVAL;
    m.lock();
    tmp = gmtime(time);
    if (!tmp) {
	m.unlock();
	return EINVAL;
    }
    *_tm = *tmp;
    m.unlock();
    return 0;
}

static int _localtime_s(struct tm* _tm, const time_t* time)
{
    static TelEngine::Mutex m(false,"_localtime_s");
    struct tm* tmp;
    if (!_tm)
	return EINVAL;
    _tm->tm_isdst = _tm->tm_yday = _tm->tm_wday = _tm->tm_year = _tm->tm_mon = _tm->tm_mday =
	_tm->tm_hour = _tm->tm_min = _tm->tm_sec = -1;
    if (!time)
	return EINVAL;
    m.lock();
    tmp = localtime(time);
    if (!tmp) {
	m.unlock();
	return EINVAL;
    }
    *_tm = *tmp;
    m.unlock();
    return 0;
}

#endif

#else // !_WINDOWS
#include <sys/resource.h>
#endif

namespace { // anonymous

class ObjCounterList : public TelEngine::ObjList
{
public:
    inline ObjCounterList()
	: m_invalid(false)
	{ }
    ~ObjCounterList()
	{ m_invalid = true; }
    inline bool invalid() const
	{ return m_invalid; }
private:
    bool m_invalid;
};

}; // anonymous namespace

namespace TelEngine {

#define DebugMin DebugFail
#define DebugVis DebugConf
#define DebugDef DebugNote
#define DebugMax DebugAll

#define OUT_BUFFER_SIZE 8192
#define OUT_HEADER_SIZE 112

// RefObject mutex pool array size
#ifndef REFOBJECT_MUTEX_COUNT
#define REFOBJECT_MUTEX_COUNT 47
#endif

// Number of seconds from 1900 to 1970
#define SECONDS_1900_TO_1970 2208988800u // 0x83AA7E80
// UNIX time of NTP time 07 Feb. 2036 6h 28m 16s
// NTP time will overflow at this UNIX time
#define UNIX_NTP_MAX 2085978496u         // 0x7C558180

static int s_debug = DebugDef;
static int s_indent = 0;
static bool s_debugging = true;
static bool s_abort = false;
static u_int64_t s_startTime = 0;
static u_int64_t s_timestamp = 0;
static Debugger::Formatting s_fmtstamp = Debugger::None;

static const char* const s_colors[11] = {
    "\033[5;41;1;33m\033[K",// DebugFail - blinking yellow on red
    "\033[44;1;37m\033[K",  // DebugTest - white on blue
    "\033[41;1;37m\033[K",  // DebugCrit - white on red
    "\033[41;37m\033[K",    // DebugConf - gray on red
    "\033[40;31m\033[K",    // DebugStub - red on black
    "\033[40;1;31m\033[K",  // DebugWarn - light red on black
    "\033[40;1;33m\033[K",  // DebugMild - yellow on black
    "\033[40;1;32m\033[K",  // DebugNote - light green on black
    "\033[40;1;37m\033[K",  // DebugCall - white on black
    "\033[40;1;36m\033[K",  // DebugInfo - light cyan on black
    "\033[40;36m\033[K"     // DebugAll  - cyan on black
};

static const char* const s_levels[11] = {
    "FAIL",
    "TEST",
    "CRIT",
    "CONF",
    "STUB",
    "WARN",
    "MILD",
    "NOTE",
    "CALL",
    "INFO",
    "ALL",
};

static const char* dbg_level(int& level)
{
    if (level < DebugMin)
	level = DebugMin;
    if (level > DebugMax)
	level = DebugMax;
    return s_levels[level];
}

static void dbg_stderr_func(const char* buf, int level)
{
    YIGNORE(::write(2,buf,::strlen(buf)));
}

static void dbg_colorize_func(const char* buf, int level)
{
    const char* col = debugColor(level);
    YIGNORE(::write(2,col,::strlen(col)));
    YIGNORE(::write(2,buf,::strlen(buf)));
    col = debugColor(-2);
    YIGNORE(::write(2,col,::strlen(col)));
}

static void (*s_output)(const char*,int) = dbg_stderr_func;
static void (*s_intout)(const char*,int) = 0;
static void (*s_alarms)(const char*,int,const char*,const char*) = 0;
static void (*s_relay)(int,const char*,const char*,const char*) = 0;

static Mutex out_mux(false,"DebugOutput");
static Mutex ind_mux(false,"DebugIndent");
static Thread* s_thr = 0;

bool CapturedEvent::s_capturing = false;
ObjList CapturedEvent::s_events;

static bool reentered()
{
    if (!s_thr)
	return false;
    return (Thread::current() == s_thr);
}

static void common_output(int level,char* buf)
{
    if (level < -1)
	level = -1;
    if (level > DebugMax)
	level = DebugMax;
    int n = ::strlen(buf);
    if (n && (buf[n-1] == '\n'))
	n--;
    // serialize the output strings
    out_mux.lock();
    // TODO: detect reentrant calls from foreign threads and main thread
    s_thr = Thread::current();
    if (CapturedEvent::capturing()) {
	buf[n] = '\0';
	bool save = s_debugging;
	s_debugging = false;
	CapturedEvent::append(level,buf);
	s_debugging = save;
    }
    buf[n] = '\n';
    buf[n+1] = '\0';
    if (s_output)
	s_output(buf,level);
    if (s_intout)
	s_intout(buf,level);
    buf[n] = '\0';
    s_thr = 0;
    out_mux.unlock();
}

static void dbg_output(int level,const char* prefix, const char* format, va_list ap,
    const char* alarmComp = 0, const char* alarmInfo = 0)
{
    void (*relay)(int,const char*,const char*,const char*) = s_relay;
    void (*alarms)(const char*,int,const char*,const char*) = s_alarms;
    bool out = (s_output || s_intout || relay) && (prefix || format);
    bool alarm = alarmComp && format && (alarms || relay);
    if (!(out || alarm))
	return;
    char buf[OUT_BUFFER_SIZE];
    unsigned int n = Debugger::formatTime(buf,s_fmtstamp);
    unsigned int l = s_indent*2;
    if (l >= sizeof(buf)-n)
	l = sizeof(buf)-n-1;
    ::memset(buf+n,' ',l);
    n += l;
    buf[n] = 0;
    l = sizeof(buf)-n-2;
    if (prefix)
	::strncpy(buf+n,prefix,l);
    n = ::strlen(buf);
    l = sizeof(buf)-n-2;
    char* msg = buf+n;
    if (format) {
	::vsnprintf(msg,l,format,ap);
	buf[OUT_BUFFER_SIZE - 2] = 0;
    }
    if (relay) {
	relay(level,buf,alarmComp,alarmInfo);
	return;
    }
    if (out)
	common_output(level,buf);
    if (alarm)
	alarms(msg,level,alarmComp,alarmInfo);
}

void Output(const char* format, ...)
{
    char buf[OUT_BUFFER_SIZE];
    void (*relay)(int,const char*,const char*,const char*) = s_relay;
    if (!((s_output || s_intout || relay) && format && *format))
	return;
    if (reentered())
	return;
    va_list va;
    va_start(va,format);
    ::vsnprintf(buf,sizeof(buf)-2,format,va);
    va_end(va);
    if (relay)
	relay(-1,buf,0,0);
    else
	common_output(-1,buf);
}

void Debug(int level, const char* format, ...)
{
    if (!s_debugging)
	return;
    if (level > s_debug || level < DebugMin)
	return;
    if (reentered())
	return;
    if (!format)
	format = "";
    char buf[32];
    ::sprintf(buf,"<%s> ",dbg_level(level));
    va_list va;
    va_start(va,format);
    ind_mux.lock();
    dbg_output(level,buf,format,va);
    ind_mux.unlock();
    va_end(va);
    if (s_abort && (level == DebugFail))
	abort();
}

void Debug(const char* facility, int level, const char* format, ...)
{
    if (!s_debugging)
	return;
    if (level > s_debug || level < DebugMin)
	return;
    if (reentered())
	return;
    if (!format)
	format = "";
    char buf[OUT_HEADER_SIZE];
    ::snprintf(buf,sizeof(buf),"<%s:%s> ",facility,dbg_level(level));
    va_list va;
    va_start(va,format);
    ind_mux.lock();
    dbg_output(level,buf,format,va);
    ind_mux.unlock();
    va_end(va);
    if (s_abort && (level == DebugFail))
	abort();
}

void Debug(const DebugEnabler* local, int level, const char* format, ...)
{
    if (!s_debugging)
	return;
    const char* facility = 0;
    if (!local) {
	if (level > s_debug || level < DebugMin)
	    return;
    }
    else {
	if (!local->debugAt(level))
	    return;
	facility = local->debugName();
    }
    if (reentered())
	return;
    if (!format)
	format = "";
    char buf[OUT_HEADER_SIZE];
    if (facility)
	::snprintf(buf,sizeof(buf),"<%s:%s> ",facility,dbg_level(level));
    else
	::sprintf(buf,"<%s> ",dbg_level(level));
    va_list va;
    va_start(va,format);
    ind_mux.lock();
    dbg_output(level,buf,format,va);
    ind_mux.unlock();
    va_end(va);
    if (s_abort && (level == DebugFail))
	abort();
}

void Alarm(const char* component, int level, const char* format, ...)
{
    if (!format || level < DebugMin || reentered())
	return;
    if (TelEngine::null(component))
	component = "unknown";
    char buf[OUT_HEADER_SIZE];
    ::snprintf(buf,sizeof(buf),"<%s:%s> ",component,dbg_level(level));
    va_list va;
    va_start(va,format);
    ind_mux.lock();
    dbg_output(level,buf,format,va,component);
    ind_mux.unlock();
    va_end(va);
    if (s_abort && (level == DebugFail))
	abort();
}

void Alarm(const DebugEnabler* component, int level, const char* format, ...)
{
    if (!format || level < DebugMin || reentered())
	return;
    const char* name = (component && !TelEngine::null(component->debugName()))
	? component->debugName() : "unknown";
    char buf[OUT_HEADER_SIZE];
    ::snprintf(buf,sizeof(buf),"<%s:%s> ",name,dbg_level(level));
    va_list va;
    va_start(va,format);
    ind_mux.lock();
    dbg_output(level,buf,format,va,name);
    ind_mux.unlock();
    va_end(va);
    if (s_abort && (level == DebugFail))
	abort();
}

void Alarm(const char* component, const char* info, int level, const char* format, ...)
{
    if (!format || level < DebugMin || reentered())
	return;
    if (TelEngine::null(component))
	component = "unknown";
    char buf[OUT_HEADER_SIZE];
    ::snprintf(buf,sizeof(buf),"<%s:%s> ",component,dbg_level(level));
    va_list va;
    va_start(va,format);
    ind_mux.lock();
    dbg_output(level,buf,format,va,component,info);
    ind_mux.unlock();
    va_end(va);
    if (s_abort && (level == DebugFail))
	abort();
}

void Alarm(const DebugEnabler* component, const char* info, int level, const char* format, ...)
{
    if (!format || level < DebugMin || reentered())
	return;
    const char* name = (component && !TelEngine::null(component->debugName()))
	? component->debugName() : "unknown";
    char buf[OUT_HEADER_SIZE];
    ::snprintf(buf,sizeof(buf),"<%s:%s> ",name,dbg_level(level));
    va_list va;
    va_start(va,format);
    ind_mux.lock();
    dbg_output(level,buf,format,va,name,info);
    ind_mux.unlock();
    va_end(va);
    if (s_abort && (level == DebugFail))
	abort();
}

void TraceDebug(const char* traceId, int level, const char* format, ...)
{
    if (!s_debugging)
	return;
    if (level > s_debug || level < DebugMin)
	return;
    if (reentered())
	return;
    if (!format)
	format = "";
    char buf[OUT_HEADER_SIZE];
    if (traceId)
	::snprintf(buf,sizeof(buf),"<%s> Trace:%s ",dbg_level(level),traceId);
    else
	::sprintf(buf,"<%s> ",dbg_level(level));
    va_list va;
    va_start(va,format);
    ind_mux.lock();
    dbg_output(level,buf,format,va);
    ind_mux.unlock();
    va_end(va);
    if (s_abort && (level == DebugFail))
	abort();
}

void TraceDebug(const char* traceId, const char* facility, int level, const char* format, ...)
{
    if (!s_debugging)
	return;
    if (level > s_debug || level < DebugMin)
	return;
    if (reentered())
	return;
    if (!format)
	format = "";
    char buf[OUT_HEADER_SIZE];
    if (traceId)
	::snprintf(buf,sizeof(buf),"<%s:%s> Trace:%s ",facility,dbg_level(level),traceId);
    else
	::snprintf(buf,sizeof(buf),"<%s:%s> ",facility,dbg_level(level));
    va_list va;
    va_start(va,format);
    ind_mux.lock();
    dbg_output(level,buf,format,va);
    ind_mux.unlock();
    va_end(va);
    if (s_abort && (level == DebugFail))
	abort();
}

void TraceDebug(const char* traceId, const DebugEnabler* local, int level, const char* format, ...)
{
    if (!s_debugging)
	return;
    const char* facility = 0;
    if (!local) {
	if (level > s_debug || level < DebugMin)
	    return;
    }
    else {
	if (!local->debugAt(level))
	    return;
	facility = local->debugName();
    }
    if (reentered())
	return;
    if (!format)
	format = "";
    char buf[OUT_HEADER_SIZE];
    if (facility) {
	if (traceId)
	    ::snprintf(buf,sizeof(buf),"<%s:%s> Trace:%s ",facility,dbg_level(level),traceId);
	else
	    ::snprintf(buf,sizeof(buf),"<%s:%s> ",facility,dbg_level(level));
    }
    else {
	if (traceId)
	    ::snprintf(buf,sizeof(buf),"<%s> Trace:%s ",dbg_level(level),traceId);
	else
	    ::sprintf(buf,"<%s> ",dbg_level(level));
    }

    va_list va;
    va_start(va,format);
    ind_mux.lock();
    dbg_output(level,buf,format,va);
    ind_mux.unlock();
    va_end(va);
    if (s_abort && (level == DebugFail))
	abort();
}

void TraceAlarm(const char* traceId, const char* component, int level, const char* format, ...)
{
    if (!format || level < DebugMin || reentered())
	return;
    if (TelEngine::null(component))
	component = "unknown";
    char buf[OUT_HEADER_SIZE];
    if (traceId)
	::snprintf(buf,sizeof(buf),"<%s:%s> Trace:%s ",component,dbg_level(level),traceId);
    else
	::snprintf(buf,sizeof(buf),"<%s:%s> ",component,dbg_level(level));
    va_list va;
    va_start(va,format);
    ind_mux.lock();
    dbg_output(level,buf,format,va,component);
    ind_mux.unlock();
    va_end(va);
    if (s_abort && (level == DebugFail))
	abort();
}

void TraceAlarm(const char* traceId, const DebugEnabler* component, int level, const char* format, ...)
{
    if (!format || level < DebugMin || reentered())
	return;
    const char* name = (component && !TelEngine::null(component->debugName()))
	? component->debugName() : "unknown";
    char buf[OUT_HEADER_SIZE];
    if (traceId)
	::snprintf(buf,sizeof(buf),"<%s:%s> Trace:%s ",name,dbg_level(level),traceId);
    else
	::snprintf(buf,sizeof(buf),"<%s:%s> ",name,dbg_level(level));
    va_list va;
    va_start(va,format);
    ind_mux.lock();
    dbg_output(level,buf,format,va,name);
    ind_mux.unlock();
    va_end(va);
    if (s_abort && (level == DebugFail))
	abort();
}

void TraceAlarm(const char* traceId, const char* component, const char* info, int level, const char* format, ...)
{
    if (!format || level < DebugMin || reentered())
	return;
    if (TelEngine::null(component))
	component = "unknown";
    char buf[OUT_HEADER_SIZE];
       if (traceId)
	::snprintf(buf,sizeof(buf),"<%s:%s> Trace:%s ",component,dbg_level(level),traceId);
    else
	::snprintf(buf,sizeof(buf),"<%s:%s> ",component,dbg_level(level));
    va_list va;
    va_start(va,format);
    ind_mux.lock();
    dbg_output(level,buf,format,va,component,info);
    ind_mux.unlock();
    va_end(va);
    if (s_abort && (level == DebugFail))
	abort();
}

void TraceAlarm(const char* traceId, const DebugEnabler* component, const char* info, int level, const char* format, ...)
{
    if (!format || level < DebugMin || reentered())
	return;
    const char* name = (component && !TelEngine::null(component->debugName()))
	? component->debugName() : "unknown";
    char buf[OUT_HEADER_SIZE];
    if (traceId)
	::snprintf(buf,sizeof(buf),"<%s:%s> Trace:%s ",name,dbg_level(level),traceId);
    else
	::snprintf(buf,sizeof(buf),"<%s:%s> ",name,dbg_level(level));
    va_list va;
    va_start(va,format);
    ind_mux.lock();
    dbg_output(level,buf,format,va,name,info);
    ind_mux.unlock();
    va_end(va);
    if (s_abort && (level == DebugFail))
	abort();
}

void abortOnBug()
{
    if (s_abort)
	abort();
}

bool abortOnBug(bool doAbort)
{
    bool tmp = s_abort;
    s_abort = doAbort;
    return tmp;
}

int debugLevel()
{
    return s_debug;
}

int debugLevel(int level)
{
    if (level < DebugVis)
	level = DebugVis;
    if (level > DebugMax)
	level = DebugMax;
    return (s_debug = level);
}

bool debugAt(int level)
{
    return (s_debugging && (level <= s_debug));
}

const char* debugColor(int level)
{
    if (level == -2)
	return "\033[0m\033[K"; // reset to defaults
    if ((level < DebugMin) || (level > DebugMax))
	return "\033[0;40;37m\033[K"; // light gray on black
    return s_colors[level];
}

const char* debugLevelName(int level)
{
    return (level < DebugMin) ? "" : dbg_level(level);
}


int DebugEnabler::debugLevel(int level)
{
    if (level < DebugVis)
	level = DebugVis;
    if (level > DebugMax)
	level = DebugMax;
    m_chain = 0;
    return (m_level = level);
}

bool DebugEnabler::debugAt(int level) const
{
    if (m_chain)
	return m_chain->debugAt(level);
    return (m_enabled && (level <= m_level));
}

void DebugEnabler::debugCopy(const DebugEnabler* original)
{
    if (original) {
	m_level = original->debugLevel();
	m_enabled = original->debugEnabled();
    }
    else {
	m_level = TelEngine::debugLevel();
	m_enabled = debugEnabled();
    }
    m_chain = 0;
}

Debugger::Debugger(const char* name, const char* format, ...)
    : m_name(name), m_level(DebugAll)
{
    if (s_debugging && m_name && (s_debug >= DebugAll) && !reentered()) {
	char buf[OUT_HEADER_SIZE];
	::snprintf(buf,sizeof(buf),">>> %s",m_name);
	va_list va;
	va_start(va,format);
	ind_mux.lock();
	dbg_output(m_level,buf,format,va);
	va_end(va);
	s_indent++;
	ind_mux.unlock();
    }
    else
	m_name = 0;
}

Debugger::Debugger(int level, const char* name, const char* format, ...)
    : m_name(name), m_level(level)
{
    if (s_debugging && m_name && (s_debug >= level) && !reentered()) {
	char buf[OUT_HEADER_SIZE];
	::snprintf(buf,sizeof(buf),">>> %s",m_name);
	va_list va;
	va_start(va,format);
	ind_mux.lock();
	dbg_output(m_level,buf,format,va);
	va_end(va);
	s_indent++;
	ind_mux.unlock();
    }
    else
	m_name = 0;
}

static void dbg_dist_helper(int level, const char* buf, const char* fmt, ...)
{
    va_list va;
    va_start(va,fmt);
    dbg_output(level,buf,fmt,va);
    va_end(va);
}

bool controlReturn(NamedList* params, bool ret, const char* retVal)
{
    if (retVal && params)
	params->setParam("retVal",retVal);
    if (!params || !params->getObject(YATOM("Message")))
	return ret;
    const String* module = params->getParam("module");
    if (TelEngine::null(module) || YSTRING("rmanager") != *module)
	return ret;
    const String s_opStat("operation-status");
    if (!params->getParam(s_opStat))
	params->addParam(s_opStat,String::boolText(ret));
    return ret;
}

Debugger::~Debugger()
{
    if (m_name) {
	ind_mux.lock();
	s_indent--;
	if (s_debugging)
	    dbg_dist_helper(m_level,"<<< ","%s",m_name);
	ind_mux.unlock();
    }
}

void Debugger::setOutput(void (*outFunc)(const char*,int))
{
    out_mux.lock();
    s_output = outFunc ? outFunc : dbg_stderr_func;
    out_mux.unlock();
}

void Debugger::setIntOut(void (*outFunc)(const char*,int))
{
    out_mux.lock();
    s_intout = outFunc;
    out_mux.unlock();
}

void Debugger::setAlarmHook(void (*alarmFunc)(const char*,int,const char*,const char*))
{
    s_alarms = alarmFunc;
}

void Debugger::setRelayHook(void (*relayFunc)(int,const char*,const char*,const char*))
{
    s_relay = relayFunc;
}

void Debugger::enableOutput(bool enable, bool colorize)
{
    s_debugging = enable;
    if (colorize)
	setOutput(dbg_colorize_func);
}

uint32_t Debugger::getStartTimeSec()
{
    return (uint32_t)(s_timestamp / 1000000);
}

Debugger::Formatting Debugger::getFormatting()
{
    return s_fmtstamp;
}

void Debugger::setFormatting(Formatting format, uint32_t startTimeSec)
{
    // start stamp will be rounded to full second
    if (!s_timestamp) {
	uint64_t sec = Time::now() / 1000000;
	if (startTimeSec && startTimeSec < sec)
	    sec = startTimeSec;
	s_timestamp = sec * 1000000;
    }
    s_fmtstamp = format;
}

unsigned int Debugger::formatTime(char* buf, Formatting format)
{
    if (!buf)
	return 0;
    if (None != format) {
	u_int64_t t = Time::now();
	if (Relative == format)
	    t -= s_timestamp;
	unsigned int s = (unsigned int)(t / 1000000);
	unsigned int u = (unsigned int)(t % 1000000);
	switch (format) {
	    case Textual:
	    case TextLocal:
	    case TextSep:
	    case TextLSep:
		{
		    time_t sec = (time_t)s;
		    struct tm tmp;
		    if (TextLocal == format || TextLSep == format)
#ifdef _WINDOWS
			_localtime_s(&tmp,&sec);
#else
			localtime_r(&sec,&tmp);
#endif
		    else
#ifdef _WINDOWS
			_gmtime_s(&tmp,&sec);
#else
			gmtime_r(&sec,&tmp);
#endif
		    if (Textual == format || TextLocal == format)
			::sprintf(buf,"%04d%02d%02d%02d%02d%02d.%06u ",
			    tmp.tm_year+1900,tmp.tm_mon+1,tmp.tm_mday,
			    tmp.tm_hour,tmp.tm_min,tmp.tm_sec,u);
		    else
			::sprintf(buf,"%04d-%02d-%02d_%02d:%02d:%02d.%06u ",
			    tmp.tm_year+1900,tmp.tm_mon+1,tmp.tm_mday,
			    tmp.tm_hour,tmp.tm_min,tmp.tm_sec,u);
		}
		break;
	    default:
		::sprintf(buf,"%07u.%06u ",s,u);
	}
	return ::strlen(buf);
    }
    buf[0] = '\0';
    return 0;
}

void Debugger::relayOutput(int level, char* buffer, const char* component, const char* info)
{
    if (TelEngine::null(buffer))
	return;
    void (*alarms)(const char*,int,const char*,const char*) = s_alarms;
    bool out = s_output || s_intout;
    bool alarm = (level >= 0) && alarms && !TelEngine::null(component);
    if (!(out || alarm))
	return;
    if (reentered())
	return;
    if (out)
	common_output(level,buffer);
    if (alarm) {
	const char* msg = ::strstr(buffer,"> ");
	if (msg && msg != buffer)
	    msg += 2;
	else
	    msg = buffer;
	if (*msg)
	    alarms(msg,level,component,info);
    }
}


u_int64_t Time::now()
{
#ifdef _WINDOWS
    FILETIME ft;
    GetSystemTimeAsFileTime(&ft);
    // Convert from FILETIME (100 nsec units since January 1, 1601)
    //  to extended time_t (1 usec units since January 1, 1970)
    u_int64_t rval = ((ULARGE_INTEGER*)&ft)->QuadPart / 10;
    rval -= 11644473600000000;
    return rval;
#else
    struct timeval tv;
    return ::gettimeofday(&tv,0) ? 0 : fromTimeval(&tv);
#endif
}

u_int64_t Time::msecNow()
{
    return (u_int64_t)(now() / 1000);
}

u_int32_t Time::secNow()
{
#ifdef _WINDOWS
    return (u_int32_t)(now() / 1000000);
#else
    struct timeval tv;
    return ::gettimeofday(&tv,0) ? 0 : tv.tv_sec;
#endif
}

u_int64_t Time::fromTimeval(const struct timeval* tv)
{
    u_int64_t rval = 0;
    if (tv) {
	// Please keep it this way or the compiler may b0rk
	rval = tv->tv_sec;
	rval *= 1000000;
	rval += tv->tv_usec;
    }
    return rval;
}

void Time::toTimeval(struct timeval* tv, u_int64_t usec)
{
    if (tv) {
	tv->tv_usec = (long)(usec % 1000000);
	tv->tv_sec = (long)(usec / 1000000);
    }
}

// Build EPOCH time from date/time components
unsigned int Time::toEpoch(int year, unsigned int month, unsigned int day,
	unsigned int hour, unsigned int minute, unsigned int sec, int offset)
{
    DDebug(DebugAll,"Time::toEpoch(%d,%u,%u,%u,%u,%u,%d)",
	year,month,day,hour,minute,sec,offset);
    if (year < 1970)
	return (unsigned int)-1;
    if (month < 1 || month > 12 || !day)
	return (unsigned int)-1;
    if (hour == 24 && (minute || sec))
	return (unsigned int)-1;
    else if (hour > 23 || minute > 59 || sec > 59)
	return (unsigned int)-1;
    // Check if month and day are correct in the given year
    month--;
    unsigned int m[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
    if (isLeap(year))
	m[1] = 29;
    if (day > m[month])
	return (unsigned int)-1;
    // Count the number of days since EPOCH
    int64_t days = (year - 1970) * 365;
    // Add a day for each leap year from 1970 to 'year' (not including)
    for (int y = 1972; y < year; y += 4) {
	if (isLeap(y))
	    days++;
    }
    // Add days ellapsed in given year
    for (unsigned int i = 0; i < month; i++)
	days += m[i];
    days += day - 1;
    int64_t ret = (days * 24 + hour) * 3600 + minute * 60 + sec + offset;

    // Check for incorrect time or overflow
    if (ret < 0 || ret > (unsigned int)-1)
	return (unsigned int)-1;
    return (unsigned int)ret;
}

// Split a given EPOCH time into its date/time components
bool Time::toDateTime(unsigned int epochTimeSec, int& year, unsigned int& month,
    unsigned int& day, unsigned int& hour, unsigned int& minute, unsigned int& sec,
    unsigned int* wDay)
{
#ifdef _WINDOWS
    FILETIME ft;
    SYSTEMTIME st;
    // 11644473600: the number of seconds from 1601, January 1st (FILETIME)
    //  to EPOCH (1970, January 1st)
    // Remember: FILETIME keeps the number of 100 nsec units
    u_int64_t time = (11644473600 + epochTimeSec) * 10000000;
    ft.dwLowDateTime = (DWORD)time;
    ft.dwHighDateTime = (DWORD)(time >> 32);
    if (!FileTimeToSystemTime(&ft,&st))
	return false;
    year = st.wYear;
    month = st.wMonth;
    day = st.wDay;
    hour = st.wHour;
    minute = st.wMinute;
    sec = st.wSecond;
    if (wDay)
	*wDay = st.wDayOfWeek;
#else
    struct tm t;
    time_t time = (time_t)epochTimeSec;
    if (!gmtime_r(&time,&t))
	return false;
    year = 1900 + t.tm_year;
    month = t.tm_mon + 1;
    day = t.tm_mday;
    hour = t.tm_hour;
    minute = t.tm_min;
    sec = t.tm_sec;
    if (wDay)
	*wDay = t.tm_wday;
#endif
    DDebug(DebugAll,"Time::toDateTime(%u,%d,%u,%u,%u,%u,%u)",
	epochTimeSec,year,month,day,hour,minute,sec);
    return true;
}

uint32_t Time::toNtp(uint32_t sec, uint32_t* over, bool rfc2030)
{
    if (sec < UNIX_NTP_MAX)
	return sec + SECONDS_1900_TO_1970;
    if (rfc2030) {
	sec -= UNIX_NTP_MAX;
	if (sec <= 0x7fffffff)
	    return sec;
	if (over)
	    *over = sec - 0x7fffffff;
	return 0x7fffffff;
    }
    if (over)
	*over = (uint32_t)(((uint64_t)sec + SECONDS_1900_TO_1970) - 0xffffffff);
    return 0xffffffff;
}

uint32_t Time::fromNtp(uint32_t val, uint32_t* under, bool rfc2030)
{
    if (rfc2030 && val <= 0x7fffffff)
	return val + UNIX_NTP_MAX;
    if (val >= SECONDS_1900_TO_1970)
	return val - SECONDS_1900_TO_1970;
    if (under)
	*under = SECONDS_1900_TO_1970 - val;
    return 0;
}

unsigned int Time::toString(char* buf, uint64_t time, int frac)
{
    if (!buf)
	return 0;
    int y;
    unsigned int m,d,hh,mm,ss;
    if (!Time::toDateTime(time / 1000000,y,m,d,hh,mm,ss) || y < 0 || y > 9999)
	return 0;
    unsigned int n = 0;
    if (!frac)
	n = ::sprintf(buf,"%04d-%02u-%02uT%02u:%02u:%02u",y,m,d,hh,mm,ss);
    else {
	unsigned int f = time % 1000000;
	if (frac > 0)
	    n = ::sprintf(buf,"%04d-%02u-%02uT%02u:%02u:%02u.%03u",y,m,d,hh,mm,ss,f / 1000);
	else
	    n = ::sprintf(buf,"%04d-%02u-%02uT%02u:%02u:%02u.%06u",y,m,d,hh,mm,ss,f);
    }
    if (n)
	buf[n++] = 'Z';
    return n;
}

static inline bool addNum2(const char* buf, unsigned int& val, unsigned int maxVal = 0)
{
    if (buf[0] < '0' || buf[0] > '9' || buf[1] < '0' || buf[1] > '9')
	return false;
    val += 10 * (buf[0] - '0') + (buf[1] - '0');
    return !maxVal || val <= maxVal;
}

// Decode yyyy-mm-dd{T|t}hh:mm:ss[.SEC-FRAC]{{Z|z}|{+/-hh:mm}}
uint64_t Time::toEpoch(const char* buf, unsigned int len, int frac)
{
    if (!buf)
	return (uint64_t)-1;
    if (!len)
	len = ::strlen(buf);
    if (len < 20)
	return (uint64_t)-1;
    // Check mandatory yyyy-mm-ddThh:mm:ss
    if (buf[4] != '-' || buf[7] != '-' || (buf[10] != 'T' && buf[10] != 't')
	|| buf[13] != ':' || buf[16] != ':')
	return (uint64_t)-1;
    int offsetSec = 0;
    unsigned int delta = 0;
    if (buf[19] != 'Z' && buf[19] != 'z') {
	// We must receive at least '.' + digit + 'Z' or TZ offset
	if (len < 22)
	    return (uint64_t)-1;
	unsigned int crt = 19;
	// Second fraction ?
	if (buf[19] == '.') {
	    for (crt++; crt < len; crt++) {
		if (buf[crt] < '0' || buf[crt] > '9')
		    break;
	    }
	    unsigned int fracLen = crt - 20;
	    // Empty fraction or buffer end
	    if (!fracLen || crt == len)
		return (uint64_t)-1;
	    if (frac) {
		unsigned int req = frac > 0 ? 3 : 6;
		String tmp(buf + 20,fracLen > req ? req : fracLen);
		delta = tmp.toInteger(0,10);
		for (; fracLen < req; ++fracLen)
		    delta *= 10;
	    }
	}
	// TZ offset: ('+' / '-')hh:mm
	if (buf[crt] != 'Z' && buf[crt] != 'z') {
	    const char* b = buf + crt;
	    unsigned int tzOffsLen = len - crt;
	    if (tzOffsLen != 6 || b[3] != ':')
		return (uint64_t)-1;
	    int mul = 1;
	    if (*b == '-')
		mul = -1;
	    else if (*b != '+')
		return (uint64_t)-1;
	    unsigned int hh = 0, mm = 0;
	    if (!(addNum2(b + 1,hh,23) && addNum2(b + 4,mm,59)))
		return (uint64_t)-1;
	    offsetSec = mul * (int)(hh * 3600 + mm * 60);
	}
    }
    unsigned int y = 0, m = 0, d = 0, hh = 0, mm = 0, ss = 0;
    if (!addNum2(buf,y))
	return (uint64_t)-1;
    y *= 100;
    if (!(addNum2(buf + 2,y) && (addNum2(buf + 5,m,12) || !m) && (addNum2(buf + 8,d,31) || !d)
	&& addNum2(buf + 11,hh,23) && addNum2(buf + 14,mm,59) && addNum2(buf + 17,ss,60)))
	return (uint64_t)-1;
    bool leapSec = (ss == 60);
    if (leapSec)
	ss = 59;
    uint64_t sec = toEpoch(y,m,d,hh,mm,ss,offsetSec);
    if (sec == (unsigned int)-1)
	return (uint64_t)-1;
    if (leapSec)
	sec++;
    if (!frac)
	return sec;
    if (frac > 0)
	return sec * 1000 + delta;
    return sec * 1000000 + delta;
}

int Time::timeZone(u_int32_t when)
{
#ifdef _WINDOWS
    struct tm t;
    time_t time = (time_t)when;
    _localtime_s(&t,&time);
    if (t.tm_isdst)
	return -(_timezone + _dstbias);
    return -_timezone;
#else
#ifdef HAVE_GMTOFF
    struct tm t;
    time_t time = (time_t)when;
    if (localtime_r(&time,&t))
	return t.tm_gmtoff;
#endif
#ifdef HAVE_INT_TZ
    return -timezone;
#else
    return 0;
#endif
#endif
}


static Random s_random;
static Mutex s_randomMutex(false,"Random");

u_int32_t Random::next()
{
    return (m_random = (m_random + 1) * 0x8088405);
}

long int Random::random()
{
    s_randomMutex.lock();
    long int ret = s_random.next() % RAND_MAX;
    s_randomMutex.unlock();
    return ret;
}

void Random::srandom(unsigned int seed)
{
    s_randomMutex.lock();
    s_random.set(seed % RAND_MAX);
    s_randomMutex.unlock();
}


bool GenObject::s_counting = false;
static ObjCounterList s_counters;
static Mutex s_countersMutex(false,"Counters");

GenObject::GenObject()
    : m_counter(0)
{
    NamedCounter* counter = Thread::getCurrentObjCounter();
    if (counter && counter->enabled())
	setObjCounter(counter);
}

bool GenObject::alive() const
{
    return true;
}

void GenObject::destruct()
{
    delete this;
}

NamedCounter* GenObject::setObjCounter(NamedCounter* counter)
{
    if (counter == m_counter)
	return counter;
    if (s_counters.invalid())
	return 0;
    Lock mylock(0);
    if (Mutex::count() >= 0)
	mylock.acquire(s_countersMutex);
    NamedCounter* oldCounter = m_counter;
    if (counter != oldCounter) {
	m_counter = counter;
	mylock.drop();
	if (counter)
	    counter->inc();
	if (oldCounter)
	    oldCounter->dec();
    }
    return oldCounter;
}

NamedCounter* GenObject::getObjCounter(const String& name, bool create)
{
    if (name.null() || s_counters.invalid())
	return 0;
    Lock mylock(s_countersMutex);
    NamedCounter* cnt = static_cast<NamedCounter*>(s_counters[name]);
    if (create && !cnt) {
	NamedCounter* saved = Thread::setCurrentObjCounter(0);
	s_counters.append(cnt = new NamedCounter(name));
	Thread::setCurrentObjCounter(saved);
    }
    return cnt;
}

ObjList& GenObject::getObjCounters()
{
    return s_counters;
}


#ifndef ATOMIC_OPS
static MutexPool s_refMutex(REFOBJECT_MUTEX_COUNT,false,"RefObject");
#endif

RefObject::RefObject()
    : m_refcount(1), m_mutex(0)
{
#ifndef ATOMIC_OPS
    m_mutex = s_refMutex.mutex(this);
#endif
}

RefObject::~RefObject()
{
    if (m_refcount > 0)
	Debug(DebugFail,"RefObject [%p] destroyed with count=%d",this,m_refcount);
}

void* RefObject::getObject(const String& name) const
{
    if (name == YATOM("RefObject"))
	return (void*)this;
    return GenObject::getObject(name);
}
bool RefObject::alive() const
{
    return m_refcount > 0;
}

void RefObject::destruct()
{
    deref();
}

bool RefObject::ref()
{
#ifdef ATOMIC_OPS
#ifdef _WINDOWS
    if (InterlockedIncrement((LONG*)&m_refcount) > 1)
	return true;
    InterlockedDecrement((LONG*)&m_refcount);
#else
    if (__sync_add_and_fetch(&m_refcount,1) > 1)
	return true;
    __sync_sub_and_fetch(&m_refcount,1);
#endif
#else
    Lock lock(m_mutex);
    if (m_refcount > 0) {
	++m_refcount;
	return true;
    }
#endif
    return false;
}

bool RefObject::deref()
{
#ifdef ATOMIC_OPS
#ifdef _WINDOWS
    int i = InterlockedDecrement((LONG*)&m_refcount) + 1;
    if (i <= 0)
	InterlockedIncrement((LONG*)&m_refcount);
#else
    int i = __sync_fetch_and_sub(&m_refcount,1);
    if (i <= 0)
	__sync_fetch_and_add(&m_refcount,1);
#endif
#else
    m_mutex->lock();
    int i = m_refcount;
    if (i > 0)
	--m_refcount;
    m_mutex->unlock();
#endif
    if (i == 1)
	zeroRefs();
    else if (i <= 0)
	Debug(DebugFail,"RefObject::deref() called with count=%d [%p]",i,this);
    return (i <= 1);
}

void RefObject::zeroRefs()
{
    destroyed();
    delete this;
}

bool RefObject::resurrect()
{
#ifdef ATOMIC_OPS
#ifdef _WINDOWS
    if (InterlockedIncrement((LONG*)&m_refcount) == 1)
	return true;
    InterlockedDecrement((LONG*)&m_refcount);
    return false;
#else
    if (__sync_add_and_fetch(&m_refcount,1) == 1)
	return true;
    __sync_sub_and_fetch(&m_refcount,1);
    return false;
#endif
#else
    m_mutex->lock();
    bool ret = (0 == m_refcount);
    if (ret)
	m_refcount = 1;
    m_mutex->unlock();
    return ret;
#endif
}

void RefObject::destroyed()
{
}

bool RefObject::efficientIncDec()
{
#ifdef ATOMIC_OPS
    return true;
#else
    return false;
#endif
}

void RefPointerBase::assign(RefObject* oldptr, RefObject* newptr, void* pointer)
{
    if (oldptr == newptr)
	return;
    // Always reference the new object before dereferencing the old one
    //  and also don't keep pointers to objects that fail referencing
    m_pointer = (newptr && newptr->ref()) ? pointer : 0;
    if (oldptr)
	oldptr->deref();
}


NamedCounter::NamedCounter(const String& name)
    : String(name), m_count(0), m_enabled(getObjCounting()), m_mutex(0)
{
#ifndef ATOMIC_OPS
    m_mutex = s_refMutex.mutex(this);
#endif
}

int NamedCounter::inc()
{
#ifdef ATOMIC_OPS
#ifdef _WINDOWS
    return InterlockedIncrement((LONG*)&m_count);
#else
    return __sync_add_and_fetch(&m_count,1);
#endif
#else
    Lock lock(m_mutex);
    return ++m_count;
#endif
}

int NamedCounter::dec()
{
#ifdef ATOMIC_OPS
#ifdef _WINDOWS
    return InterlockedDecrement((LONG*)&m_count);
#else
    return __sync_fetch_and_sub(&m_count,1);
#endif
#else
    Lock lock(m_mutex);
    return --m_count;
#endif
}


void SysUsage::init()
{
    if (!s_startTime)
	s_startTime = Time::now();
}

u_int64_t SysUsage::startTime()
{
    init();
    return s_startTime;
}

u_int64_t SysUsage::usecRunTime(Type type)
{
    switch (type) {
	case WallTime:
	    return Time::now() - startTime();
	case UserTime:
	    {
#ifdef _WINDOWS
		FILETIME dummy,ft;
		if (GetProcessTimes(GetCurrentProcess(),&dummy,&dummy,&dummy,&ft)) {
		    u_int64_t t = ft.dwLowDateTime | (((u_int64_t)ft.dwHighDateTime) << 32);
		    return t / 10;
		}
#else
		struct rusage usage;
		// FIXME: this is broken, may not sum all threads
		if (!::getrusage(RUSAGE_SELF,&usage))
		    return Time::fromTimeval(usage.ru_utime);
#endif
	    }
	    break;
	case KernelTime:
	    {
#ifdef _WINDOWS
		FILETIME dummy,ft;
		if (GetProcessTimes(GetCurrentProcess(),&dummy,&dummy,&ft,&dummy)) {
		    u_int64_t t = ft.dwLowDateTime | (((u_int64_t)ft.dwHighDateTime) << 32);
		    return t / 10;
		}
#else
		struct rusage usage;
		// FIXME: this is broken, may not sum all threads
		if (!::getrusage(RUSAGE_SELF,&usage))
		    return Time::fromTimeval(usage.ru_stime);
#endif
	    }
	    break;
    }
    return 0;
}

u_int64_t SysUsage::msecRunTime(Type type)
{
    return usecRunTime(type) / 1000;
}

u_int32_t SysUsage::secRunTime(Type type)
{
    return (u_int32_t)(usecRunTime(type) / 1000000);
}

double SysUsage::runTime(Type type)
{
#ifdef _WINDOWS
    // VC++ 6 does not implement conversion from UINT64 to double!
    return 0.000001 * (int64_t)usecRunTime(type);
#else
    return 0.000001 * usecRunTime(type);
#endif
}

};

/* vi: set ts=8 sw=4 sts=4 noet: */
