/*
 * Modem_Core.c
 *
 * Implementacja funkcji bazowych do komunikacji z modulem SIM7000E na
 * STM32 (HAL): wysylanie komend AT przez DMA, odbior i parsowanie
 * odpowiedzi, obsluga bledow i PWRKEY. Wspolna dla wszystkich
 * funkcjonalnosci modemu (GPS, SMS, itd.), ktore korzystaja z tych
 * funkcji przez wskaznik na sim7000e_t.
 */

/* Includes ------------------------------------------------------------------*/
#include "Modem_Core.h"

/* Private user code -----------------------------------------------------------*/

// Wskaznik do UART-u diagnostycznego (VCP przez ST-Link) - ustawiany w main.c
extern UART_HandleTypeDef huart2;
#define DEBUG_UART huart2

// Wypisuje tekst diagnostyczny na USART2 (kanal do PC). Uzywane wylacznie
// do celow debugowania, nie wplywa na logike modemu.
void Debug_Print(const char *text)
{
    HAL_UART_Transmit(&DEBUG_UART, (uint8_t*)text, strlen(text), 100);
}

/*
 * Wysyla surowy ciag znakow do modemu przez DMA.
 * Tresc kopiowana jest najpierw do sim->bufout - pola struktury, a nie
 * lokalnej zmiennej - poniewaz HAL_UART_Transmit_DMA jest funkcja
 * nieblokujaca: zwraca sterowanie od razu, a rzeczywista wysylka
 * odbywa sie w tle. Gdyby zrodlem danych byla zmienna lokalna funkcji,
 * po jej wyjsciu trafilaby na stos i mogla zostac nadpisana, zanim
 * DMA skonczylby jej faktycznie wysylac.
 * Po zleceniu transmisji funkcja czeka na powrot UART-u do stanu
 * READY, aby kolejne wywolanie (np. nastepnej komendy AT) nie
 * nadpisalo sim->bufout w trakcie trwajacej jeszcze wysylki.
 */
void Modem_Send_String(sim7000e_t *sim, const char *str)
{
	// Czekamy, aż ewentualna poprzednia transmisja DMA dobiegnie końca
	    while(sim->huart->gState != HAL_UART_STATE_READY) {
	        // Krótkie opóźnienie lub brak blokowania całej pętli głównej
	    }

	    strncpy(sim->bufout, str, MODEM_BUFFER_SIZE - 1);
	    sim->bufout[MODEM_BUFFER_SIZE - 1] = '\0';

	    HAL_UART_Transmit_DMA(sim->huart, (uint8_t*)sim->bufout, strlen(sim->bufout));
}

// Zeruje bufor odbiorczy wewnatrz struktury wskazywanej przez sim
void Clear_Modem_Buffer(sim7000e_t *sim)
{
    memset(sim->bufin, 0, sizeof(sim->bufin));
    sim->bufin_index = 0;
}

/*
 * Czysci flagi bledow sprzetowych UART (Overrun, Noise, Framing, Parity).
 * Moze to byc przydatne po intensywnej transmisji, gdy w wyniku bledu
 * sprzetowego UART dalszy odbior zostalby zablokowany. Wyczyszczenie
 * flag pozwala rozpoczac kolejny odbior bez pozostalosci po
 * ewentualnych bledach peryferium UART.
 */
void Modem_Clear_UART_Errors(sim7000e_t *sim)
{
    __HAL_UART_CLEAR_OREFLAG(sim->huart);
    __HAL_UART_CLEAR_NEFLAG(sim->huart);
    __HAL_UART_CLEAR_FEFLAG(sim->huart);
    __HAL_UART_CLEAR_PEFLAG(sim->huart);
}

/*
 * Zatrzymuje program w przypadku bledu krytycznego, z ktorego dalsza
 * czesc procedury nie moze sensownie kontynuowac. Ustawia sim->status
 * na MODEM_STATE_ERROR, wypisuje komunikat diagnostyczny na kanal
 * debug i zatrzymuje program w petli nieskonczonej.
 */
void Fatal_Error(sim7000e_t *sim, const char *msg)
{
    sim->status = MODEM_STATE_ERROR;

    Debug_Print("\r\n### BLAD KRYTYCZNY: ");
    Debug_Print(msg);
    Debug_Print(" ###\r\n");
    Debug_Print(sim->bufin);

    while(1)
    {
        // Celowo pusta petla - program zatrzymuje sie tutaj.
    }
}

/*
 * Odbiera znaki z modemu bajt po bajcie i dopisuje je do sim->bufin,
 * az do wykrycia oczekiwanego fragmentu tekstu ("expected") lub uplywu
 * czasu "timeout_ms". Dodatkowo wykrywa typowe komunikaty bledu modemu
 * (ERROR, +CME ERROR, +CMS ERROR) i przerywa oczekiwanie natychmiast.
 *
 * Na starcie czysci flagi bledow UART (patrz Modem_Clear_UART_Errors) -
 * to zapobiega sytuacji, w ktorej nierozwiazany Overrun Error z
 * poprzedniej intensywnej transmisji blokuje caly dalszy odbior
 * w milczeniu.
 *
 * expected -> fragment tekstu, na ktory czekamy (np. "OK", "READY", ">")
 * timeout_ms -> maksymalny czas oczekiwania w milisekundach
 * return -> 1 jesli znaleziono oczekiwany tekst, 0 w przypadku bledu/timeoutu
 */
uint8_t Modem_Wait_For_Response(sim7000e_t *sim, const char *expected, uint32_t timeout_ms)
{
    uint8_t rx;
    uint32_t start = HAL_GetTick();

    sim->status = MODEM_STATE_CMD;

    Modem_Clear_UART_Errors(sim);

    while((HAL_GetTick() - start) < timeout_ms)
    {
        if(HAL_UART_Receive(sim->huart, &rx, 1, 10) == HAL_OK)
        {
            // Filtr znakow: akceptujemy tylko znaki drukowalne ASCII oraz CR/LF.
            if(!((rx >= 32 && rx <= 126) || rx == '\r' || rx == '\n'))
            {
                continue;
            }

            if(sim->bufin_index < MODEM_BUFFER_SIZE - 1)
            {
                sim->bufin[sim->bufin_index++] = rx;
                sim->bufin[sim->bufin_index] = '\0';
            }
            else
            {
                sim->status = MODEM_STATE_ERROR;
                return 0;
            }

            if(strstr(sim->bufin, "\r\nERROR\r\n") ||
               strstr(sim->bufin, "+CME ERROR") ||
               strstr(sim->bufin, "+CMS ERROR"))
            {
                sim->status = MODEM_STATE_ERROR;
                return 0;
            }

            if(strstr(sim->bufin, expected))
            {
                if(strcmp(expected, ">") == 0)
                {
                    sim->status = MODEM_STATE_OK;
                    return 1;
                }

                if(strstr(sim->bufin, "\r\nOK\r\n"))
                {
                    sim->status = MODEM_STATE_OK;
                    return 1;
                }
            }
        }
        else
        {
            // Brak odczytu w tym cyklu - sprawdzamy, czy to nie Overrun Error.
            // Jesli tak, czyscimy flage natychmiast, zamiast czekac do konca
            // timeoutu z zablokowanym odbiorem.
            if(__HAL_UART_GET_FLAG(sim->huart, UART_FLAG_ORE))
            {
                Modem_Clear_UART_Errors(sim);
            }
        }
    }

    sim->status = MODEM_STATE_TIMEOUT;
    return 0;
}

/*
 * Wysyla pojedyncza komende AT do modemu i czeka na okreslona odpowiedz.
 * Przed wyslaniem czysci bufor odbiorczy, aby nie mieszac z poprzednia
 * odpowiedzia. W przypadku bledu wypisuje diagnostyke.
 * cmd -> tekst komendy AT (z zakonczeniem \r\n)
 * expected -> oczekiwany fragment odpowiedzi
 * timeout_ms -> maksymalny czas oczekiwania
 * return -> 1 sukces, 0 blad/timeout
 */
uint8_t Modem_Send_Command(sim7000e_t *sim, const char *cmd, const char *expected, uint32_t timeout_ms)
{
    Clear_Modem_Buffer(sim);

    Debug_Print("\r\n>> ");
    Debug_Print(cmd);

    Modem_Send_String(sim, cmd);

    if(!Modem_Wait_For_Response(sim, expected, timeout_ms))
    {
        Debug_Print("\r\nTIMEOUT/BLAD\r\n");
        Debug_Print(sim->bufin);
        Debug_Print("\r\n");

        return 0;
    }

    Debug_Print("\r\n<< ");
    Debug_Print(sim->bufin);
    Debug_Print("\r\n");

    return 1;
}

/*
 * Wielokrotnie probuje podstawowej komendy "AT", nie ruszajac PWRKEY.
 * attempts -> liczba prob
 * return 1 -> jesli modem odpowiedzial w ktorejkolwiek probie, 0 jesli brak odpowiedzi
 */
uint8_t Modem_Try_AT(sim7000e_t *sim, uint8_t attempts)
{
    while(attempts--)
    {
        if(Modem_Send_Command(sim, "AT\r\n", "OK", 1500))
        {
            return 1;
        }

        HAL_Delay(1000);
    }

    return 0;
}
