// SPDX-FileCopyrightText: 2025  Amal Mazrah <mazrah@ruc.dk>
// SPDX-FileCopyrightText: 2025  Jonas Smedegaard <dr@jones.dk>
// SPDX-FileCopyrightText: 2025  Mennatullah Hatim Kassim <stud-mennatulla@ruc.dk>
// SPDX-FileCopyrightText: 2025  Noor Ahmad <noora@ruc.dk>
// SPDX-FileCopyrightText: 2025  Tanishka Suwalka <tanishkas@ruc.dk>
// SPDX-License-Identifier: GPL-3.0-or-later

/// Mussel - a small library for Arduino to emulate a mussel biosensor
///
/// * v0.0.2
///   * rewrite attitude #2 to also handle button press
///   * add voting functions and example
///
/// * v0.0.1
///   * initial release to radicle
///
/// @version 0.0.2
/// @see <https://app.radicle.xyz/nodes/seed.radicle.garden/rad:z2tFBF4gN7ziG9oXtUytVQNYe3VhQ/tree/Mussel/README.md>
/// @see <https://moodle.ruc.dk/course/view.php?id=23504>

#include "Mussel.h"
#include "Arduino.h"

/// Main constructor
///
/// @param attitude  behavioral profile as integer
Mussel::Mussel(int attitude) {
  _attitude = attitude;
}

/// Constructor for attitudes using an input pin
///
/// @param attitude  behavioral profile as integer
/// @param pin       Used pin as uint8_t
Mussel::Mussel(int attitude, uint8_t pin) {
  _attitude = attitude;
  _pin = pin;
}

/// Constructor for attitudes using an input pin and a sensor type
///
/// @param attitude  behavioral profile as integer
/// @param pin       Used pin as uint8_t
/// @param type      type of sensor as uint8_t
Mussel::Mussel(int attitude, uint8_t pin, uint8_t type)
#ifdef DHT_H
 : mussel_dht(pin, type)
#endif
{
  _attitude = attitude;
  _pin = pin;
}

/// Setup function
void Mussel::begin() {
  switch(_attitude) {
    case 1:
      _boolState = HIGH;

      // reset timer
      _time = millis();

      // use INPUT_PULLDOWN to signal when the button is held down
      // (not PULLUP which signals when it is released)
      pinMode(_pin, INPUT_PULLDOWN);
      break;
#ifdef DHT_H
    case 3:
      mussel_dht.begin();
      break;
#endif
    case 4:
      // use INPUT_PULLDOWN to signal when the button is held down
      // (not PULLUP which signals when it is released)
      pinMode(_pin, INPUT_PULLDOWN);
      break;
    case 5:
      _boolState = HIGH;
      _count = 0;
      _time = 0;

      // Enable internal pull-up resistor for button
      pinMode(_pin, INPUT_PULLUP);
      break;
    case 6:
      _boolState = HIGH;
      break;
  }
}

/// Description of mussel
///
/// @return  name and attitude of mussel as String
String Mussel::desc() {
  String _str;

  switch(_attitude) {
    case 1:
      _str = "closed 10 seconds every 50 seconds and on button push";
      break;
    case 2:
      _str = "closed 4 seconds every 8 seconds";
      break;
    case 3:
      _str = "closed when cold";
      break;
    case 4:
      _str = "closed when button is pushed";
      break;
    case 5:
      _str = "changes state when button is pushed";
      break;
    case 6:
      _str = "changes state on ON/OFF command";
      break;
    case 10:
      _str = "handles voting";
      break;
    default:
      _str = "undefined [" + static_cast<String>(_attitude) + "]";
      break;
  }

  return _str;
}

/// Sensor reading
///
/// * Values 0-99 is a measured relative mussel gape size
/// * Values 100-254 are reserved for future use
/// * Value 255 is an internal error
///
/// @return  relative gape size as value 0-255 encoded as int
int Mussel::read() {
  int _read;

  switch(_attitude) {
    case 1:
      if (digitalRead(_pin)
        // TODO: account for rollover
        or (millis() - _time) > MUSSEL_NORMAL_PACE * 1000
      ) {
        _read = 2;
        _boolState = HIGH;

        // reset timer
        _time = millis();
      } else if (_boolState == HIGH
        and (millis() - _time) > MUSSEL_STRESS_PACE * 1000
      ) {
        _read = 42;
        _boolState = LOW;

        // reset timer
        _time = millis();
      }
      break;
    case 2:
      // 42 if current second modulo 12 is below 9, else 2
      _read = (static_cast<unsigned long>(millis() / 1000) % 12) < 9
          ? 42
          : 2;
      break;
    case 3:
#ifdef DHT_H
      // temperature in Celsius
      _read = mussel_dht.readTemperature();
#else
      _read = 255;
#endif
      break;
    case 4:
      // 2 if button is pressed, else 42
      _read = digitalRead(_pin) == HIGH
          ? 2
          : 42;
      break;
    case 5: {
      bool _reading = digitalRead(_pin); // Read button state

      // Debounce logic:
      // Ensures a single press isn't detected multiple times
      if (_reading != _boolState) {
        _time = millis(); // Reset debounce timer
      }

      if ((millis() - _time) > MUSSEL_DEBOUNCE_DELAY) {
        // Check for button press (transition from HIGH to LOW)
        if (_reading == LOW && _boolState == HIGH) {
          _count++; // Increment click count
          if (_count > 3) {
            _count = 0; // Reset cycle after 3 clicks
          }
            switch (_count) {
              case 1: _read = 2; break;  // State: Angry
              case 2: _read = 42; break;  // State: Happy
              case 3: _read = 15; break;  // State: Unsure
              case 0: _read = 99; break;  // State: Off
          }
        }
      }

      _boolState = _reading; // Update button state
      break;
    }
    case 6:
      if (Serial.available() > 0) {
        String command = Serial.readStringUntil('\n');
        command.trim();

        if (command.equalsIgnoreCase("ON")) {
          _boolState = HIGH;
        }
        else if (command.equalsIgnoreCase("OFF")) {
          _boolState = LOW;
        }
      }
      _read = _boolState == HIGH
        ? 42
        : 2;
      break;
    default:
      _read = 255;
      break;
  }

  return _read;
}

/// Function to push data onto the stack
bool Mussel::push(String id, unsigned long timestamp, int measure) {

  // Check if stack is full
  if (top >= STACK_SIZE - 1) {
    Serial.println("Stack Full");

    // Return false if stack is full
    return false;
  }
  top++;
  idStack[top] = id;
  timeStack[top] = timestamp;
  measureStack[top] = measure;

  // Return true on successful push
  return true;
}

/// Function to print stack contents
void Mussel::printStack() {
  for (int i = top; i >= 0; i--) {
    Serial.print("ID: "); Serial.print(idStack[i]);
    Serial.print(", Time: "); Serial.print(timeStack[i]);
    Serial.print(", measure: "); Serial.println(measureStack[i]);
  }
}

bool qualifyVote(Vote vote, unsigned long currentTime) {

  // If the measure is 42 (YES), check timestamp validity
  if (vote.measure == 42) {
    // If the vote's timestamp is within 1 minute, count it as YES
    if (currentTime - vote.timestamp <= MUSSEL_VOTE_TIME_AHEAD) {
      return true;
    }
    // If the vote's timestamp is older than 2 minutes, count it as NO
    else if (currentTime - vote.timestamp > MUSSEL_VOTE_TIME_BEHIND) {
      return false;
    }
  }

  // If the measure is 2, always count the vote as NO
  if (vote.measure == 2) {
    return false;
  }

  // Default case: vote is invalid if no conditions are met
  return false;
}

/// Dump internal variables, formatted for use with Serial Plotter
///
/// @return  internal variables as String
String Mussel::debug() {
  return static_cast<String>(
    "pin:") + _pin
    + "\tboolState:" + _boolState
    + "\ttime:" + _time
    + "\tcount:" + _count;
}