/**************************************************************************
Главный заголовочный файл для матлаба. Включает дейфайны для S-Function,
объявляет базовые функции для симуляции МК и подключает базовые библиотеки:
для S-Function		"simstruc.h"
для МК				"main.h"
для симуляции		"stm32f4xx_matlab_conf.h"

**************************************************************************/
#ifndef _CONTROLLER_H_
#define _CONTROLLER_H_

typedef void* HANDLE;

#include "mcu_wrapper_conf.h"  // For stm simulate functions
#include "simstruc.h"               // For S-Function variables
#include <process.h>                // For threads

// Parameters of S_Function
#define NPARAMS     1       // number of input parametrs (only Ts)
#define PORT_WIDTH  16      // width of one port
#define PORT_NUMB   2       // amount of ports

// Parametrs of MCU simulation
#define MAX_STACK_SIZE      0xFFFF  // If stack is bigger than this number - exit simulations.
#define MAX_WHILE_TIMEOUT   0xFFF   // timeout for while. If after this amount of cycles app stil in while - stop app and go to wrapper
#define TIME_FOR_MCU_APP    0xFF   //


/** MCU HANDLE STRUCTURE */
/** 
  * @brief  MCU handle Structure definition.
  * @var    pStackPtr_Origin - pointer to the "beginning" of the stack (RSP before go to MCU app)
  * @vars   Main stop context - context of MCU app at stop point.
            Included: registers, pointer to stack and saved stack.
  * @var    Main start context - context of wrapper, before go to MCU app @ref setjmp/longjmp.
  * @vars   Flags and counters - flags and counter that control MCU app.
  * @note   Do not change stucrure before Stack_Buff, the assembly code uses fixed address offsets.
  */
typedef struct _DEL_MCUHandleTypeDef {
    uint64_t*       pStackPtr_Origin;           /* указатель на "начало" стека - конец стека у управляющей МКом функции */

    // Main stop context
    _JUMP_BUFFER    REGS;                       /* регистры во время приостановки программы МК */
    int long        Stack_Size;                 /* размер стека - от "начала" до "верхнушки" */
    uint64_t*       pStackPtr_Current;          /* указатель на последний элемент стека */
    uint64_t        Stack_Buff[MAX_STACK_SIZE]; /* содержимое стека от "начала" до "верхнушки" во время приостановки */

    // Main start context
    jmp_buf         main_start;                 /* буффер, для выхода из программы МК - longjmp(main_start) */

    // Flags and counters
    int             While_Timeout;              /* счетчик, после истечения которого while прерывается */
    unsigned        Stop_MCU_App : 1;           /* флаг для остановки программы МК */
    unsigned        Resume_MCU_App : 1;         /* флаг для возобновления работы программы МК */
    unsigned        Get_RSP_Origin : 1;         /* флаг для получения указателя на "начало" стека */

}DEL_MCUHandleTypeDef;
extern DEL_MCUHandleTypeDef hmcu; /* extern для видимости переменной во всех файлах */

// Variable for param of S-Function
extern double SIM_Sample_Time; // sample time


//-------------------------------------------------------------//
//-------------------  SAVE CONTEXT DEFINES  ------------------//
/** SAVE STACK */
/**
  * @brief  Save stack of running program.
  * @param  _hmcu_ - MCU Handle structure with registers and stack buff for filling.
  * 
  * @code   @ref _SAVE_STACK_CODE_ to see for replace _SAVE_STACK_ macros (for debug).
  */
#define _SAVE_STACK_(_hmcu_)                    if(1){                                                                              \
/* Сброс индекса буфера для стека */                _hmcu_.Stack_Size = 0;                                                          \
/* Пока весь необходимый стек не сохранен */        native_while(  _hmcu_.pStackPtr_Current != _hmcu_.pStackPtr_Origin)             \
/* Если буфер не переполнен: */                         if(_hmcu_.Stack_Size <= MAX_STACK_SIZE)                                     \
        /* Сохранение стека */                             _hmcu_.Stack_Buff[_hmcu_.Stack_Size++] = *_hmcu_.pStackPtr_Current++;    \
/* Если буфер переполнен: */                            else {                                                                      \
        /* Сообщение об ошибке и выход из программы */      printf("Max stack size is reached\n");                                  \
                                                            exit(0);                                                                \
                                                }}else
/***************************************************************/

/** SAVE PROGRAMM COUNTER */
/**
  * @brief  Save programm counter (RIP register) of running program.
  * @param  _hmcu_ - MCU Handle structure with registers and stack buff for filling
  * @note   Используется вместе с @ref _SAVE_CONTEXT_ для определения точки возврата.
  *         Вызывается до или после _SAVE_CONTEXT_, как вам нужно.
  * @ret    Store address of next instruction after this macros in Rip
  */
#define _SAVE_RIP_(_hmcu_) _hmcu_.REGS.Rip = getNextAddr()
/***************************************************************/
//-------------------  SAVE CONTEXT DEFINES  ------------------//
//-------------------------------------------------------------//

//-------------------------------------------------------------//
//------------------  SIMULINK WHILE DEFINES  -----------------//
/** DEFINE TO WHILE WITH SIMULINK WHILE */
/**
  * @brief  Redefine C while statement with sim_while() macro.
  * @param  _expression_ - expression for while.
  * @note   @ref sim_while для подробностей.
  */
#define while(_expression_)                          sim_while(_expression_) /* while который будет использоваться в симулинке */

  /** DEFAULT WHILE */
  /**
    * @brief  Default/Native C while statement.
    * @param  _expression_ - expression for while.
    * @note   Данный while - аналог обычного while, без дополнительного функционала.
    */
#define native_while(_expression_)                   for(; (_expression_); )
    /***************************************************************/

    /** SIMULINK WHILE */
    /**
      * @brief  While statement for emulate MCU code in Simulink.
      * @param  _expression_ - expression for while.
      * @note   Данный while, работает как обычный, пока флаг окончания симуляции (flag_to_end)
      *         равен нулю. Т.е. симуляция продолжается. 
                Когда время симуляции закончилась (флаг выставляется в 1) все while в коде МК будут пропускаться
      * 
      *
      * @code   @ref sim_while_CODE_ to see for replace while sim_while (for debug).
      */
extern unsigned flag_to_end;
#define sim_while(_expression_)                     while((_expression_)&&(flag_to_end == 0))


//#define sim_while(_expression_)                     while(0);if(_expression_&&(hmcu.While_Timeout>-5)){ \
///* Установка таймаута для while */                      hmcu.While_Timeout = MAX_WHILE_TIMEOUT;         \
//                                                    }                                                   \
///* Цикл while - for(;_expression_;) */              native_while(_expression_)                          \
///* Если таймаут истек */                                if(hmcu.While_Timeout-- < 0 ) {                 \
///* Cброс таймаута, чтобы на след шаге войти в тело цикла */ hmcu.While_Timeout = 0;                     \
///* Установка флага для остановки программы */               hmcu.Stop_MCU_App = 1;                      \
///* Установка флага для возврата в код МК на след шаге */    hmcu.Resume_MCU_App = 1;                    \
///* Сохранение контекста */                                  saveProgramContext();                       \
//                                                            if(hmcu.Stop_MCU_App)                       \
///* Возврат в оболочку */                                        longjmp(hmcu.main_start, 1);            \
//                                                        }                                               \
///* Иначе - выполнение тела цикла */                     else
      //------------------  SIMULINK WHILE DEFINES  -----------------//
      //-------------------------------------------------------------//

//-------------------------------------------------------------//
//---------------- SIMULATE FUNCTIONS PROTOTYPES  -------------//
/** MCU STEP SIMULATION */
/**
  * @brief  Read from simulink S-Block Inputs and write to MCU I/O ports.
  * @param  in - inputs of S-Function.
  * @param  disc - discrete array of S-Function. Outputs would be written from disc.
  * @param  time - current time of simulation (in second).
  * @note   Вызывает главную функцию main из пользовательского кода и управляет её ходом:
  *         Прерывает её, если она попала в бесконечный while, симулирует периферию
  *         и возвращает в точку остановки на следующем шаге.
  */
  /* simulate controller step */
void MCU_Step_Simulation(SimStruct *S, const real_T *in, 
                                real_T *disc, time_T time);     /* step simulation */

/** SIMULATE MCU PERIPHERAL */
/**
  * @brief  Simulate peripheral of MCU
  * @note   Пользовательский код, который симулирует работу периферии МК.
  */
void MCU_Periph_Simulation(void);                               /* MCU peripheral simulation */

/** MCU WRAPPER INITIALIZATION */
/**
  * @brief  Initialize structures and variables for simulating MCU.
  * @note   Пользовательский код, который будет настраивать все структуры.
  */
void SIM_Initialize_Simulation(void);                           /* initialize MCU simulation */
/** MCU WRAPPER DEINITIALIZATION */
/**
  * @brief  Deinitialize structures and variables for simulating MCU.
  * @note   Пользовательский код, который будет очищать все структуры.
  */
void SIM_deInitialize_Simulation(void);                         /* SIM_deInitialize_Simulation MCU simulation */
/** READ INPUTS S-FUNCTION */
/**
  * @brief  Read from simulink S-Block Inputs and write to MCU I/O ports.
  * @param  in - inputs of S-Function.
  * @note   Пользовательский код, который записывает в порты ввода-вывода из disc.
  */
void MCU_readInputs(real_T* in);
/** WRITE OUTPUTS S-FUNCTION */
/**
  * @brief  Read from MCU I/O ports and write to simulink S-Block Outputs.
  * @param  disc - discrete array of S-Function. Outputs would be written from disc.
  * @note   Пользовательский код, который записывает в disc порты ввода-вывода.
  */
void MCU_writeOutputs(real_T* disc);
/** SAVE CONTEXT OF PROGRAMM */
/**
  * @brief  Save context (stack, registers) of running program.
  * @note   Данная функция устанавливает счетчик комманд на начало функции.
  *         Так что возврат будет произведен в начало этой функции.
  */
void saveProgramContext(void);
/** GET NEXT INSTRUCTION ADDRESS */
/**
  * @brief  Gives the address where this function will return.
  */
void* getNextAddr(void);

//---------------- SIMULATE FUNCTIONS PROTOTYPES  -------------//
//-------------------------------------------------------------//

#endif // _CONTROLLER_H_


/** MACROSES REPLACEMENT CODE */

/** _SAVE_STACK_CODE_
* CODE TO REPLACE _SAVE_STACK_() MACROS (FOR DEBUG) */

/*
    hmcu.Stack_Size = 0; // Сброс индекса буфера для стека
    // Пока весь необходимый стек не сохранен
    native_while(hmcu.pStackPtr_Current != hmcu.pStackPtr_Origin)
        if (hmcu.Stack_Size <= MAX_STACK_SIZE)      // Если буфер не переполнен
            hmcu.Stack_Buff[hmcu.Stack_Size++] = *hmcu.pStackPtr_Current++; // Сохранение стека
        else {                                      // Если буфер переполнен
            printf("Max stack size is reached\n");  // Сообщение об ошибке и выход из программы
            exit(0); }
*/
/***************************************************************/

/** sim_while_CODE_
* CODE TO REPLACE sim_while() MACROS (FOR DEBUG) */
/*
    while(0);if(_expression_&&(hmcu.While_Timeout>-5)){
        hmcu.While_Timeout = MAX_WHILE_TIMEOUT; // Установка таймаута для while
    }
    native_while(_expression_) // Цикл while - for(;_expression_;)
        if (hmcu.While_Timeout-- < 0) { // Если таймаут истек
            hmcu.While_Timeout = 0;     // Сброс таймаута, чтобы на след шаге войти в тело цикла
            hmcu.Stop_MCU_App = 1;      // Установка флага для остановки программы
            hmcu.Resume_MCU_App = 1;    // Установка флага для возврата в код МК на след шаге
            saveProgramContext();       // Сохранение контекста
            if(hmcu.Stop_MCU_App)       // Если приложение было остановлено
                longjmp(hmcu.main_start, 1); // Возврат в оболочку
            // иначе (при возвращении в код МК) продолжить выполнение кода
        }
        else // Иначе - выполнение тела цикла
*/
/***************************************************************/