
#include "msystem.h"

time_t get_timezone(void)
{
    struct tm tmp;
    time_t utc = time(NULL);
    localtime_r(&utc, &tmp);
    return tmp.tm_gmtoff;
}

time_t get_systime(void)
{
    return time(NULL);
}

struct tm* get_localtime(struct tm* ptm)
{
    if (ptm)
    {
        time_t utc = time(NULL);
        localtime_r(&utc, ptm);
    }
    return ptm;
}

struct tm* get_gmttime(struct tm* ptm)
{
    if (ptm)
    {
        time_t utc = time(NULL);
        gmtime_r(&utc, ptm);
    }
    return ptm;
}

int64_t systime_ms(void)
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (int64_t) tv.tv_sec * 1000 + tv.tv_usec / 1000;
}

int64_t systime_us(void)
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (int64_t) tv.tv_sec * 1000000 + tv.tv_usec;
}

int64_t systime_ns(void)
{
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    return (int64_t) ts.tv_sec * 1000000000 + ts.tv_nsec;
}

int printline(uint8_t *data, int N, const char* fmt, ...)
{
    if ( N > 0 && data != NULL)
    {
        va_list valist;
        va_start(valist, fmt);
        int len = vprintf(fmt, valist);
        va_end(valist);
        printf("(%3d):", N);
        int i;
        for (i=0; i<N; i++)
        {
            printf(" %02X", data[i]);
        }
        printf("\n");
    }
    return N;
}

void msleep(int timeout)
{
    int err;
    struct timeval tv;
    tv.tv_sec = timeout/1000;
    tv.tv_usec = (timeout%1000) * 1000;
    do{ err=select(0,NULL,NULL,NULL,&tv); }while(err<0 && errno==EINTR);
}

void dump_U16String_To_U8String(uint8_t *dst, uint16_t *src, int N)
{
    int i;
    for (i=0; i<N; i++)
    {
        dst[2*i+0] = (src[i]>>8)&0xFF;
        dst[2*i+1] = (src[i]>>0)&0xFF;
    }
}

void dump_U8String_To_U16String(uint16_t *dst, uint8_t *src, int N)
{
    int i;
    for (i=0; i<N; i+=2)
    {
        dst[i/2] = (src[i+0]<<8)|src[i+1];
    }
}

uint8_t DEC2BCD(uint8_t dec)
{
    uint8_t tmp = dec % 100;
    uint8_t bcd = (tmp/10)<<4;
    return (bcd + (tmp%10));
}

uint8_t BCD2DEC(uint8_t bcd)
{
    uint8_t dec = (bcd >> 4) * 10;
    return (dec + (bcd & 0xFF));
}

int sem_waittimeout(sem_t *__sem, int timeout)
{
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    ts.tv_sec += timeout/1000;
    ts.tv_nsec += (timeout%1000)*1000000;
    // return sem_timedwait(__sem, &ts);
    return sem_wait(__sem);
}

time_t RFC3339String_to_time(const char* datetime, struct timeval* tv, struct timespec* ts)
{
    time_t utc_seconds = -1;
    #define ASCII_CODE(x)  ((x)==32? 0:((x)-'0'))
    if (datetime)
    {
        struct tm  tmp;
        memset(&tmp, 0, sizeof(tmp)); // tmp.tm_isdst = 0;

        tmp.tm_year = ASCII_CODE(datetime[0])*1000 + ASCII_CODE(datetime[1])*100 + ASCII_CODE(datetime[2])*10 + ASCII_CODE(datetime[3]) - 1900;
        tmp.tm_mon = ASCII_CODE(datetime[5])*10 + ASCII_CODE(datetime[6]) - 1;
        tmp.tm_mday = ASCII_CODE(datetime[8])*10 + ASCII_CODE(datetime[9]);

        tmp.tm_hour = ASCII_CODE(datetime[11])*10 + ASCII_CODE(datetime[12]);
        tmp.tm_min = ASCII_CODE(datetime[14])*10 + ASCII_CODE(datetime[15]);
        tmp.tm_sec = ASCII_CODE(datetime[17])*10 + ASCII_CODE(datetime[18]);

        utc_seconds = mktime(&tmp);

        int next = 19;
        int ratio = 1;
        int frac = 0;
        int zhours = 0;
        int zminutes = 0;
        if (datetime[next]=='.')
        {
            for(next++; datetime[next]<='9' && datetime[next]>='0'; next++)
            {
                ratio *=10;
                frac *= 10;
                frac += ASCII_CODE(datetime[next]);
            }
        }
        if (datetime[next]=='+')
        {
            zhours = ASCII_CODE(datetime[next+1])*10 + ASCII_CODE(datetime[next+2]);
            zminutes = ASCII_CODE(datetime[next+4])*10 + ASCII_CODE(datetime[next+5]);
            utc_seconds += zhours*3600 + zminutes+60;
        }
        if (datetime[next]=='-')
        {
            zhours = ASCII_CODE(datetime[next+1])*10 + ASCII_CODE(datetime[next+2]);
            zminutes = ASCII_CODE(datetime[next+4])*10 + ASCII_CODE(datetime[next+5]);
            utc_seconds -= zhours*3600 + zminutes+60;
        }
        if (tv)
        {
            tv->tv_sec = utc_seconds;
            tv->tv_usec = frac * 1000000 / ratio;
        }
        if (ts)
        {
            ts->tv_sec = utc_seconds;
            ts->tv_nsec = frac * 1000000000 / ratio;
        }
    }
    #undef ASCII_CODE
    return utc_seconds;
}

int sprintf_UTCTime(char* stream, time_t utc)
{
    struct tm tmp;
    gmtime_r(&utc, &tmp);
    return sprintf(stream, "%04d-%02d-%02dT%02d:%02d:%02dZ", tmp.tm_year+1900, tmp.tm_mon+1, tmp.tm_mday, tmp.tm_hour, tmp.tm_min, tmp.tm_sec);
}

char* format_UTCTime(char* stream, time_t utc)
{
    sprintf_UTCTime(stream, utc);
    return stream;
}

int sprintf_structTM(char* stream, struct tm* tmp)
{
    if (tmp)
    {
        if (tmp->tm_gmtoff==0)
            return sprintf(stream, "%04d-%02d-%02dT%02d:%02d:%02dZ", tmp->tm_year+1900, tmp->tm_mon+1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
    
        if (tmp->tm_gmtoff>0)
        {
            int zhour = tmp->tm_gmtoff/3600;
            int zminute = (tmp->tm_gmtoff%3600)/60;
            return sprintf(stream, "%04d-%02d-%02dT%02d:%02d:%02d+%02d:%02d", tmp->tm_year+1900, tmp->tm_mon+1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec, zhour, zminute);
        }
        if (tmp->tm_gmtoff<0)
        {
            int zhour = (-tmp->tm_gmtoff)/3600;
            int zminute = (-tmp->tm_gmtoff%3600)/60;
            return sprintf(stream, "%04d-%02d-%02dT%02d:%02d:%02d-%02d:%02d", tmp->tm_year+1900, tmp->tm_mon+1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec, zhour, zminute);
        }
    }
    return 0;
}

char* format_structTM(char* stream, struct tm* tmp)
{
    sprintf_structTM(stream, tmp);
    return stream;
}

int sprintf_structTV(char* stream, struct timeval* tv)
{
    if (tv)
    {
        time_t utc = tv->tv_sec;
        struct tm tmv;
        localtime_r(&utc, &tmv);
        struct tm *tmp=&tmv;
        if (tmp->tm_gmtoff==0)
            return sprintf(stream, "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", tmp->tm_year+1900, tmp->tm_mon+1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec, tv->tv_usec/1000);
    
        if (tmp->tm_gmtoff>0)
        {
            int zhour = tmp->tm_gmtoff/3600;
            int zminute = (tmp->tm_gmtoff%3600)/60;
            return sprintf(stream, "%04d-%02d-%02dT%02d:%02d:%02d.%03d+%02d:%02d", tmp->tm_year+1900, tmp->tm_mon+1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec, tv->tv_usec/1000, zhour, zminute);
        }
        if (tmp->tm_gmtoff<0)
        {
            int zhour = (-tmp->tm_gmtoff)/3600;
            int zminute = (-tmp->tm_gmtoff%3600)/60;
            return sprintf(stream, "%04d-%02d-%02dT%02d:%02d:%02d.%03d-%02d:%02d", tmp->tm_year+1900, tmp->tm_mon+1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec, tv->tv_usec/1000, zhour, zminute);
        }
    }
}

char* format_structTV(char* stream, struct timeval* tv)
{
    sprintf_structTV(stream, tv);
    return stream;
}

int sprintf_structTS(char* stream, struct timespec* ts)
{
    if (ts)
    {
        time_t utc = ts->tv_sec;
        struct tm tmv;
        localtime_r(&utc, &tmv);
        struct tm *tmp=&tmv;
        if (tmp->tm_gmtoff==0)
            return sprintf(stream, "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", tmp->tm_year+1900, tmp->tm_mon+1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec, ts->tv_nsec/1000000);
    
        if (tmp->tm_gmtoff>0)
        {
            int zhour = tmp->tm_gmtoff/3600;
            int zminute = (tmp->tm_gmtoff%3600)/60;
            return sprintf(stream, "%04d-%02d-%02dT%02d:%02d:%02d.%03d+%02d:%02d", tmp->tm_year+1900, tmp->tm_mon+1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec, ts->tv_nsec/1000000, zhour, zminute);
        }
        if (tmp->tm_gmtoff<0)
        {
            int zhour = (-tmp->tm_gmtoff)/3600;
            int zminute = (-tmp->tm_gmtoff%3600)/60;
            return sprintf(stream, "%04d-%02d-%02dT%02d:%02d:%02d.%03d-%02d:%02d", tmp->tm_year+1900, tmp->tm_mon+1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec, ts->tv_nsec/1000000, zhour, zminute);
        }
    }
}

char* format_structTS(char* stream, struct timespec* ts)
{
    sprintf_structTS(stream, ts);
    return stream;
}

int sprintf_basename(char* stream, const char* filename)
{
    sprintf(stream, filename);
    return sprintf(stream, basename(stream));
}

int sprintf_pathname(char* stream, const char* filename)
{
    sprintf(stream, filename);
    return sprintf(stream, dirname(stream));
}

char* format_basename(char* stream, const char* filename)
{
    sprintf(stream, filename);
    return basename(stream);
}

char* format_pathname(char* stream, const char* filename)
{
    sprintf(stream, filename);
    return dirname(stream);
}

const char* choose_first_ipv6_interface(void) 
{
    struct ifaddrs *ifaddr, *ifa;
    char buffer[INET6_ADDRSTRLEN];

    if (getifaddrs(&ifaddr) == -1)
        return NULL;

    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
        if (!ifa->ifa_addr)
            continue;

        if (ifa->ifa_addr->sa_family == AF_INET6) {
            inet_ntop(AF_INET6, &ifa->ifa_addr->sa_data, buffer, sizeof(buffer));
            if (strstr(buffer, "fe80") != NULL) {
                return ifa->ifa_name;
            }
        }
    }
    printi("No necessary IPv6 link-local address was found!");
    return NULL;
}

int get_ipv6_address(const char* if_name, struct sockaddr_in6* addr, char* ip_string, int ip_string_size) 
{
    struct ifaddrs *ifaddr, *ifa;
    int rv = -1;

    if (getifaddrs(&ifaddr) == -1)
        return -1;

    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
        if (!ifa->ifa_addr)
            continue;

        if (ifa->ifa_addr->sa_family != AF_INET6)
            continue;

        if (strcmp(ifa->ifa_name, if_name) != 0)
            continue;

        //addr->sin6_scope_id == 0; //ADDR6_TYPE_LINKLOCAL
        //addr->sin6_scope_id != 0; //ADDR6_TYPE_GLOBAL
        if (addr)
        {
            memcpy(addr, ifa->ifa_addr, sizeof(struct sockaddr_in6));
        }
        if (ip_string)
        {
            struct sockaddr_in6 addr6;
            memcpy(&addr6, ifa->ifa_addr, sizeof(struct sockaddr_in6));
            inet_ntop(AF_INET6, &addr6, ip_string, ip_string_size);
        }
        rv = 0;
        break;
    }

    freeifaddrs(ifaddr);
    return rv;
}

int get_ipv4_address(const char* if_name, struct sockaddr_in* addr, char *ip_string, int size)
{
    struct ifaddrs *ifaddr, *ifa;
    int rv = -1;

    if (getifaddrs(&ifaddr) == -1)
        return -1;

    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
        if (!ifa->ifa_addr)
            continue;

        if (ifa->ifa_addr->sa_family != AF_INET)
            continue;

        if (strcmp(ifa->ifa_name, if_name) != 0)
            continue;
        if (addr)
        {
            memcpy(addr, ifa->ifa_addr, sizeof(struct sockaddr_in));
        }
        if (ip_string)
        {
            struct sockaddr_in addr4;
            memcpy(&addr4, ifa->ifa_addr, sizeof(struct sockaddr_in));
            snprintf(ip_string, size, "%s", inet_ntoa(addr4.sin_addr));
        }
        rv = 0;
        break;
    }

    freeifaddrs(ifaddr);
    return rv;
}

int create_listen_socket_bind_ipv6(struct sockaddr_in6* sockaddr)
{
    /* create socket */
    int s = socket(AF_INET6, SOCK_STREAM, 0);
    if (s == -1) {
        printd("socket() failed: %s\n", strerror(errno));
        return -1;
    }
    int enable = 1;
    if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(enable)) == -1) {
        printd("setsockopt(SO_REUSEPORT) failed: %s\n", strerror(errno));
        close(s);
        return -1;
    }

    /* bind it to interface */
    socklen_t addrlen = sizeof(struct sockaddr_in6);
    if (bind(s, (struct sockaddr*)sockaddr, addrlen) == -1) {
        printd("bind() failed: %s\n", strerror(errno));
        printd("Verify that the configured interface has a valid IPv6 link local address configured.\n");
        close(s);
        return -1;
    }

    /* listen on this socket */
    if (listen(s, 3) == -1) {
        printd("listen() failed: %s\n", strerror(errno));
        close(s);
        return -1;
    }

    /* retrieve the actual port number we are listening on */
    if (getsockname(s, (struct sockaddr*)sockaddr, &addrlen) == -1) {
        printd("getsockname() failed: %s\n", strerror(errno));
        close(s);
        return -1;
    }

    return s;
}

int create_listen_socket_bind_ipv4(struct sockaddr_in* sockaddr)
{
    /* create socket */
    int s = socket(AF_INET, SOCK_STREAM, 0);
    if (s == -1) {
        printd("socket() failed: %s\n", strerror(errno));
        return -1;
    }

    int enable = 1;
    if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(enable)) == -1) {
        printd("setsockopt(SO_REUSEPORT) failed: %s\n", strerror(errno));
        close(s);
        return -1;
    }

    /* bind it to interface */
    socklen_t addrlen = sizeof(struct sockaddr_in);
    if (bind(s, (struct sockaddr*)sockaddr, addrlen) == -1) {
        printd("bind() failed: %s\n", strerror(errno));
        printd("Verify that the configured interface has a valid IPv6 link local address configured.\n");
        close(s);
        return -1;
    }

    /* listen on this socket */
    if (listen(s, 3) == -1) {
        printd("listen() failed: %s\n", strerror(errno));
        close(s);
        return -1;
    }

    /* retrieve the actual port number we are listening on */
    if (getsockname(s, (struct sockaddr*)sockaddr, &addrlen) == -1) {
        printd("getsockname() failed: %s\n", strerror(errno));
        close(s);
        return -1;
    }

    return s;
}

int create_stream_socket_connect_ipv6(struct sockaddr_in6* sockaddr)
{
    /* create socket */
    int s = socket(AF_INET6, SOCK_STREAM, 0);
    if (s == -1) {
        printd("socket() failed: %s\n", strerror(errno));
        return -1;
    }

    int enable = 1;
    if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(enable)) == -1) {
        printd("setsockopt(SO_REUSEPORT) failed: %s\n", strerror(errno));
        close(s);
        return -1;
    }

    /* connect it to remote host  */
    socklen_t addrlen = sizeof(struct sockaddr_in6);
    if (connect(s, (struct sockaddr*)sockaddr, addrlen) == -1) {
        printd("bind() failed: %s\n", strerror(errno));
        printd("Verify that the configured interface has a valid IPv6 link local address configured.\n");
        close(s);
        return -1;
    }

    return s;
}

int create_stream_socket_connect_ipv4(struct sockaddr_in* sockaddr)
{
    /* create socket */
    int s = socket(AF_INET, SOCK_STREAM, 0);
    if (s == -1) {
        printd("socket() failed: %s\n", strerror(errno));
        return -1;
    }

    int enable = 1;
    if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(enable)) == -1) {
        printd("setsockopt(SO_REUSEPORT) failed: %s\n", strerror(errno));
        close(s);
        return -1;
    }

    /* connect it to remote host */
    socklen_t addrlen = sizeof(struct sockaddr_in);
    if (connect(s, (struct sockaddr*)sockaddr, addrlen) == -1) {
        printd("bind() failed: %s\n", strerror(errno));
        printd("Verify that the configured interface has a valid IPv6 link local address configured.\n");
        close(s);
        return -1;
    }

    return s;
}


int string_strip_chr(char* out, const char* xin, char target, int N)
{
    int i, j=0;
    for (i=0; i<N; i++)
    {
        if (xin[i] != target)
        {
            out[j++] = xin[i];
        }
    }
    out[j++]='\0';
    return j;
}
