#define _GNU_SOURCE
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

#define SIG SIGRTMIN
#define SIG1 (SIGRTMIN+1)

void thread_attributes_test(void)
{   pthread_attr_t my_attrs;
    int scope;
    int inheritsched;
    int policy;
    struct sched_param param;

    pthread_getattr_np(pthread_self(),&my_attrs);

    pthread_attr_getscope(&my_attrs,&scope);
    printf("Zakres szeregowania watkow");
    switch (scope)
    {   case PTHREAD_SCOPE_SYSTEM:  printf("Wszystkie watki i procesy konkuruja w jednej puli priorytetow. \n");
                                    break;
        case PTHREAD_SCOPE_PROCESS: printf("Priorytety watkow sa definiowane na poziomie procesow. Procesy konkuruja na poziomie systemu\n");
                                    break;
        default:                    printf("Nieznany uklad przydzialu priorytetow dla watkow.\n");
                                    break;

    }
    printf("Dziedziczenie polityki szeregowania:  ");
    pthread_attr_getinheritsched(&my_attrs,&inheritsched);
    switch (inheritsched)
    {   case PTHREAD_INHERIT_SCHED: printf("Wlaczone jest dziedziczenie polityki szeregowania po rodzicu\n");
                                    break;
        case PTHREAD_EXPLICIT_SCHED:printf("Watki nie dziedzicza polityki szeregowania. Same ustalaja polityke szeregowania.\n");
                                    break;
        default:                    printf("Nierozpoznana polityka szertegowania\n");
                                    break;
    }

    printf("Wybrana polityka szeregowania:  ");
    pthread_attr_getschedpolicy(&my_attrs, &policy);
    switch (policy)
    {   case SCHED_FIFO:    printf("SCHED_FIFO.\n");
                            break;
        case SCHED_RR:      printf("SCHED_RR.\n");
                            break;
        case SCHED_OTHER:   printf("SCHED_OTHER.\n");
                            break;
        default:            printf("Nieznana polityka \n");
                            break;
    }
    pthread_getschedparam(pthread_self(),&policy, &param);
    printf("Priorytet biezacego wotku:  %d\n",param.sched_priority);
}

void *thread1(void *arg) {
    sigset_t set;
    int sig;
    timer_t timer1;
    struct itimerspec its1;
    struct sigevent sev1;
    struct timespec now;

    sigemptyset(&set);
    sigaddset(&set, SIG);

    sev1.sigev_notify = SIGEV_SIGNAL;
    sev1.sigev_signo = SIG;
    timer_create(CLOCK_REALTIME, &sev1, &timer1);

    clock_gettime(CLOCK_REALTIME, &now);
    its1.it_value.tv_sec = now.tv_sec + 2; // 2 sekundy opóżnienia
    its1.it_value.tv_nsec = now.tv_nsec;
    its1.it_interval.tv_sec = 1; // 1-dnosekudowy interwał
    its1.it_interval.tv_nsec = 0;

    timer_settime(timer1, TIMER_ABSTIME, &its1, NULL);

    printf("Watek 1 polityka szeregowania:\n");
    thread_attributes_test();

    while (1) {
        sigwait(&set, &sig);
        printf("Watek 1: obliczenia wznowione\n");
    }
    pthread_exit(NULL);
}

void *thread2(void *arg) {
    sigset_t set;
    int sig;
    timer_t timer2;
    struct itimerspec its2;
    struct sigevent sev2;
    struct timespec now;

    sigemptyset(&set);
    sigaddset(&set, SIG1);

    sev2.sigev_notify = SIGEV_SIGNAL;
    sev2.sigev_signo = SIG1;
    timer_create(CLOCK_REALTIME, &sev2, &timer2);

    clock_gettime(CLOCK_REALTIME, &now);
    its2.it_value.tv_sec = now.tv_sec + 2; // 2 sekundy opoznienia
    its2.it_value.tv_nsec = now.tv_nsec;
    its2.it_interval.tv_sec = 5; // 5-ciosekundowy interwał
    its2.it_interval.tv_nsec = 0;

    timer_settime(timer2, TIMER_ABSTIME, &its2, NULL);

    printf("Watek 2 polityka szeregowania:\n");
    thread_attributes_test();

    while (1) {
        sigwait(&set, &sig);
        printf("Watek 2: obliczenia wznowione\n");
    }
    pthread_exit(NULL);
}

int main() {
    pthread_t t1, t2;
    pthread_attr_t attr1, attr2;
    struct sched_param param1, param2, param_main;
    cpu_set_t cpuset;

    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIG);
    sigaddset(&set, SIG1);
    pthread_sigmask(SIG_BLOCK, &set, NULL);

    CPU_ZERO(&cpuset);
    CPU_SET(1, &cpuset); // Wybież procesor nr 2 w systemie
    printf("Liczba dostepnych CPU: %d\n", (int) sysconf(_SC_NPROCESSORS_ONLN));

    param_main.sched_priority = sched_get_priority_max(SCHED_FIFO) - 3;
    pthread_setschedparam(pthread_self(), SCHED_FIFO, &param_main);
    pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);

    printf("Glowny watek, polityka szeregowania:\n");
    thread_attributes_test();

    pthread_attr_init(&attr1);
    pthread_attr_setscope(&attr1, PTHREAD_SCOPE_SYSTEM);
    pthread_attr_setinheritsched(&attr1, PTHREAD_EXPLICIT_SCHED);
    pthread_attr_setschedpolicy(&attr1, SCHED_FIFO);
    param1.sched_priority = sched_get_priority_max(SCHED_FIFO) - 1;
    pthread_attr_setschedparam(&attr1, &param1);
    pthread_attr_setaffinity_np(&attr1, sizeof(cpu_set_t), &cpuset);
    pthread_create(&t1, &attr1, thread1, NULL);

    pthread_attr_init(&attr2);
    pthread_attr_setscope(&attr2, PTHREAD_SCOPE_SYSTEM);
    pthread_attr_setinheritsched(&attr2, PTHREAD_EXPLICIT_SCHED);
    pthread_attr_setschedpolicy(&attr2, SCHED_FIFO);
    param2.sched_priority = sched_get_priority_max(SCHED_FIFO) - 2;
    pthread_attr_setschedparam(&attr2, &param2);
    pthread_create(&t2, &attr2, thread2, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    pthread_attr_destroy(&attr1);
    pthread_attr_destroy(&attr2);

    return 0;
}
