/*
 * pt-SyncActive.hpp
 *
 *  Created on: 14 дек. 2018 г.
 *      Author: alexrayne <alexraynepe196@gmail.com>
  ------------------------------------------------------------------------
    Copyright (c) alexrayne

   All rights reserved.
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
   - Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
   - Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.
   - Neither the name of ARM nor the names of its contributors may be used
     to endorse or promote products derived from this software without
     specific prior written permission.
   *
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS AND CONTRIBUTORS BE
   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   POSSIBILITY OF SUCH DAMAGE. *
 * ------------------------------------------------------------------------
 *  Примитивы активоной синхронизации процессов contiki.
 */

#ifndef HAL_PORTING_CONTIKI_PT_SYNCACTIVE_HPP_
#define HAL_PORTING_CONTIKI_PT_SYNCACTIVE_HPP_


/* \abstract
 *
 *  протонитки contiki, засыпая в ожидании какогото события, должны активироваться
 *    источником события - либо сообщением process_post_synch, либо
 *    активацией process_poll.
 *  засыпание на время требует запуска таймаута на etimer - в этом случае подсистема
 *      etimer активирует нить по заданном времени.

 *  чтобы избежать ручного управления активацией, и сделать код боее похожим на
 *      ситуацию вытесняюих ОС, здесь представлены примитивы, которые своими ресурсами
 *      регистрируют ожидающие нити, и активируют их.
 *      Этих примитивы базируются на типе ActiveSync_t.
 *      Реализованы:ActiveMutex_t, BinSemaphore_t, ActiveSignal
 *      (Предоставлены их обертки в классах: ActiveMutex, BinSemaphore)
 *
 * \brief Активируемые процессы:
 *  для работы работы активных примивов используется ожидающий процесс. их реализованы
 *      2 типа :
 *      waitable_process - имеет встроенный etimer, и моэтому может делать sleep(x)
 *      lockable_process - помимо etimer, умеет связываться с активными примитивами.
 *      !!!WARN: блокировка автивного примитива не на lockable_process выдаст ассерт

 *  для создания процессов предоставляются макросы:
 *       WAITABLE_PROCESS
 *       LOCKABLE_PROCESS
 *
 * \brief Работа с активными примитивами:
 *  ActiveXXX.lock
 *  Активный примитив предоставляет функции Lock/Unlock.
 *  Функция lock - протонитка, она исполняется все вромея пока примитив пытается
 *          получить владение, и возвращает результат на котором
 *              PT_SCHEDULE(  Lock(...))  даст true
 *  !!! прерывание опроса Lock не дожидаясь завершения процесса может нарушить
 *      работу последующих вызовов.
 *
 *  \brief sleep(...):
 *  аналогично Lock реализована функция sleep( ... ) - эта протонитка должна
 *      опрашиваться до завершения сна.
 *      прервать сон снаружи можно вызовом  lockable_process.wake
 *
 * */


#include <stdint.h>
#include <stdbool.h>

#include "compiler-port.h"
#include <hal_types.h>

extern "C"{
#include <contiki-conf.h>
#include <sys/process.h>
#include <lib/list.h>
#include <sys/etimer.h>
#include <sys/pt-sem.h>
#include <ptx.h>
}
#include <os_isr.h>

#include <OsSync.h>



#define MARK32(a,b,c,d) ( ((a)<< 24) | ((b)<< 16) | ((c)<< 8) | d )

//************************************************************************
//                  waitable_process
//  это дескрирптор прото-нитки имеющей собственный etimer, на котором работают
//  примитивы ожидания sleep, lock(... , timeout)  и т.п.
//  этот локальный таймер должен упростить жизнь и код

struct waitable_process
    : public process
{
public:
    // чтобы обезопасить код, в примитивы работы с таймаутом введу
    //  проверку текущей нитки - что она допустимого типа
    //  тип нитки буду определять маркером.
    typedef unsigned long   wtr_mark_t;
    enum {
          markWAITING_THREAD = MARK32('w','t','h','r')
        , markWAITING_MASK   = MARK32(0, 0xff, 0xff, 0xff)
    };
    wtr_mark_t      mark;

    etimer          wake_time;

    // вызванная сторонней ниткой, оборвет текущий сон
    void awake();

    //отличить срабатывание таймера, от его первого запуска можно только по
    //      наличию установки интервала срабатывания
    bool is_awaken() const {
        if (wake_time.p != PROCESS_NONE)
            return false;
        return (wake_time.timer.interval != 0);
    };

    void drop_waken(){ wake_time.timer.interval = 0; };

public:
    typedef PT_THREAD((* thread_t)(struct pt *, process_event_t, process_data_t));

    waitable_process() :mark(markWAITING_THREAD){
        //PT_INIT(&pt);
        //wake_time.p = PROCESS_NONE;
        //wake_time.timer.interval = 0;
        //wake_time.next = NULL;
    };

    waitable_process( thread_t f )
        :mark(markWAITING_THREAD)
    {
        thread = f;
        //PT_INIT(&pt);
        //wake_time.p = PROCESS_NONE;
        //wake_time.timer.interval = 0;
    };

    waitable_process( thread_t f, const char* _name )
        :mark(markWAITING_THREAD)
    {
        thread = f;
#if !PROCESS_CONF_NO_PROCESS_NAMES
        name = _name;
#endif
        //PT_INIT(&pt);
        //wake_time.p = PROCESS_NONE;
        //wake_time.timer.interval = 0;
    };
};

#if PROCESS_CONF_NO_PROCESS_NAMES
#define WAITABLE_PROCESS(name, strname)              \
  PROCESS_THREAD(name, ev, data);           \
  struct waitable_process name = { NULL,             \
                          process_thread_##name }
#else
#define WAITABLE_PROCESS(name, strname)              \
  PROCESS_THREAD(name, ev, data);           \
  struct waitable_process name = { NULL, strname,        \
                          process_thread_##name }
#endif

//************************************************************************
//                  lockable_process
//  блокирующаяся нитка может становиться в список ожидания на блокирующем примитиве
//  блокирующий примитив сигналит всем ожидающим ниткам при изменении блокировки

typedef struct lockable_process * TaskHandle_t;

struct lockable_process
    : public waitable_process
{
public:
    enum {
        markLOCKING_THREAD = MARK32('l','t','h','r')
    };

    // когда нитка лочится на примитиве синхронизации она включается в список
    //  ожидающих примитива. Это поле и есть элемент этого списка
    void*               owner_list;

    static
    TaskHandle_t      this_on_ownerlist(void* item) {
        return (TaskHandle_t)( ((char*)(item)) - OFFSETOF(lockable_process, owner_list) );
    };

    lockable_process()
        :waitable_process()
        , owner_list(NULL)
    {
        mark = markLOCKING_THREAD;
    };

    lockable_process(thread_t f)
        :waitable_process(f)
        , owner_list(NULL)
    {
        mark = markLOCKING_THREAD;
    };

    lockable_process(thread_t f, const char* _name)
        :waitable_process(f, _name)
        , owner_list(NULL)
    {
        mark = markLOCKING_THREAD;
    };
};



//************************************************************************
#if PROCESS_CONF_NO_PROCESS_NAMES
#define LOCKABLE_PROCESS(name, strname)              \
  PROCESS_THREAD(name, ev, data);           \
  struct lockable_process name( process_thread_##name )
#else
#define LOCKABLE_PROCESS(name, strname)              \
  PROCESS_THREAD(name, ev, data);           \
  struct lockable_process name(process_thread_##name,  strname )
#endif



//***********************************\*************************************
//  активный примитив синхронизации имеет очередь ожидаюих ниток, которым
//      он сигналит при изменении состояния
template <unsigned long strict_mark =MARK32('s','y','n','c') >
struct ActiveSync_t{
    void*           locked_tasks;

    // для отладки в примитивы введу маркеры, позволят отличать примитивы, и
    //  делать тест их целостности runtime
    typedef unsigned long   mark_t;
    static const mark_t markCHECK = strict_mark;

#if SYNC_STRICT
    mark_t      mark;
    bool is_valid() const {return mark == markCHECK;};
    ActiveSync_t():locked_tasks(NULL),mark(markCHECK){};
#else
    bool is_valid() const {return true;}
    ActiveSync_t():locked_tasks(NULL){};
#endif

};



//************************************************************************
//                      Mutex

// мутех реализую связным списком заблочившихся на нем процессов
struct ActiveMutex_t : public ActiveSync_t<MARK32('m','u','t','x')>{
    TaskHandle_t    owner;
    int             level;
};
typedef ActiveMutex_t* ActiveMutex_h;



ActiveMutex_h MutexInit(ActiveMutex_t& m);

//bool MutexLock(ActiveMutex_h m, unsigned to_ticks);
PTResult MutexLock(ActiveMutex_t& m);
PTResult MutexTryLock(ActiveMutex_t& m, TickType_t to);
bool MutexUnlock(ActiveMutex_t& m);

INLINE
void MutexFree(ActiveMutex_t& m){
      m.level = 0;
      MutexUnlock(m);
}



//**************   uOS    *****************************
//* uOS kernel bindings. used by intfaces/mem.h

INLINE
PTResult mutex_lock(ActiveMutex_t* m){
    if (m != NULL)
        return MutexLock(*m);
    return ptOK;
}

INLINE
bool mutex_unlock(ActiveMutex_t* m){
    if (m != NULL)
        return MutexUnlock(*m);
    return true;
}

#define memsetw(d,s,z) memset(d,s,z)



//**************   semaphore    *****************************
//  текущая реализация базируется на pt_sem - имеет неблокирующие счетчики
//      источника, и догоняющего
// бинарный семафор при взятии всегда сбрасывает счетчик догоняющий к источнику
struct BinSemaphore_t
        : public pt_sem
        , public ActiveSync_t<MARK32('s','e','m','b')>
{
};

struct Semaphore_t : public BinSemaphore_t {
public:
    unsigned        max;
};
typedef struct semaphore_t* semaphore_h;
typedef struct BinSemaphore_t* BinSemaphore_h;



BinSemaphore_h SemBinInit(BinSemaphore_t& s, unsigned start = false);
PTResult SemTake(BinSemaphore_t& s);
PTResult SemTryTake(BinSemaphore_t& s, TickType_t to);

void SemUnlock(BinSemaphore_t& s);

INLINE
void SemFree(BinSemaphore_t& s){
    SemUnlock(s);
}

INLINE
bool SemGive(BinSemaphore_t& s){
    PT_SEM_SIGNAL( &process_current->pt, &s);
    if (s.locked_tasks != NULL)
        SemUnlock(s);
    return true;
}

INLINE
PTResult SemTake_isr(BinSemaphore_t& s){
  return SemTake(s);
}

INLINE
bool SemGive_isr(BinSemaphore_t& s){
    return SemGive(s);
}



//***************************************************************************
//                      Tasks

// \args ticks == 0 - sure wake
PTResult sleep(const unsigned ticks);

INLINE
PTResult sleep_ms(const unsigned ms){
    return sleep( OSTicksMS(ms) );
}



//***************************************************************************
// это классовые обертки над С-структурами. дают больше сахара в С++ код

class ActiveMutex {
    public:
        ActiveMutex_t m;
        ActiveMutex():m(){};
        ~ActiveMutex(){
                destroy();
        }
        bool init(){
            sync_assert(this != NULL);
            MutexInit(m);
            return true;
        };
        void destroy(){
            sync_assert(this != NULL);
            MutexFree(m);
        };

        PTResult lock() {
            sync_assert(this != NULL);
            return MutexLock(m);
        };
        PTResult lock(unsigned TOticks) {
            sync_assert(this != NULL);
            return MutexTryLock(m, TOticks);
        };
        void    unlock(){
            sync_assert(this != NULL);
            MutexUnlock(m);
        };
};

class BinSemaphore{
public:
    BinSemaphore_t s;
    BinSemaphore():s(){};
    ~BinSemaphore(){
        destroy();
    };

    bool init(){
      SemBinInit(s);
      return true;
    };

    void destroy() { SemFree(s); };

    // ожидание на семафоре
    PTResult take(){return SemTake(s);};
    PTResult take(TickType_t to){return SemTryTake(s, to);};

    // сигнал семафору
    bool give(){return SemGive(s);};

    // freeRTOS  для работы из прерываний требует отдельных функций
    PTResult take_isr(){return SemTake_isr(s);};
    bool give_isr(){return SemGive_isr(s);};

};

struct ActiveSignal
        : public ActiveSync_t<MARK32('s','i','g',' ')>
{
    public:
        typedef unsigned        EventBits_t;
        typedef EventBits_t     signal_bits;
        // валидное значение сигнала всегда возвращается с signalSOME
        // так можно отличить  signal_bits от PTResult_t
        enum {
            signalSOME      = 0x80000000u
        };

        ActiveSignal(): events(0){};
        ~ActiveSignal(){
            destroy();
        };

        bool init(){
            locked_tasks = NULL;
            events = 0;
            return true;
        };
        void destroy();

        signal_bits value() const {
                return events;
        };

        signal_bits set(signal_bits x);
        signal_bits give(signal_bits x){return set(x);};

        signal_bits set_isr(signal_bits x);
        signal_bits give_isr(signal_bits x){return set_isr(x);};

        signal_bits clear(const signal_bits x){
            __disable_irq();
            events &= ~x;
            __enable_irq();
            return events | signalSOME;
        };

        signal_bits clear_isr(signal_bits x){
            events &= ~x;
            return events | signalSOME;
        }

        static const unsigned toInfinite = ~0u;

        // \return signal_bits|signalSOME - when success
        //         PTResult_t - when wait
        //* wait for any of x is set
        signal_bits wait(signal_bits x, unsigned TOticks = toInfinite);
        //* wait for all x is set
        signal_bits wait_all(signal_bits x, unsigned TOticks = toInfinite);

        //* wait for any of x is set, and clear ones
        signal_bits take(signal_bits x, unsigned TOticks = toInfinite);
        //* wait for any of x is set
        signal_bits take_all(signal_bits x, unsigned TOticks = toInfinite);

        //* wait for any of x is set
        signal_bits signal_take_all(signal_bits post, signal_bits waitfor
                        , unsigned TOticks = toInfinite)
        {
            give(post);
            return take_all(waitfor, TOticks);
        };
    protected:
        EventBits_t         events;
        signal_bits Unlock();
        signal_bits wait_checked(signal_bits passed, unsigned TOticks);
};



#endif /* HAL_PORTING_CONTIKI_PT_SYNCACTIVE_HPP_ */
