#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <pthread.h>

#ifdef _WIN32
#   include <windows.h>
#else
#   include <unistd.h>
#endif

static
void SleepMs(unsigned ms)
{
#ifdef _WIN32
    Sleep(ms);
#else
    usleep( 1000 * ms );
#endif
}

static
unsigned GetMsTime(void)
{
#ifdef _WIN32
    return GetTickCount();
#else
    struct timespec ts;
    int err = clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);
    assert( err == 0 );

    return ts.tv_sec * 1000 + ts.tv_nsec / (1000*1000);
#endif
}

struct DummyInfo
{
    char description[128];
    unsigned version;
    int id;
    int level;
    bool activated;
};

struct ServerData
{
    pthread_t thrd;
    bool go_term;

    pthread_mutex_t lock;
    struct DummyInfo stat_info;

    unsigned period;
    unsigned w_count;
    unsigned w_spend;
};

struct ClientData
{
    pthread_t thrd;
    bool go_term;

    struct ServerData *server;
    struct DummyInfo stat_info;

    unsigned period;
    unsigned r_count;
    unsigned r_spend;
};

static
void* ServerThread(struct ServerData *data)
{
    for(; !data->go_term; SleepMs(data->period))
    {
        unsigned begin_time = GetMsTime();
        pthread_mutex_lock(&data->lock);

        // 更新記載某些狀態資訊的資料
        data->stat_info.version = 101;
        data->stat_info.id = 25;
        data->stat_info.level = ( data->stat_info.level + 1 ) % 5;
        data->stat_info.activated = !data->stat_info.activated;
        sprintf(
            data->stat_info.description,
            "A test server, level=%d",
            data->stat_info.level);

        // 為了模擬出更加容易被觀察的現象，
        // 這裡小休眠一下以放大在更新資訊時可能需要消耗的時間。
        SleepMs(100);

        pthread_mutex_unlock(&data->lock);
        unsigned end_time = GetMsTime();

        // 計算和儲存本次讀寫所消耗的時間
        data->w_count ++;
        data->w_spend += end_time - begin_time;
    }

    return NULL;
}

static
void* ClientThread(struct ClientData *data)
{
    for(; !data->go_term; SleepMs(data->period))
    {
        unsigned begin_time = GetMsTime();
        pthread_mutex_lock(&data->server->lock);

        // 從目標位置讀取狀態資料並存放到自己的記憶體位置，
        // 以待後續分析處理。
        // (當然在本範例中並不需要任何的後續處理)
        data->stat_info = data->server->stat_info;

        // 為了模擬出更加容易被觀察的現象，
        // 這裡小休眠一下以放大在讀取資訊時可能需要消耗的時間。
        SleepMs(50);

        pthread_mutex_unlock(&data->server->lock);
        unsigned end_time = GetMsTime();

        // 計算和儲存本次讀寫所消耗的時間
        data->r_count ++;
        data->r_spend += end_time - begin_time;
    }

    return NULL;
}

int main(int argc, char *argv[])
{
    // 建立一個每隔 300 毫秒更新資訊的執行緒

    struct ServerData server = { .period = 300 };
    pthread_mutex_init(&server.lock, NULL);

    pthread_create(
        &server.thrd,
        NULL,
        (void*(*)(void*)) ServerThread,
        &server);

    // 建立 6 個每隔大約 400 毫秒查詢一次資訊的執行緒

    struct ClientData client_list[] =
    {
        { .server = &server, .period = 350 },
        { .server = &server, .period = 370 },
        { .server = &server, .period = 390 },
        { .server = &server, .period = 410 },
        { .server = &server, .period = 430 },
        { .server = &server, .period = 450 },
    };

    static const int client_num =
        sizeof(client_list)/sizeof(client_list[0]);

    for(int i = 0; i < client_num; ++i)
    {
        pthread_create(
            &client_list[i].thrd,
            NULL,
            (void*(*)(void*)) ClientThread,
            &client_list[i]);
    }

    // 等待 15 秒鐘讓前面建立的那些執行緒運作一下

    SleepMs(15*1000);

    // 結束剛才建立的那些執行緒

    server.go_term = true;
    for(int i = 0; i < client_num; ++i)
        client_list[i].go_term = true;

    pthread_join(server.thrd, NULL);
    for(int i = 0; i < client_num; ++i)
        pthread_join(client_list[i].thrd, NULL);

    pthread_mutex_destroy(&server.lock);

    // 整理統計讀寫資訊的時間花費

    printf("Write statistics: count=%u, total-spend=%u, ave-spend=%u\n",
        server.w_count, server.w_spend, server.w_spend / server.w_count);

    unsigned r_count = 0, r_spend = 0;
    for(int i = 0; i < client_num; ++i)
    {
        r_count += client_list[i].r_count;
        r_spend += client_list[i].r_spend;
    }

    printf("Read statistics: count=%u, total-spend=%u, ave-spend=%u\n",
        r_count, r_spend, r_spend / r_count);

    return 0;
}
