/*
 Copyright (C) 2020 Fredrik Öhrström (gpl-3.0-or-later)

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 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.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "threads.h"

#include <unistd.h>
#include <sys/resource.h>
#include <stdio.h>

#if defined(__APPLE__) && defined(__MACH__)
#include <mach/mach.h>
#endif

using namespace std;

pthread_t main_thread_ {};

pthread_t event_loop_thread_ {};
function<void()> event_loop_entry_point_;

pthread_t timer_loop_thread_ {};
function<void()> timer_loop_entry_point_;

pthread_t getMainThread()
{
    return main_thread_;
}

void recordMyselfAsMainThread()
{
    main_thread_ = pthread_self();
}

pthread_t getEventLoopThread()
{
    return event_loop_thread_;
}

void *dispatch(void *ptr)
{
    function<void()> *cb = static_cast<function<void()>*>(ptr);
    (*cb)();
    return NULL;
}

void startEventLoopThread(function<void()> cb)
{
    event_loop_entry_point_ = cb;
    pthread_create(&event_loop_thread_, NULL, dispatch, &event_loop_entry_point_);
}

pthread_t getTimerLoopThread()
{
    return timer_loop_thread_;
}

void startTimerLoopThread(function<void()> cb)
{
    timer_loop_entry_point_ = cb;
    pthread_create(&timer_loop_thread_, NULL, dispatch, &timer_loop_entry_point_);
}

pthread_mutex_t wmbus_devices_lock_ = PTHREAD_MUTEX_INITIALIZER;
const char *wmbus_devices_lock_func_ = "";
pid_t       wmbus_devices_lock_pid_;

pthread_mutex_t serial_devices_lock_ = PTHREAD_MUTEX_INITIALIZER;
const char *serial_devices_lock_func_ = "";
pid_t       serial_devices_lock_pid_;

pthread_mutex_t event_loop_lock_ = PTHREAD_MUTEX_INITIALIZER;
const char *event_loop_lock_func_ = "";
pid_t       event_loop_lock_pid_;

pthread_mutex_t timers_lock_ = PTHREAD_MUTEX_INITIALIZER;
const char *timers_lock_func_ = "";
pid_t       timers_lock_pid_;

RecursiveMutex serial_devices_mutex_("serial_devices_mutex");

RecursiveMutex::RecursiveMutex(const char *name)
    : name_(name), locked_in_func_(""), locked_by_pid_(0)
{
    pthread_mutexattr_init(&attr_);
    pthread_mutexattr_settype(&attr_, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&mutex_, &attr_);
}

RecursiveMutex::~RecursiveMutex()
{
    pthread_mutex_destroy(&mutex_);
    pthread_mutexattr_destroy(&attr_);
}

void RecursiveMutex::lock()
{
    pthread_mutex_lock(&mutex_);
}

void RecursiveMutex::unlock()
{
    pthread_mutex_unlock(&mutex_);
}

Lock::Lock(RecursiveMutex *rmutex, const char *func_name)
{
    rmutex_ = rmutex;
    func_name_ = func_name;
    trace("[LOCKING] %s %s (%s %d)\n", rmutex_->name_, func_name_, rmutex_->locked_in_func_, rmutex->locked_by_pid_);
    pthread_mutex_lock(&rmutex_->mutex_);
    rmutex->locked_in_func_ = func_name;
    rmutex->locked_by_pid_ = getpid();
    trace("[LOCKED]  %s %s (%s %d)\n", rmutex_->name_, func_name_, rmutex_->locked_in_func_, rmutex->locked_by_pid_);
}

Lock::~Lock()
{
    trace("[UNLOCKING] %s %s (%s %d)\n", rmutex_->name_, func_name_, rmutex_->locked_in_func_, rmutex_->locked_by_pid_);
    pthread_mutex_unlock(&rmutex_->mutex_);
    rmutex_->locked_in_func_ = "";
    rmutex_->locked_by_pid_ = 0;
    trace("[UNLOCKED]  %s %s (%s %d)\n", rmutex_->name_, func_name_, rmutex_->locked_in_func_, rmutex_->locked_by_pid_);
}


Semaphore::Semaphore(const char *name)
    : name_(name)
{
    pthread_cond_init(&condition_, NULL);
    pthread_mutex_init(&mutex_, NULL);
}

Semaphore::~Semaphore()
{
    pthread_mutex_destroy(&mutex_);
    pthread_cond_destroy(&condition_);
}

bool Semaphore::wait()
{
    trace("[WAITING] %s\n", name_);

    pthread_mutex_lock(&mutex_);
    struct timespec wait_until;
    clock_gettime(CLOCK_REALTIME, &wait_until);
    wait_until.tv_sec += 5;

    int rc = 0;
    for (;;)
    {
        rc = pthread_cond_timedwait(&condition_, &mutex_, &wait_until);
        if (!rc) break;
        if (rc == EINTR) continue;
        if (rc == ETIMEDOUT) break;
        error("(thread) pthread cond timedwait ERROR %d\n", rc);
    }

    pthread_mutex_unlock(&mutex_);

    trace("[WAITED] %s %s\n", name_, (rc==ETIMEDOUT)?"TIMEOUT":"OK");

    // Return true if proper wait.
    // Return false if timeout!!!!
    return rc != ETIMEDOUT;
}

void Semaphore::notify()
{
    trace("[NOTIFY] %s\n", name_);
    int rc = pthread_cond_signal(&condition_);
    if (rc)
    {
        error("(thread) pthread cond signal ERROR\n");
    }
}

size_t getPeakRSS()
{
    struct rusage rusage;

    getrusage( RUSAGE_SELF, &rusage );

#if defined(__APPLE__) && defined(__MACH__)
    return (size_t)rusage.ru_maxrss;
#else
    return (size_t)(rusage.ru_maxrss * 1024L);
#endif
}

size_t getCurrentRSS()
{
#if defined(__APPLE__) && defined(__MACH__)

    struct mach_task_basic_info info;

    mach_msg_type_number_t info_count = MACH_TASK_BASIC_INFO_COUNT;

    int rc = task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &info_count);
    if (rc != KERN_SUCCESS)
    {
        return 0;
    }
    return (size_t)info.resident_size;

#elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__)

    long rss = 0;
    FILE *fp = fopen("/proc/self/statm", "r");
    if (!fp)
    {
        return 0;
    }
    int rc = fscanf(fp, "%*s%ld", &rss);
    if (rc != 1)
    {
        fclose(fp);
        return 0;
    }
    fclose(fp);
    return (size_t)rss * (size_t)sysconf( _SC_PAGESIZE);

#endif
}
