/**
 * \file
 * \brief Buzzer driver interface.
 * \author Erich Styger, erich.styger@hslu.ch
 * \license SPDX-License-Identifier: BSD-3-Clause
 * This module implements the driver for the buzzer.
 */

#include "platform.h"
#if PL_CONFIG_USE_BUZZER
#include "Buzzer.h"
#include "McuGPIO.h"
#include "Trigger.h"
#include "McuUtility.h"
#include "McuRTOS.h"
#if PL_CONFIG_USE_SHELL
  #include "McuShell.h"
#endif

#define BUZZER_USE_FREERTOS_TIMER   (1)  /* if using FreeRTOS or software timer */

#if BUZZER_USE_FREERTOS_TIMER
  static TimerHandle_t timerToggle, timerDuration;
#else
  typedef struct {
    uint16_t buzPeriodTicks; /*!< number of trigger ticks for a PWM period */
    uint16_t buzIterationCntr; /*!< number of iterations */
  } BUZ_TrgInfo;

  static volatile BUZ_TrgInfo trgInfo;
#endif

static McuGPIO_Handle_t BUZ_Pin;

typedef struct {
  int freq; /* frequency */
  int ms; /* milliseconds */
} BUZ_Tune;

#define NOTE_C5 261
#define NOTE_D5 293
#define NOTE_E5 329
#define NOTE_F5 349
#define NOTE_G5 392

static const BUZ_Tune MelodyJingleBells[] =
{ /* freq, ms */
    {NOTE_E5, 4*100},
    {NOTE_E5, 4*100},
    {NOTE_E5, 4*200},
    {NOTE_E5, 4*100},
    {NOTE_E5, 4*100},
    {NOTE_E5, 4*200},
    {NOTE_E5, 4*100},
    {NOTE_G5, 4*100},
    {NOTE_C5, 4*100},
    {NOTE_D5, 4*100},
    {NOTE_E5, 4*200},
    {0, 4*100},
    {NOTE_F5, 4*100},
    {NOTE_F5, 4*100},
    {NOTE_F5, 4*100},
    {NOTE_F5, 4*100},
    {NOTE_F5, 4*100},
    {NOTE_E5, 4*100},
    {NOTE_E5, 4*100},
    {NOTE_E5, 4*100},
    {NOTE_E5, 4*100},
    {NOTE_D5, 4*100},
    {NOTE_D5, 4*100},
    {NOTE_E5, 4*100},
    {NOTE_D5, 4*200},
    {NOTE_G5, 4*200},
};

static const BUZ_Tune MelodyWelcome[] =
{ /* freq, ms */
    {300,500},
    {500,200},
    {300,100},
    {200,300},
    {500,400},
    {300,100},
    {200,300},
    {300,100},
    {200,300},
    {500,400},
};

static const BUZ_Tune MelodyButton[] =
{ /* freq, ms */
    {200,100},
    {600,100},
};

static const BUZ_Tune MelodyButtonLong[] =
{ /* freq, ms */
    {500,50},
    {100,100},
    {300,50},
    {150,50},
    {450,50},
    {500,50},
    {250,200},
};

static const BUZ_Tune MelodyMazeDestination[] =
{ /* freq, ms */
    {500,50},
    {100,100},
    {300,50},
    {150,50},
    {450,50},
    {500,50},
    {250,200},
    {500,50},
    {100,100},
    {300,50},
    {150,50},
    {450,50},
    {500,50},
    {250,200},
    {500,50},
    {100,100},
    {300,50},
    {150,50},
    {450,50},
    {500,50},
    {250,200},
};

typedef struct {
  int idx; /* current index */
  int maxIdx; /* maximum index */
#if !BUZZER_USE_FREERTOS_TIMER
  BUZ_TrgInfo trgInfo;
#endif
  const BUZ_Tune *melody;
} MelodyDesc;

#if BUZZER_USE_FREERTOS_TIMER
  static MelodyDesc *playingMelody = NULL;
#endif

static MelodyDesc BUZ_Melodies[] = {
#if BUZZER_USE_FREERTOS_TIMER
  {0, sizeof(MelodyWelcome)/sizeof(MelodyWelcome[0]),                   MelodyWelcome}, /* BUZ_TUNE_WELCOME */
  {0, sizeof(MelodyButton)/sizeof(MelodyButton[0]),                     MelodyButton}, /* BUZ_TUNE_BUTTON */
  {0, sizeof(MelodyButtonLong)/sizeof(MelodyButtonLong[0]),             MelodyButtonLong}, /* BUZ_TUNE_BUTTON_LONG */
  {0, sizeof(MelodyMazeDestination)/sizeof(MelodyMazeDestination[0]),   MelodyMazeDestination}, /* BUZ_TUNE_MAZE_DESTINATION */
  {0, sizeof(MelodyJingleBells)/sizeof(MelodyJingleBells[0]),           MelodyJingleBells}, /* BUZ_TUNE_JINGLE_BELLS */
#else
  {0, sizeof(MelodyWelcome)/sizeof(MelodyWelcome[0]),                   {0, 0}, MelodyWelcome}, /* BUZ_TUNE_WELCOME */
  {0, sizeof(MelodyButton)/sizeof(MelodyButton[0]),                     {0, 0}, MelodyButton}, /* BUZ_TUNE_BUTTON */
  {0, sizeof(MelodyButtonLong)/sizeof(MelodyButtonLong[0]),             {0, 0}, MelodyButtonLong}, /* BUZ_TUNE_BUTTON_LONG */
  {0, sizeof(MelodyMazeDestination)/sizeof(MelodyMazeDestination[0]),   {0, 0}, MelodyMazeDestination}, /* BUZ_TUNE_MAZE_DESTINATION */
  {0, sizeof(MelodyJingleBells)/sizeof(MelodyJingleBells[0]),           [0, 0}, MelodyJingleBells}, /* BUZ_TUNE_JINGLE_BELLS */
#endif
};

#if !BUZZER_USE_FREERTOS_TIMER
static void BUZ_Toggle(void *dataPtr) {
  BUZ_TrgInfo *trgInfo = (BUZ_TrgInfo *)dataPtr;

  if (trgInfo->buzIterationCntr==0) {
    McuGPIO_SetLow(BUZ_Pin); /* turn buzzer off */
  } else {
    trgInfo->buzIterationCntr--;
    McuGPIO_Toggle(BUZ_Pin);
    (void)TRG_SetTrigger(TRG_BUZ_BEEP, trgInfo->buzPeriodTicks, BUZ_Toggle, trgInfo);
  }
}
#endif

uint8_t BUZ_Beep(uint16_t freqHz, uint16_t durationMs) {
#if BUZZER_USE_FREERTOS_TIMER
  BaseType_t res;

  if (freqHz>1000) { /* timer frequency is max 1 kHz */
    return ERR_FAILED;
  }
  if (freqHz==0) { /* pause */
    res = xTimerChangePeriod(timerToggle, pdMS_TO_TICKS(durationMs), pdMS_TO_TICKS(100));
  } else {
    res = xTimerChangePeriod(timerToggle, pdMS_TO_TICKS(1000/freqHz), pdMS_TO_TICKS(100));
  }
  if (res!=pdPASS) {
    return ERR_FAILED;
  }
  res = xTimerChangePeriod(timerDuration, pdMS_TO_TICKS(durationMs), pdMS_TO_TICKS(100));
  if (res!=pdPASS) {
    return ERR_FAILED;
  }
  res = xTimerStart(timerToggle, pdMS_TO_TICKS(100));
  if (res!=pdPASS) {
    return ERR_FAILED;
  }
  res = xTimerStart(timerDuration, pdMS_TO_TICKS(100));
  if (res!=pdPASS) {
    return ERR_FAILED;
  }
  return ERR_OK;
#else
  if (trgInfo.buzIterationCntr==0) { /* only if buzzer is not running right now */
    McuGPIO_SetHigh(BUZ_Pin); /* turn buzzer on */
    trgInfo.buzPeriodTicks = (1000*TRG_TICKS_MS)/freq;
    trgInfo.buzIterationCntr = durationMs/TRG_TICKS_MS/trgInfo.buzPeriodTicks;
    return TRG_SetTrigger(TRG_BUZ_BEEP, trgInfo.buzPeriodTicks, BUZ_Toggle, (void*)&trgInfo);
  } else {
    return ERR_BUSY;
  }
#endif
}

static void BUZ_Play(void *dataPtr) {
  MelodyDesc *melody = (MelodyDesc*)dataPtr;

  /* play tune */
  (void)BUZ_Beep(melody->melody[melody->idx].freq, melody->melody[melody->idx].ms);
  melody->idx++;
  if (melody->idx<melody->maxIdx) { /* not reached end of tune? */
#if BUZZER_USE_FREERTOS_TIMER
    /* nothing needed */
#else
    TRG_SetTrigger(TRG_BUZ_TUNE, melody->melody[melody->idx-1].ms/TRG_TICKS_MS, BUZ_Play, (void*)melody);
#endif
  } else { /* end of tune */
#if BUZZER_USE_FREERTOS_TIMER
    playingMelody = NULL;
#endif
  }
}

void BUZ_StopTune(void) {
#if BUZZER_USE_FREERTOS_TIMER
  (void)xTimerStop(timerToggle, pdMS_TO_TICKS(100));
  (void)xTimerStop(timerDuration, pdMS_TO_TICKS(100));
#else
  TRG_StopTrigger(TRG_BUZ_TUNE);
#endif
}

void BUZ_StopBeep(void) {
#if BUZZER_USE_FREERTOS_TIMER
  (void)xTimerStop(timerToggle, pdMS_TO_TICKS(100));
  (void)xTimerStop(timerDuration, pdMS_TO_TICKS(100));
#else
  trgInfo.buzIterationCntr = 0;
  TRG_StopTrigger(TRG_BUZ_BEEP);
#endif
}

uint8_t BUZ_PlayTune(BUZ_Tunes tune) {
  if (tune>=BUZ_TUNE_NOF_TUNES) {
    return ERR_OVERFLOW;
  }
  BUZ_Melodies[tune].idx = 0; /* reset index */
#if BUZZER_USE_FREERTOS_TIMER
  playingMelody = &BUZ_Melodies[tune];
  BUZ_Play((void*)playingMelody);
  return ERR_OK;
#else
  return TRG_SetTrigger(TRG_BUZ_TUNE, 0, BUZ_Play, (void*)&BUZ_Melodies[tune]);
#endif
}

#if BUZZER_USE_FREERTOS_TIMER
static void vTimerCallbackToggle(TimerHandle_t pxTimer) {
  (void)pxTimer; /* not used */
  /* called with TIMER_PERIOD_MS while playing */
  McuGPIO_Toggle(BUZ_Pin);
}

static void vTimerCallbackDuration(TimerHandle_t pxTimer) {
  /* called with TIMER_PERIOD_MS while playing */
  (void)pxTimer; /* not used */
  (void)xTimerStop(timerToggle, pdMS_TO_TICKS(100));
  if (playingMelody!=NULL) {
    BUZ_Play((void*)playingMelody); /* next tune */
  }
}
#endif

#if PL_CONFIG_USE_SHELL
static uint8_t BUZ_PrintHelp(const McuShell_StdIOType *io) {
  McuShell_SendHelpStr((unsigned char*)"buzzer", (unsigned char*)"Group of buzzer commands\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  help|status", (unsigned char*)"Shows buzzer help or status\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  buz <freq> <time>", (unsigned char*)"Beep for time (ms) and frequency (Hz)\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  play tune <tune>", (unsigned char*)"Play tune, default tune 0\r\n", io->stdOut);
  return ERR_OK;
}

static uint8_t BUZ_PrintStatus(const McuShell_StdIOType *io) {
  unsigned char buf[16];
  McuShell_SendStatusStr((unsigned char*)"buzzer", (unsigned char*)"Buzzer status\r\n", io->stdOut);
  McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"0 - ");
  McuUtility_strcatNum32u(buf, sizeof(buf), BUZ_TUNE_NOF_TUNES-1);
  McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n");
  McuShell_SendStatusStr((unsigned char*)"  tunes", buf, io->stdOut);
  return ERR_OK;
}

uint8_t BUZ_ParseCommand(const unsigned char *cmd, bool *handled, const McuShell_StdIOType *io) {
  const unsigned char *p;
  uint16_t freq, duration;

  if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_HELP)==0 || McuUtility_strcmp((char*)cmd, (char*)"buzzer help")==0) {
    *handled = TRUE;
    return BUZ_PrintHelp(io);
  } else if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_STATUS)==0 || McuUtility_strcmp((char*)cmd, (char*)"buzzer status")==0) {
    *handled = TRUE;
    return BUZ_PrintStatus(io);
  } else if (McuUtility_strncmp((char*)cmd, (char*)"buzzer buz ", sizeof("buzzer buz ")-1)==0) {
    *handled = TRUE;
    p = cmd+sizeof("buzzer buz ")-1;
    if (McuUtility_ScanDecimal16uNumber(&p, &freq)==ERR_OK && McuUtility_ScanDecimal16uNumber(&p, &duration)==ERR_OK) {
      if (BUZ_Beep(freq, duration)!=ERR_OK) {
        McuShell_SendStr((unsigned char*)"Starting buzzer failed\r\n", io->stdErr);
        return ERR_FAILED;
      }
      return ERR_OK;
    }
  } else if (McuUtility_strncmp((char*)cmd, (char*)"buzzer play tune ", sizeof("buzzer play tune ")-1)==0) {
    uint8_t tune;

    *handled = TRUE;
    p = cmd+sizeof("buzzer play tune ")-1;
    McuUtility_ScanDecimal8uNumber(&p, &tune);
    if (tune<BUZ_TUNE_NOF_TUNES) {
      return BUZ_PlayTune(tune);
    } else {
      return ERR_FAILED;
    }
  } else if (McuUtility_strcmp((char*)cmd, (char*)"buzzer play tune")==0) {
    *handled = TRUE;
    return BUZ_PlayTune(BUZ_TUNE_WELCOME);
  }
  return ERR_OK;
}
#endif /* PL_CONFIG_USE_SHELL */

void BUZ_Deinit(void) {
#if BUZZER_USE_FREERTOS_TIMER
  (void)xTimerDelete(timerToggle, pdMS_TO_TICKS(100));
  timerToggle = NULL;
  (void)xTimerDelete(timerDuration, pdMS_TO_TICKS(100));
  timerDuration = NULL;
#else
  /* nothing special to do */
#endif
  BUZ_Pin = McuGPIO_DeinitGPIO(BUZ_Pin);
}

void BUZ_Init(void) {
  McuGPIO_Config_t gpioConfig;

  McuGPIO_GetDefaultConfig(&gpioConfig);
  /* IR on/off: PTC3, Low active */
  gpioConfig.hw.gpio = GPIOC;
  gpioConfig.hw.port = PORTC;
  gpioConfig.hw.pin = 3;
  gpioConfig.isInput = false;
  gpioConfig.isHighOnInit = true;
  BUZ_Pin = McuGPIO_InitGPIO(&gpioConfig);
#if BUZZER_USE_FREERTOS_TIMER
  timerToggle = xTimerCreate(
          "buzToggle", /* name */
          1, /* period/time */
          pdTRUE, /* auto reload */
          (void*)0, /* timer ID */
          vTimerCallbackToggle); /* callback */
  if (timerToggle==NULL) {
    for(;;); /* failure! */
  }
  timerDuration = xTimerCreate(
          "buzDuration", /* name */
          1, /* period/time */
          pdFALSE, /* auto reload */
          (void*)0, /* timer ID */
          vTimerCallbackDuration); /* callback */
  if (timerDuration==NULL) {
    for(;;); /* failure! */
  }
#else
  trgInfo.buzPeriodTicks = 0;
  trgInfo.buzIterationCntr = 0;
#endif
}
#endif /* PL_CONFIG_HAS_BUZZER */
