//
// LDASTools AL - A library collection to provide an abstraction layer
//
// Copyright (C) 2018 California Institute of Technology
//
// LDASTools AL is free software; you may redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 (GPLv2) of the
// License or at your discretion, any later version.
//
// LDASTools AL is distributed in the hope that it will be useful, but
// without any warranty or even the implied warranty of merchantability
// or fitness for a particular purpose. See the GNU General Public
// License (GPLv2) for more details.
//
// Neither the names of the California Institute of Technology (Caltech),
// The Massachusetts Institute of Technology (M.I.T), The Laser
// Interferometer Gravitational-Wave Observatory (LIGO), nor the names
// of its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// You should have received a copy of the licensing terms for this
// software included in the file LICENSE located in the top-level
// directory of this package. If you did not, you can view a copy at
// http://dcc.ligo.org/M1500244/LICENSE
//

#include <ldastoolsal_config.h>

#if HAVE_SYS_KINFO_PROC_H
#include <sys/kinfo_proc.h>
#endif /* HAVE_SYS_KINFO_PROC_H */
#if HAVE_PROCFS_H
#if !defined( _LP64 ) && _FILE_OFFSET_BITS == 64
//-----------------------------------------------------------------------
// For 32-bit builds, cannot use LARGE FILE interface
//-----------------------------------------------------------------------
#undef _FILE_OFFSET_BITS
#undef _LARGE_FILES
#endif /* */

#include <sys/time.h>

#include <procfs.h>

#else /* HAVE_PROCFS_H */

#include <sys/types.h>
#if HAVE_SYS_SYSINFO_H
#include <sys/sysinfo.h>
#else /* HAVE_SYS_SYSINFO_H */
#if HAVE_SYS_SYSCTL_H
#include <sys/sysctl.h>
#endif /* HAVE_SYS_SYSCTL_H */
#include <sys/proc.h>
#endif /* HAVE_SYS_SYSINFO_H */
#if HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif /* HAVE_SYS_STAT_H */

#endif /* HAVE_PROCFS_H */

#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#include <fstream>
#include <iostream>
#include <pwd.h>
#include <sstream>
#include <stdexcept>

#include "ldastoolsal/types.hh"
#include "ldastoolsal/mutexlock.hh"
#include "ldastoolsal/PSInfo.hh"
#include "ldastoolsal/System.hh"
#include "ldastoolsal/UserInfoCache.hh"

using LDASTools::AL::PSInfo;
using LDASTools::AL::UserInfoCache;

namespace
{
#if !defined( HAVE_PROCFS_H )
    unsigned long get_total_ram( );

    static const int           PageSize = getpagesize( );
    static const long          Hertz = sysconf( _SC_CLK_TCK );
    static const unsigned long TotalRam = get_total_ram( );
#endif /* !defined( HAVE_PROCFS_H ) */

    static void
    getpsinfo( pid_t PID, PSInfo::psinfo_type& Query )
    {
#if HAVE_PROCFS_PSINFO
        //-------------------------------------------------------------------
        // Solaris
        //-------------------------------------------------------------------
        psinfo_t       info;
        struct timeval now;

        std::ostringstream filename;
        int                fd;

        filename << "/proc/" << PID << "/psinfo";
        fd = open( filename.str( ).c_str( ), O_RDONLY );
        if ( fd < 0 )
        {
            std::ostringstream msg;
            msg << "Unable to open: " << filename.str( );
            throw std::runtime_error( msg.str( ) );
        }
        read( fd, &info, sizeof( info ) );
        close( fd );
        //-------------------------------------------------------------------
        // Obtain the current time
        //-------------------------------------------------------------------
        gettimeofday( &now, NULL );
        //-------------------------------------------------------------------
        // Translate user id to user name
        //-------------------------------------------------------------------
        {
            const UserInfoCache::UserInfo& ui =
                UserInfoCache::UID( info.pr_uid );
            Query.s_user = ui.Name( );
        }
        //-------------------------------------------------------------------
        // Populate the information
        //-------------------------------------------------------------------
        Query.s_state = ( info.pr_wstat == 0 ) ? 'R' : 'Z';
        Query.s_vsz = info.pr_size;
        Query.s_rsz = info.pr_rssize;
        Query.s_pcpu = ( info.pr_pctcpu / float( 0x8000 ) ) * 100.0;
        Query.s_pmem = ( info.pr_pctmem / float( 0x8000 ) ) * 100.0;
        Query.s_etime = ( now.tv_sec - info.pr_start.tv_sec );
        Query.s_fname = info.pr_fname;
        Query.s_args = info.pr_psargs;
#else /* HAVE_PROCFS_PSINFO */
        //-------------------------------------------------------------------
        // Linux
        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        // Extract information from /proc/<pid>/stat file
        //-------------------------------------------------------------------
        unsigned long uptime, idle;

        std::ifstream uptimed( "/proc/uptime" );

        uptimed >> uptime >> idle;
        uptimed.close( );

        std::ostringstream filename;

        unsigned long starttime, stime = 0, utime = 0, vsize;
        long          cutime = 0, cstime = 0, rss;
        char          comm_buffer[ 1024 ];
        char*         comm = comm_buffer;
        char          state;

#if HAVE_PROCFS_STAT
#if defined( __GNUC__ )
        __attribute__( ( unused ) )
#endif /* defined(__GNUC__) */
        int pid = 0,
            ppid = 0, pgrp = 0;
        int           session, tty_nr;
        unsigned long flags, minflt, cminflt, majflt, cmajflt;
        long          tpgid, priority, nice, ph0, itrealvalue;

        filename << "/proc/" << PID << "/stat";
        std::ifstream statd( filename.str( ).c_str( ) );

        if ( !statd.good( ) )
        {
            std::ostringstream msg;
            msg << "Unable to open: " << filename.str( );
            throw std::runtime_error( msg.str( ) );
        }

        statd >> pid >> comm_buffer >> state >> ppid >> pgrp >> session >>
            tty_nr >> tpgid >> flags >> minflt >> cminflt >> majflt >>
            cmajflt >> utime >> stime >> cutime >> cstime >> priority >> nice >>
            ph0 >> itrealvalue >> starttime >> vsize >> rss;
        statd.close( );
#else /* HAVE_PROCFS_STAT */
        {
            static const unsigned int MIB_LEN = 4;

            int               mib[ MIB_LEN ];
            size_t            len = MIB_LEN;
            struct kinfo_proc kp;

            std::cerr << "DEBUG: getpid(): " << getpid( ) << std::endl;

            errno = 0;

            mib[ 0 ] = CTL_KERN;
            mib[ 1 ] = KERN_PROC;
            mib[ 2 ] = KERN_PROC_PID;
            mib[ 3 ] = PID;

            len = sizeof( kp );
            errno = 0;
            if ( sysctl( mib, MIB_LEN, &kp, &len, NULL, 0 ) == -1 )
            {
                std::ostringstream msg;

                msg << "Unable to obtain info for process: " << PID << "["
                    << mib[ 3 ] << "]"
                    << " (" << LDASTools::System::ErrnoMessage( ) << ")";
                throw std::runtime_error( msg.str( ) );
            }
            // pid = kp.kp_proc.p_pid;
#ifdef SRUN
            switch ( kp.kp_proc.p_stat )
            {
            case SIDL:
                state = 'I';
                break;
            case SRUN:
                state = 'R';
                break;
            case SSLEEP:
                state = 'S';
                break;
            case SSTOP:
                state = 'T';
                break;
            case SZOMB:
                state = 'Z';
                break;
            default:
                state = '?';
                break;
            }
#endif /*SRUN */
            std::cerr << "DEBUG: p_comm: " << kp.kp_proc.p_comm << std::endl;
            starttime = kp.kp_proc.p_starttime.tv_sec;
            ::strcpy( comm, kp.kp_proc.p_comm );

            // ppid = kp.kp_eproc.e_ppid;
            // pgrp = kp.kp_eproc.e_pgid;
            vsize = kp.kp_eproc.e_xsize;
            rss = kp.kp_eproc.e_xrssize;

            const struct rusage* uptr = kp.kp_proc.p_ru;
            if ( uptr )
            {
                static const long u = 1000000;
                utime = uptr->ru_utime.tv_sec * u + uptr->ru_utime.tv_usec;
                stime = uptr->ru_stime.tv_sec * u + uptr->ru_stime.tv_usec;
            }
        }
#endif /* HAVE_PROCFS_STAT */

        if ( ( *comm == '(' ) && ( comm[ strlen( comm ) - 1 ] == ')' ) )
        {
            ++comm;
            comm[ strlen( comm ) - 1 ] = '\0';
        }
        unsigned long seconds = uptime - ( starttime / Hertz );

        //-------------------------------------------------------------------
        // Translate user id to user name
        //-------------------------------------------------------------------
        {
            static const int pwbuffer_size = sysconf( _SC_GETPW_R_SIZE_MAX );

            char*          pwbuffer;
            struct passwd  pw_entry;
            struct passwd* pw_entry_result = (struct passwd*)NULL;
            struct stat    stat_buf;

            ::stat( filename.str( ).c_str( ), &stat_buf );
            pwbuffer = (char*)calloc( sizeof( char ), pwbuffer_size + 1 );
            if ( pwbuffer )
            {
                getpwuid_r( stat_buf.st_uid,
                            &pw_entry,
                            pwbuffer,
                            pwbuffer_size,
                            &pw_entry_result );
            }
            if ( pw_entry_result )
            {
                Query.s_user = pw_entry_result->pw_name;
            }
            else
            {
                std::ostringstream uid;

                uid << stat_buf.st_uid;
                Query.s_user = uid.str( );
            }
            if ( pwbuffer )
            {
                free( pwbuffer );
            }
        }
        //-------------------------------------------------------------------
        // Extract information from /proc/<pid>/cmdline file
        //-------------------------------------------------------------------
        std::ostringstream cmd_filename;
        int                cmdline_fd;
        char               buffer[ 4097 ];
        int                buffer_read_size;

        cmd_filename << "/proc/" << PID << "/cmdline";

        cmdline_fd = open( cmd_filename.str( ).c_str( ), O_RDONLY );
        buffer_read_size = read( cmdline_fd, buffer, sizeof( buffer ) - 1 );
        close( cmdline_fd );

        if ( buffer_read_size > 0 )
        {
            int pos = 0;

            //-----------------------------------------------------------------
            // Null terminate the buffer to ensure we do not read past the
            //   data that was read in.
            //-----------------------------------------------------------------
            buffer[ buffer_read_size ] = '\0';

            //-----------------------------------------------------------------
            // Get information
            //-----------------------------------------------------------------
            Query.s_args = "";
            while ( ( pos < buffer_read_size ) && ( buffer[ pos ] != '\0' ) )
            {
                if ( pos > 0 )
                {
                    Query.s_args += " ";
                }
                Query.s_args += &( buffer[ pos ] );
                pos += ::strlen( &( buffer[ pos ] ) ) + 1;
            }
        }

        //-------------------------------------------------------------------

        Query.s_state = state;
        Query.s_vsz = vsize / 1024;
        Query.s_rsz = ( rss * PageSize ) / 1024;
        Query.s_pcpu = ( seconds )
            ? ( ( 100.0 * float( utime + stime + cutime + cstime ) ) / Hertz ) /
                seconds
            : 0;
        Query.s_pmem = 100.0 * ( Query.s_rsz / TotalRam );
        Query.s_etime = seconds; /* :TODO: */
        Query.s_fname = comm;
#endif /* HAVE_PROCFS_PSINFO */
    }
} // namespace

namespace LDASTools
{
    namespace AL
    {
        PSInfo::PSInfo( pid_t PID ) : m_pid( PID )
        {
        }

        void
        PSInfo::operator( )( PSInfo::psinfo_type& Query ) const
        {
            getpsinfo( m_pid, Query );
            Query.s_pid = m_pid;
        }
    } // namespace AL
} // namespace LDASTools

#if !defined( HAVE_PROCFS_H )
namespace
{
    unsigned long
    get_total_ram( )
    {
#if HAVE_SYS_SYSINFO_H
        struct sysinfo info;

        sysinfo( &info );
        return info.totalram;
#elif HAVE_SYS_SYSCTL_H
        int    mib[ 2 ], maxram;
        size_t len;

        mib[ 0 ] = CTL_HW;
        mib[ 1 ] = HW_PHYSMEM;
        len = sizeof( maxram );

        sysctl( mib, 2, &maxram, &len, NULL, 0 );

        return maxram;
#else
#error Have no way to determine total system memory
#endif
    }
} // namespace

#endif /* */
