/*
 * hal_os.cpp
 * ru cp1251
 *
 *  Created on: 7 дек. 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
 */

#include <cstddef>
#include <cassert>
#include "OsSync.h"
#include "OsTime.h"
extern "C"{
#include <lib/list.h>
}
#include <mcu_isr.h>



#if !SYNC_CS_INLINE

void EnterCS(void){
    ENTER_CS();
}

void ExitCS(void){
    EXIT_CS();
}

#endif


void ostimeout_stop(os_timeout_t* timer){
    if (!etimer_expired(timer))
        etimer_stop(timer);
    timer->timer.interval = 0;
}

bool ostimeout_least(os_timeout_t* timer){
    return (etimer_expiration_time(timer) - clock_now());
}

void ostimeout_restart(os_timeout_t* timer){
    etimer_reset(timer);
    if (!etimer_expired(timer))
        return;
    for (int i = 3; (i > 0); i-- ){
        if (!etimer_expired(timer))
            return;
    }
    etimer_set(timer, timer->timer.interval);
}

struct OS_CriticalSec_t OS_CriticalSec;


void waitable_process::awake(){
    if (ostimeout_active(&wake_time))
    if (!etimer_expired(&wake_time))
        etimer_stop(&wake_time);
};

// останавливает и сбрасывает таймер сна текущей нитки. следующий sleep запустит
//      ожидание заново
void sleep_wake(){
    TaskHandle_t task = (TaskHandle_t)task_current();

    // если задача не активная - нечего на ней просыпаться
    const waitable_process::wtr_mark_t mask = waitable_process::markWAITING_MASK;
    if ( (task->mark &mask) != (waitable_process::markWAITING_THREAD &mask))
        return;

    if ( !etimer_expired(&task->wake_time) ){
        etimer_stop(&task->wake_time);
        task->drop_waken();
    }
}
// для улучшения безопасности кода, подстраховываю останов сна ожидающей нитки
//  в завершении блокировок.
//  Лок не должен быть прерываем в процессе ожидания, он должен вызываться до
//      тех пор пока не вернет результат завершения. Если это условие не
//      соблюдено, то возможен сбой запуска ожидания следующего лока.
//  чтобы эти ситуации както блокировать, делаю сброс сна при завершении любого
//      лока на этом примитиве
#if 0
#define safe_wake()    sleep_wake()
#else
#define safe_wake()
#endif

//***************************************************************************
//                      mutex_t
ActiveMutex_h MutexInit(ActiveMutex_h m){
    m->locked_tasks = NULL;
    m->owner = NULL;
    m->level = 0;
    return m;
}

bool list_have(list_t list, void *item)
{
    list_t l;

    for(l = list; l != NULL; l = (list_t)*l) {
        if ((void*)l == item)
            return true;
    }

  return false;
}

void LockCurrentTask(void* & l){
    TaskHandle_t task = (TaskHandle_t)task_current();
    sync_assert (task != NULL);
    sync_assert (task->mark == lockable_process::markLOCKING_THREAD);
    if (list_have(&l, &(task->owner_list)))
        {;}
    else {
        sync_assert( task->owner_list == NULL );
        list_push( &l, &(task->owner_list));
    }
    //task->owner_list = l;
    //l = task;
}

void UnlockCurrentTask(void* & l){
    TaskHandle_t task = (TaskHandle_t)task_current();
    sync_assert (task->mark == lockable_process::markLOCKING_THREAD);
    list_remove(&l, &(task->owner_list) );
}

void UnlockTasks(void* & l){
    void* p = l;
    l = NULL;
    void* next = NULL;
    for ( ; p != NULL ;  ) {
        next = *((list_t)p);
        TaskHandle_t i = lockable_process::this_on_ownerlist(p);
        p = next;
        sync_assert (i->mark == lockable_process::markLOCKING_THREAD);
        i->owner_list = NULL;
        process_poll(i);
    }
}

ActiveMutex_h MutexInit(ActiveMutex_t& m){
    m.level = 0;
    m.locked_tasks = NULL;
    m.owner = NULL;
    return &m;
}

bool MutexTry(mutex_t& m){
    sync_assert(m.is_valid());
    if (m.owner == NULL){
        m.owner = (TaskHandle_t)task_current();
        //sync_assert (m.owner->mark == lockable_process::markLOCKING_THREAD);
        m.level = 1;
        return true;
    }
    else if (m.owner == task_current()) {
        m.level++;
        return true;
    }
    return false;
}

PTResult MutexLock(mutex_t& m){
    if (MutexTry(m)){
        safe_wake();
        return ptOK;
    }
    LockCurrentTask(m.locked_tasks);
    return ptWAITING;
}

PTResult MutexTryLock(mutex_t& m, TickType_t to){
    if (MutexTry(m)){
        sleep_wake();
        return ptOK;
    }

    if (to <= 0){
        safe_wake();
        return ptNOK;
    }

    LockCurrentTask(m.locked_tasks);
    if (to == TO_INFINITE)
        return ptWAITING;

    PTResult res = sleep(to);
    if ( !PT_SCHEDULE( res ) ){
        UnlockCurrentTask(m.locked_tasks);
    }

    return res;
}

bool MutexUnlock(mutex_t& m){
      sync_assert(m.is_valid());
      m.level--;
      if (m.level <= 0){
          UnlockTasks(m.locked_tasks);
          m.owner = NULL;
      }
      return true;
}



//***************************************************************************
//                          BinSemaphore_t
BinSemaphore_h SemBinInit(BinSemaphore_t& s, unsigned start){
    sync_assert(s.is_valid());
    PT_SEM_INIT(&s, start);
    s.locked_tasks = NULL;
    return &s;
}

void SemUnlock(BinSemaphore_t& s){
    sync_assert(s.is_valid());
    UnlockTasks(s.locked_tasks);
}

PTResult SemTake(BinSemaphore_t& s){
    __disable_irq();
    if (PT_SEM_COUNT(&s)){
        s.tail = s.head;
        __enable_irq();
        safe_wake();
        return ptOK;
    }
    __enable_irq();

    sync_assert(s.is_valid());
    LockCurrentTask(s.locked_tasks);
    return ptWAITING;
}

PTResult SemTryTake(BinSemaphore_t& s, TickType_t to){
    __disable_irq();
    if (PT_SEM_COUNT(&s)){
        s.tail = s.head;
        __enable_irq();
        sleep_wake();
        return ptOK;
    }
    __enable_irq();

    if (to <= 0){
        safe_wake();
        return ptNOK;
    }

    sync_assert(s.is_valid());
    LockCurrentTask(s.locked_tasks);

    PTResult res = sleep(to);
    if ( !PT_SCHEDULE( res ) ){
        sync_assert(s.is_valid());
        UnlockCurrentTask(s.locked_tasks);
        return res;
    }
    return res;
}



//***************************************************************************
PTResult sleep(const unsigned ticks){
    TaskHandle_t task = (TaskHandle_t)task_current();
    const waitable_process::wtr_mark_t mask = waitable_process::markWAITING_MASK;
    assert( (task->mark &mask) == (waitable_process::markWAITING_THREAD &mask));

    if (task->is_awaken() || (ticks <= 0) ){
        task->drop_waken();
        return ptOK;
    }

    if (etimer_expired( &task->wake_time)){
        // таймер не запущен, поэтому стартану начало сна
        etimer_set(&task->wake_time, ticks);
    }
    if (etimer_expired( &task->wake_time)){
        task->drop_waken();
        return ptOK;
    }
    return ptWAITING;
}




//***************************************************************************
void ActiveSignal::destroy(){
    Unlock();
    events = 0;
}

ActiveSignal::signal_bits ActiveSignal::Unlock(){
    sync_assert(is_valid());
    UnlockTasks(locked_tasks);
    return events;
}

ActiveSignal::signal_bits ActiveSignal::set(signal_bits x){
    __disable_irq();
    x = events | x;
    bool changed = events != x;
    events = x;
    __enable_irq();
    if (changed & (locked_tasks!= NULL))
        return Unlock();
    return events;
};

ActiveSignal::signal_bits ActiveSignal::set_isr(signal_bits x){
    x = events | x;
    bool changed = events != x;
    events = x;
    if (changed & (locked_tasks!= NULL))
        return Unlock();
    return events;
}

ActiveSignal::signal_bits ActiveSignal::wait(signal_bits x, unsigned TOticks){
    signal_bits y = (events & x);
    return wait_checked(y, TOticks);
}

//* wait for all x is set
ActiveSignal::signal_bits ActiveSignal::wait_all(signal_bits x, unsigned TOticks){
    signal_bits y = (events & x);
    if (y != x)
        y = 0;
    return wait_checked( y , TOticks);
}

//* wait for any of x is set, and clear ones
ActiveSignal::signal_bits ActiveSignal::take(signal_bits x, unsigned TOticks){
    __disable_irq();
    signal_bits y = (events & x);
    events &= ~x;
    __enable_irq();
    return wait_checked(y, TOticks);
}

//* wait for any of x is set
ActiveSignal::signal_bits ActiveSignal::take_all(signal_bits x, unsigned TOticks){
    __disable_irq();
    signal_bits y = (events & x);
    if (y != x)
        y = 0;
    events &= ~y;
    __enable_irq();
    return wait_checked(y, TOticks);
}


ActiveSignal::signal_bits ActiveSignal::wait_checked(signal_bits passed, unsigned TOticks){
    if (passed != 0){
        sleep_wake();
        return passed | signalSOME;
    }

    if (TOticks <= 0){
        safe_wake();
        return ptNOK;
    }

    PTResult res = sleep(TOticks);
    if ( !PT_SCHEDULE( res ) ){
        sync_assert(is_valid());
        UnlockCurrentTask(locked_tasks);
    }
    return res;
}
