#include "CycloneService.h"
#include <util/atomic.h>

CycloneService::CycloneService(const int serviceBrightnessPin, const int displayPinRs, const int displayPinEn, const int displayPinD4,
                               const int displayPinD5, const int displayPinD6, const int displayPinD7, const int displayRows, const int displayCols,
                               const int ledRingCount, const int ledRingPin, const int ledRingMode, const int ledRingRefreshRate, const int buzzerPin)
  : m_buttonFlag(false), m_lastButtonPressDuration(0), m_BrightnessPin(serviceBrightnessPin), m_lastBrightnessUpdate(0),
  m_gameDisplay(displayPinRs, displayPinEn, displayPinD4, displayPinD5, displayPinD6, displayPinD7, displayRows, displayCols),
  m_ledRing(ledRingCount, ledRingPin, ledRingMode, ledRingRefreshRate), m_gameBuzzer(buzzerPin), m_player() { }


void CycloneService::animationCallback(ERingAnimation animation, void* param) {
  CycloneService *pService = ((CycloneService*) param);
  pService->m_gameDisplay.showScoreScreen(pService->m_player.getScore(), pService->m_player.getHighScore());
}

void CycloneService::hitRoutine(bool full) {
  m_player.addScore(full ? SERVICE_HITPOINTS_FULL : SERVICE_HITPOINTS_HALF);
  m_gameDisplay.showHitScreen(m_player.getScore(), full);
  m_ledRing.playAnimation(full ? ERingAnimation::HIT_FULL : ERingAnimation::HIT_HALF);
  m_gameBuzzer.playSound(EGameSound::HIT);
}

void CycloneService::brightnessUpdate(void) {
  int newBrightness = map(analogRead(m_BrightnessPin), 0, 1023, 1, 255);
  m_ledRing.setBrightness(newBrightness);
}

void CycloneService::missRoutine(void) {
  m_player.takeDamage();
  int health = m_player.getHealth();

  if (health > 0) {
    m_gameDisplay.showHealthScreen(health);
    m_ledRing.playAnimation(ERingAnimation::MISS);
    m_gameBuzzer.playSound(EGameSound::MISS);
  }
  else {
    m_gameDisplay.showGameOverScreen(m_player.getScore());
    m_ledRing.playAnimation(ERingAnimation::GAME_OVER);
    m_gameBuzzer.playSound(EGameSound::GAME_OVER);
    m_player.reset();
  }
}

void CycloneService::init(void) {
  m_gameDisplay.init();
  m_ledRing.init();
  m_gameBuzzer.init();

  m_gameDisplay.showScoreScreen(m_player.getScore(), m_player.getHighScore());
  m_ledRing.setAnimationCallback(&CycloneService::animationCallback, this);
  brightnessUpdate();
}

void CycloneService::update(void) {
#if (SERVICE_BRIGHTNESS_UPDATE_ENABLED == TRUE)
  // Time for brightness update?
  if ((millis() - m_lastBrightnessUpdate) >= SERVICE_BRIGHTNESS_UPDATE_DELAY_MS) {
    brightnessUpdate();
    m_lastBrightnessUpdate = millis();
  }
#endif

  // long is bigger than 1 Byte => Use atomic block to avoid concurrency problems
  volatile unsigned long lastButtonPressDuration;
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { lastButtonPressDuration = m_lastButtonPressDuration; }

  // Button hold for long enough to trigger reset?
  if ((lastButtonPressDuration != 0) && ((millis() - lastButtonPressDuration) > SERVICE_HARDRESET_DURATION_MS)) {
    m_gameDisplay.showResetScreen();
    m_ledRing.playAnimation(ERingAnimation::RESET);
    m_gameBuzzer.playSound(EGameSound::RESET);
    m_player.reset(true);

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { m_lastButtonPressDuration = 0; }
     m_lastButtonPressDuration = 0;
  }

  // Button pressed?
  if (m_buttonFlag) {
    delay(200);
    if (m_ledRing.isFullHit()) {
      hitRoutine(true);
    } else if (m_ledRing.isHalfHit()) {
      hitRoutine(false);
    } else {
      missRoutine();
    }

    m_buttonFlag = false;
  }

  m_ledRing.update();
  m_gameBuzzer.update();
}

void CycloneService::onActionButton(bool pressed) {
  if (pressed) {
    if (!m_ledRing.isBusy()) {
      m_buttonFlag = true;
    }

    unsigned long currentTime = millis();
    m_lastButtonPressDuration = ((currentTime != 0) ? currentTime : 1);
  }
  else {
    m_lastButtonPressDuration = 0;
  }
}