/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "apr.h"
#include "apr_pools.h"
#include "apr_network_io.h"
#include "apr_poll.h"

#include "tcn.h"
#if defined(__linux__)
#include <sys/sysinfo.h>
#elif defined(sun)
#include <unistd.h>
#include <sys/swap.h>
#include <procfs.h>
#include <kstat.h>
#include <sys/sysinfo.h>
#endif

#if defined(DARWIN)
#include <mach/mach_init.h>
#include <mach/mach_host.h>
#include <mach/host_info.h>
#include <sys/sysctl.h>
#include <sys/stat.h>
#endif

#include <syslog.h>
#include <stdarg.h>

#ifndef LOG_WARN
#define LOG_WARN LOG_WARNING
#endif

#if defined(sun)
#define MAX_PROC_PATH_LEN 64
#define MAX_CPUS 512
#define PSINFO_T_SZ sizeof(psinfo_t)
#define PRUSAGE_T_SZ sizeof(prusage_t)

static int proc_open(const char *type)
{
    char proc_path[MAX_PROC_PATH_LEN+1];

    sprintf(proc_path, "/proc/self/%s", type);
    return open(proc_path, O_RDONLY);
}

static int proc_read(void *buf, const size_t size, int filedes)
{
    ssize_t bytes;

    if (filedes >= 0) {
        bytes = pread(filedes, buf, size, 0);
        if (bytes != size)
            return -1;
        else
            return 0;
    }
    else
        return -1;
}

#endif

TCN_IMPLEMENT_CALL(jboolean, OS, is)(TCN_STDARGS, jint type)
{
    UNREFERENCED_STDARGS;
    if (type == 1)
        return JNI_TRUE;
#if defined(__linux__)
    else if (type == 5)
        return JNI_TRUE;
#endif
#if defined(sun)
    else if (type == 6)
        return JNI_TRUE;
#endif
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
    else if (type == 7)
        return JNI_TRUE;
#endif
#if defined(__APPLE__) || defined(DARWIN)
    else if (type == 8)
        return JNI_TRUE;
#endif
    else
        return JNI_FALSE;
}

TCN_IMPLEMENT_CALL(jint, OS, info)(TCN_STDARGS,
                                   jlongArray inf)
{
    jint rv;
    int  i;
    jsize ilen = (*e)->GetArrayLength(e, inf);
    jlong *pvals = (*e)->GetLongArrayElements(e, inf, NULL);

    UNREFERENCED(o);
    if (ilen < 16) {
        return APR_EINVAL;
    }
    for (i = 0; i < 16; i++)
        pvals[i] = 0;
#if defined(__linux__)
    {
        struct sysinfo info;
        if (sysinfo(&info))
            rv = apr_get_os_error();
        else {
            static char buf[1024];
            unsigned long user = 0;
            unsigned long system = 0;
            long idle = 0;
            long long starttime = 0;
            int fd;
            int len;
            long sys_clk_tck = sysconf(_SC_CLK_TCK); /* number of system ticks per second */

            pvals[0] = (jlong)(info.totalram  * info.mem_unit);
            pvals[1] = (jlong)(info.freeram   * info.mem_unit);
            pvals[2] = (jlong)(info.totalswap * info.mem_unit);
            pvals[3] = (jlong)(info.freeswap  * info.mem_unit);
            pvals[4] = (jlong)(info.sharedram * info.mem_unit);
            pvals[5] = (jlong)(info.bufferram * info.mem_unit);
            pvals[6] = (jlong)(100 - (info.freeram * 100 / info.totalram));

            if (sys_clk_tck >= 0) {
                /* Get total CPU times from /proc/stat */
                /* Example for the first line: cpu  2095497 8176 3280198 908667841 1543576 28867 375399 0 0 */
                /* According to the man pages, the numbers are given in units of USER_HZ:
                 * user mode, user mode with low priority (nice), system mode, and the idle task.
                 * Additional values can be ignored. */
                fd = open("/proc/stat", O_RDONLY);
                if (fd != -1) {
                    len = read(fd, buf, sizeof buf - 1);
                    if (len > 0) {
                        buf[len] = '\0';
                        if (sscanf(buf, "cpu %lu %*d %lu %ld", &user, &system, &idle) == 3) {
                            pvals[7] = (jlong)(idle * 1000 / sys_clk_tck * 1000); /* Idle Time in microseconds */
                            pvals[8] = (jlong)(system * 1000 / sys_clk_tck * 1000); /* Kernel Time in microseconds */
                            pvals[9] = (jlong)(user * 1000 / sys_clk_tck * 1000); /* User Time in microseconds */
                        }
                    }
                    close(fd);
                }
                /* Get process CPU times from /proc/self/stat */
                /* Example for the first line:
                 * 6309 (csh) S 6308 6309 6309 34816 7124 4202496 15119 252261 1 30 21 58 1537 1447 20 0 1 0 916031966 ... */
                /* Parsing it according to man -s 5 proci:
                 * pid %d, comm %s, state %c, ppid %d pgrp %d, session %d, tty_nr %d, tpgid %d, flags %u,
                 * minflt %lu, cminflt %lu,  majflt %lu, cmajflt %lu,
                 * utime %lu (!), stime %lu (!), cutime %ld (!), cstime %ld (!),
                 * priority %ld, nice %ld, num_threads %ld, itrealvalue %ld,
                 * starttime %llu (!) */
                fd = open("/proc/self/stat", O_RDONLY);
                if (fd != -1) {
                    len = read(fd, buf, sizeof buf - 1);
                    if (len > 0) {
                        buf[len] = '\0';
                        if (sscanf(buf, "%*d %*s %*c %*d %*d %*d %*d %*d %*u"
                                        " %*u %*u %*u %*u"
                                        " %lu %lu %*d %*d"
                                        " %*d %*d %*d %*d"
                                        "%llu", &user, &system, &starttime) == 3) {
                            pvals[10] = (jlong)(apr_time_now() - apr_time_make(info.uptime - starttime / sys_clk_tck, 0)); /* Process creation time (apr_time_t) */
                            pvals[11] = (jlong)(system * 1000 / sys_clk_tck * 1000); /* Process System Time in microseconds */
                            pvals[12] = (jlong)(user * 1000 / sys_clk_tck * 1000); /* Process User Time in microseconds */
                        }
                    }
                    close(fd);
                }
            }

            rv = APR_SUCCESS;
        }
    }
#elif defined(sun)
    {
        /* static variables with basic procfs info */
        static long creation = 0;              /* unix timestamp of process creation */
        static int psinf_fd = 0;               /* file descriptor for the psinfo procfs file */
        static int prusg_fd = 0;               /* file descriptor for the usage procfs file */
        static size_t rss = 0;                 /* maximum of resident set size from previous calls */
        /* static variables with basic kstat info */
        static kstat_ctl_t *kstat_ctl = NULL;  /* kstat control object, only initialized once */
        static kstat_t *kstat_cpu[MAX_CPUS];   /* array of kstat objects for per cpu statistics */
        static int cpu_count = 0;              /* number of cpu structures found in kstat */
        static kid_t kid = 0;                  /* kstat ID, for which the kstat_ctl holds the correct chain */
        /* non-static variables - general use */
        int res = 0;                           /* general result state */
        /* non-static variables - sysinfo/swapctl use */
        long ret_sysconf;                      /* value returned from sysconf call */
        long tck_dividend;                     /* factor used by transforming tick numbers to microseconds */
        long tck_divisor;                      /* divisor used by transforming tick numbers to microseconds */
        long sys_pagesize = sysconf(_SC_PAGESIZE); /* size of a system memory page in bytes */
        long sys_clk_tck = sysconf(_SC_CLK_TCK); /* number of system ticks per second */
        struct anoninfo info;                  /* structure for information about sizes in anonymous memory system */
        /* non-static variables - procfs use */
        psinfo_t psinf;                        /* psinfo structure from procfs */
        prusage_t prusg;                       /* usage structure from procfs */
        size_t new_rss = 0;                    /* resident set size read from procfs */
        time_t now;                            /* time needed for calculating process creation time */
        /* non-static variables - kstat use */
        kstat_t *kstat = NULL;                 /* kstat working pointer */
        cpu_sysinfo_t cpu;                     /* cpu sysinfo working pointer */
        kid_t new_kid = 0;                     /* kstat ID returned from chain update */
        int new_kstat = 0;                     /* flag indicating, if kstat structure has changed since last call */

        rv = APR_SUCCESS;

        if (sys_pagesize <= 0) {
            rv = apr_get_os_error();
        }
        else {
            ret_sysconf = sysconf(_SC_PHYS_PAGES);
            if (ret_sysconf >= 0) {
                pvals[0] = (jlong)((jlong)sys_pagesize * ret_sysconf);
            }
            else {
                rv = apr_get_os_error();
            }
            ret_sysconf = sysconf(_SC_AVPHYS_PAGES);
            if (ret_sysconf >= 0) {
                pvals[1] = (jlong)((jlong)sys_pagesize * ret_sysconf);
            }
            else {
                rv = apr_get_os_error();
            }
            res=swapctl(SC_AINFO, &info);
            if (res >= 0) {
                pvals[2] = (jlong)((jlong)sys_pagesize * info.ani_max);
                pvals[3] = (jlong)((jlong)sys_pagesize * info.ani_free);
                pvals[6] = (jlong)(100 - (jlong)info.ani_free * 100 / info.ani_max);
            }
            else {
                rv = apr_get_os_error();
            }
        }

        if (psinf_fd == 0) {
            psinf_fd = proc_open("psinfo");
        }
        res = proc_read(&psinf, PSINFO_T_SZ, psinf_fd);
        if (res >= 0) {
            new_rss = psinf.pr_rssize*1024;
            pvals[13] = (jlong)(new_rss);
            if (new_rss > rss) {
                rss = new_rss;
            }
            pvals[14] = (jlong)(rss);
        }
        else {
            psinf_fd = 0;
            rv = apr_get_os_error();
        }
        if (prusg_fd == 0) {
            prusg_fd = proc_open("usage");
        }
        res = proc_read(&prusg, PRUSAGE_T_SZ, prusg_fd);
        if (res >= 0) {
            if (creation <= 0) {
                time(&now);
                creation = (long)(now - (prusg.pr_tstamp.tv_sec -
                                         prusg.pr_create.tv_sec));
            }
            pvals[10] = (jlong)(creation * 1000000L);
            pvals[11] = (jlong)((jlong)prusg.pr_stime.tv_sec * 1000000L +
                                (prusg.pr_stime.tv_nsec / 1000L));
            pvals[12] = (jlong)((jlong)prusg.pr_utime.tv_sec * 1000000L +
                                (prusg.pr_utime.tv_nsec / 1000L));
            pvals[15] = (jlong)(prusg.pr_majf);
        }
        else {
            prusg_fd = 0;
            rv = apr_get_os_error();
        }

        if (sys_clk_tck <= 0) {
            rv = apr_get_os_error();
        }
        else {
            tck_dividend = 1000000L;
            tck_divisor = sys_clk_tck;
            for (i = 0; i < 3; i++) {
                if (tck_divisor % 2 == 0) {
                    tck_divisor = tck_divisor / 2;
                    tck_dividend = tck_dividend / 2;
                }
                if (tck_divisor % 5 == 0) {
                    tck_divisor = tck_divisor / 5;
                    tck_dividend = tck_dividend / 5;
                }
            }
            if (kstat_ctl == NULL) {
                kstat_ctl = kstat_open();
                kid = kstat_ctl->kc_chain_id;
                new_kstat = 1;
            } else {
                new_kid = kstat_chain_update(kstat_ctl);
                if (new_kid < 0) {
                    res=kstat_close(kstat_ctl);
                    kstat_ctl = kstat_open();
                    kid = kstat_ctl->kc_chain_id;
                    new_kstat = 1;
                } else if (new_kid > 0 && kid != new_kid) {
                    kid = new_kid;
                    new_kstat = 1;
                }
            }
            if (new_kstat) {
                cpu_count = 0;
                for (kstat = kstat_ctl->kc_chain; kstat; kstat = kstat->ks_next) {
                    if (strncmp(kstat->ks_name, "cpu_stat", 8) == 0) {
                        kstat_cpu[cpu_count++]=kstat;
                    }
                }
            }
            for (i = 0; i < cpu_count; i++) {
                new_kid = kstat_read(kstat_ctl, kstat_cpu[i], NULL);
                if (new_kid >= 0) {
                    cpu = ((cpu_stat_t *)kstat_cpu[i]->ks_data)->cpu_sysinfo;
                    if ( tck_divisor == 1 ) {
                        pvals[7] += (jlong)(((jlong)cpu.cpu[CPU_IDLE]) * tck_dividend);
                        pvals[7] += (jlong)(((jlong)cpu.cpu[CPU_WAIT]) * tck_dividend);
                        pvals[8] += (jlong)(((jlong)cpu.cpu[CPU_KERNEL]) * tck_dividend);
                        pvals[9] += (jlong)(((jlong)cpu.cpu[CPU_USER]) * tck_dividend);
                    } else {
                        pvals[7] += (jlong)(((jlong)cpu.cpu[CPU_IDLE]) * tck_dividend / tck_divisor);
                        pvals[7] += (jlong)(((jlong)cpu.cpu[CPU_WAIT]) * tck_dividend / tck_divisor);
                        pvals[8] += (jlong)(((jlong)cpu.cpu[CPU_KERNEL]) * tck_dividend / tck_divisor);
                        pvals[9] += (jlong)(((jlong)cpu.cpu[CPU_USER]) * tck_dividend / tck_divisor);
                    }
                }
            }
        }

        /*
         * The next two are not implemented yet for Solaris
         * inf[4]  - Amount of shared memory
         * inf[5]  - Memory used by buffers
         *
         */
    }

#elif defined(DARWIN)

    uint64_t mem_total;
    size_t len = sizeof(mem_total);

    vm_statistics_data_t vm_info;
    mach_msg_type_number_t info_count = HOST_VM_INFO_COUNT;

    sysctlbyname("hw.memsize", &mem_total, &len, NULL, 0);
    pvals[0] = (jlong)mem_total;

    host_statistics(mach_host_self (), HOST_VM_INFO, (host_info_t)&vm_info, &info_count);
    pvals[1] = (jlong)(((double)vm_info.free_count)*vm_page_size);
    pvals[6] = (jlong)(100 - (pvals[1] * 100 / mem_total));
    rv = APR_SUCCESS;

/* DARWIN */
#else
    rv = APR_ENOTIMPL;
#endif
   (*e)->ReleaseLongArrayElements(e, inf, pvals, 0);
    return rv;
}

#define LOG_MSG_DOMAIN                   "Native"


TCN_IMPLEMENT_CALL(jstring, OS, expand)(TCN_STDARGS, jstring val)
{
    jstring str;
    TCN_ALLOC_CSTRING(val);

    UNREFERENCED(o);

    /* TODO: Make ${ENVAR} expansion */
    str = (*e)->NewStringUTF(e, J2S(val));

    TCN_FREE_CSTRING(val);
    return str;
}

TCN_IMPLEMENT_CALL(void, OS, sysloginit)(TCN_STDARGS, jstring domain)
{
    const char *d;
    TCN_ALLOC_CSTRING(domain);

    UNREFERENCED(o);
    if ((d = J2S(domain)) == NULL)
        d = LOG_MSG_DOMAIN;

    openlog(d, LOG_CONS | LOG_PID, LOG_LOCAL0);
    TCN_FREE_CSTRING(domain);
}

TCN_IMPLEMENT_CALL(void, OS, syslog)(TCN_STDARGS, jint level,
                                     jstring msg)
{
    TCN_ALLOC_CSTRING(msg);
    int id = LOG_DEBUG;
    UNREFERENCED(o);

    switch (level) {
        case TCN_LOG_EMERG:
            id = LOG_EMERG;
        break;
        case TCN_LOG_ERROR:
            id = LOG_ERR;
        break;
        case TCN_LOG_NOTICE:
            id = LOG_NOTICE;
        break;
        case TCN_LOG_WARN:
            id = LOG_WARN;
        break;
        case TCN_LOG_INFO:
            id = LOG_INFO;
        break;
    }
    syslog (id, "%s", J2S(msg));

    TCN_FREE_CSTRING(msg);
}
