#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Arduino.h>
#include <EEPROM.h>
#include <AsyncTimer.h>
#include <inttypes.h>
//#include <SmartButton.h>

#define EBUTTON_SUPPORT_TRANSITION_DISABLED
#define EBUTTON_SUPPORT_EACH_CLICK_DISABLED
#define EBUTTON_SUPPORT_DONE_CLICKING_DISABLED
//#define EBUTTON_SUPPORT_SINGLE_AND_DOUBLE_CLICKS_DISABLED
#define EBUTTON_SUPPORT_LONG_PRESS_START_DISABLED
#define EBUTTON_SUPPORT_LONG_PRESS_DURING_DISABLED
//#define EBUTTON_SUPPORT_LONG_PRESS_END_DISABLED
#include <EButton.h>


#define DEFAULT_PUMP_ON_DURATION    5000
#define DEFAULT_BUZZER_DELAY        4000
#define DEFUALT_PUMP_OFF_DURATION   10000
#define MIN_DURATION                500
#define MAX_DURATION                300000
#define PARAMETER_INCREMENT         500
#define DEFAULT_REPETITIONS_NUMBER  10
#define MIN_REPETITIONS_NUMBER      1
#define MAX_REPETITIONS_NUMBER      100
#define STARTUP_BUZZER_FREQUENCY    2000
#define BUZZER_FREQUENCY            4000
#define EEPROM_INITIAL_ADDRESS      0
#define VALIDITY_CHECK_VALUE        0xABCD

#define PARAMETERS_NUMBER            4

#define PUMP_ON_DURATION_INDEX      0
#define BUZZER_DELAY_INDEX          1
#define PUMP_OFF_DURATION_INDEX     2
#define REPETITIONS_NUMBER_INDEX    3

const char STATIC_STRING_0[] PROGMEM = "Pump On Duration";
const char STATIC_STRING_1[] PROGMEM = "Buzzer Delay";
const char STATIC_STRING_2[] PROGMEM = "Pump Off Duration";
const char STATIC_STRING_3[] PROGMEM = "Repetitions Number";

PGM_P const STATIC_STRINGS[] PROGMEM =
{
    STATIC_STRING_0,
    STATIC_STRING_1,
    STATIC_STRING_2,
    STATIC_STRING_3
};

#define FIELD_WIDTH 5

//typedef String (*ParameterConverter)(uint32_t);
typedef const char* (*ParameterConverter)(uint32_t);

class Configuration;
template<class T>
class Parameter {
  friend class Configuration;

  uint8_t id_;
  T value_;
  T minValue_;
  T maxValue_;
  T increment_;
  bool printAsFloat_;

  bool initialized_;
public:
  Parameter() : id_(0xFF), value_(0), minValue_(0), maxValue_(0), increment_(0), initialized_(false), printAsFloat_(false) {}

  Parameter(uint8_t id, const T& value, const T& minValue, const T& maxValue, const T& increment, bool printAsFloat) : 
    id_(id),
    value_(value), 
    minValue_(minValue), 
    maxValue_(maxValue), 
    increment_(increment),
    initialized_(true),
    printAsFloat_(printAsFloat) {}

  Parameter(uint8_t id, const T& value, bool printAsFloat) : 
    id_(id),
    value_(value), 
    minValue_(MIN_DURATION), 
    maxValue_(MAX_DURATION), 
    increment_(PARAMETER_INCREMENT),
    initialized_(true),
    printAsFloat_(printAsFloat) {}
    
  Parameter(const Parameter&) = default;
  Parameter& operator=(const Parameter&) = default;
  virtual ~Parameter() = default;

  bool operator==(const Parameter& right) const {
    return id_ == right.id_ &&
      value_ == right.value_ &&
      minValue_ == right.minValue_ &&
      maxValue_ == right.maxValue_ &&
      increment_ == right.increment_ &&
      printAsFloat_ == right.printAsFloat_;
  }

  bool operator!=(const Parameter& right) const {
    return !operator==(right);
  }

  uint8_t getId() const {
    return id_;
  }

  void setId(uint8_t id) {
    id_ = id;
  }

  // String getName() const {
  //   // Get first string in the instructions array
  //   char buffer[32];
  //   strcpy_P(buffer, (PGM_P)pgm_read_word(&STATIC_STRINGS[id_]));
  //   return String(buffer);
  // }

  const char* getName() const {
    // Get first string in the instructions array
    static char buffer[20];
    strcpy_P(buffer, (PGM_P)pgm_read_word(&STATIC_STRINGS[id_]));
    return buffer;
  }

  T getValue() const {
    return value_;
  }

  void setValue(const T& value) {
    value_ = value;
  }

  T& operator++() {
    if (value_ + increment_ <= maxValue_) {
      value_+=increment_;
    }

    return value_;
  }

  // T operator++(int) {
  //   if (value_ + increment_ <= maxValue_) {
  //     value_+=increment_;
  //   }

  //   return value_;
  // }

  T& operator--() {
    if (value_ - increment_ >= minValue_) {
      value_-=increment_;
    }
    
    return value_;
  }

  // T operator--(int) {
  //   if (value_ - increment_ >= minValue_) {
  //     value_-=increment_;
  //   }

  //   return value_;
  // }

  bool isValid() const {
    return initialized_ && value_ >= minValue_ && value_ <= maxValue_;
  }

  static double convertToDouble(uint32_t value) {
    double tmp = value;
    tmp /= 1000;
    return tmp;
  }

  //static String convert(uint32_t x) {
  static const char* convert(uint32_t x) {
    static char paramBuffer[16];
    memset(paramBuffer, 0, sizeof(paramBuffer));
    dtostrf(convertToDouble(x), FIELD_WIDTH, 1, paramBuffer);
    // Serial.print(F("paramBuffer: "));
    // Serial.println(paramBuffer);
    // Serial.println(String(paramBuffer).c_str());
    //return String(paramBuffer);
    return paramBuffer;
  };

  //static String convertRaw(uint32_t x) {
  static const char* convertRaw(uint32_t x) {
    static char paramBuffer[16];
    char format[16];
    sprintf(format, "%c%d%c", '%', FIELD_WIDTH, 'd');
    sprintf(paramBuffer, format, x);

    //return String(paramBuffer);
    return paramBuffer;
  };

  //template<typename Functor>
  //String toString(ParameterConverter converter, bool printName = false) const {
  const char* toString(ParameterConverter converter, bool printName = false) const {
    if (printName) {
      char buffer[64];
      //sprintf(buffer, "%s: %s", getName().c_str(), converter(value_).c_str());
      //sprintf(buffer, "%s: %s", getName(), converter(value_).c_str());
      sprintf(buffer, "%s: %s", getName(), converter(value_));
      //Serial.println(buffer);
      //return String(buffer);
      return buffer;
    } else {
      //Serial.println(converter(value_).c_str());
      return converter(value_);
    }    
  }

  bool isInitialized() const {
    return initialized_;
  }

  void reset() {
    initialized_ = false;
  }

  void setMinValue(T minValue) {
    minValue_ = minValue;
  }

  void setMaxValue(T maxValue) {
    maxValue_ = maxValue;
  }

  void setIncrement(T increment) {
    increment_ = increment;
  }

  void setInitialized(bool init) {
    initialized_ = init;
  }

  bool isPrintAsFloat() const {
    return printAsFloat_;
  }

  void setPrintAsFloat(bool printAsFloat) {
    printAsFloat_ = printAsFloat;
  }
};

struct ConfigurationData_t {
  uint16_t validityCheck_;
  uint32_t pumpOnDuration_;
  uint32_t buzzerDelay_;
  uint32_t pumpOffDuration_;
  uint16_t repetitionsNumber_;

  ConfigurationData_t() : 
    validityCheck_(0xABCD), 
    pumpOnDuration_(DEFAULT_PUMP_ON_DURATION), 
    buzzerDelay_(DEFAULT_BUZZER_DELAY), 
    pumpOffDuration_(DEFUALT_PUMP_OFF_DURATION),
    repetitionsNumber_(DEFAULT_REPETITIONS_NUMBER) {}
};

class ConfigurationManager;
class Configuration {
  friend class ConfigurationManager;
  uint16_t validityCheck_;
  Parameter<uint32_t> parameters_[PARAMETERS_NUMBER];
public:
  Configuration();
  Configuration(uint16_t validityCheck, const Parameter<uint32_t>* parameters);
  Configuration(const ConfigurationData_t& confData);
  Configuration(const Configuration& right);
  Configuration& operator=(const Configuration& right);
  virtual ~Configuration() = default;

  bool operator==(const Configuration& right) const;
  bool operator!=(const Configuration& right) const;

  uint16_t getValidityCheck() const;
  void setValidityCheck(uint16_t validityCheck);  
  Parameter<uint32_t> getParameter(uint8_t index) const;
  void setParameter(uint8_t index, const Parameter<uint32_t>& param);
  void incrementParameter(uint8_t index);
  void decrementParameter(uint8_t index);

  bool isValid() const;

  ConfigurationData_t getConfigurationData() const;
};

class ConfigurationManager {
  static ConfigurationManager* instance_;
  ConfigurationManager() = default;
  
  Configuration configuration_;

public:
  static ConfigurationManager* getInstance();
  bool loadConfig();

  ConfigurationManager(const ConfigurationManager&) = delete;
  ConfigurationManager& operator=(const ConfigurationManager&) = delete;
  virtual ~ConfigurationManager() = default;

  bool isValid() const;

  static double convert(uint32_t value);
  // static String toString(const Parameter<uint32_t>& parameter, bool printName = false); 
  // String getParameterAsString(uint8_t paramIndex, bool printName = false);
  // String getRawParameterAsString(uint8_t paramIndex, bool printName = false);

  // String formatParameter(uint8_t paramIndex, bool printName = false);
  static const char* toString(const Parameter<uint32_t>& parameter, bool printName = false); 
  const char* getParameterAsString(uint8_t paramIndex, bool printName = false);
  const char* getRawParameterAsString(uint8_t paramIndex, bool printName = false);

  const char* formatParameter(uint8_t paramIndex, bool printName = false);

  Configuration getConfiguration() const;

  //String getParameterName(uint8_t paramIndex) const;
  const char* getParameterName(uint8_t paramIndex) const;

  void printConfigurationData();

};

static const uint32_t PARAMETERS_DEFAULT_VALUES[] = {DEFAULT_PUMP_ON_DURATION, DEFAULT_BUZZER_DELAY, DEFUALT_PUMP_OFF_DURATION, DEFAULT_REPETITIONS_NUMBER};

Configuration::Configuration() : 
  validityCheck_(VALIDITY_CHECK_VALUE),
  parameters_({
    Parameter<uint32_t>(0, PARAMETERS_DEFAULT_VALUES[0], true),
    Parameter<uint32_t>(1, PARAMETERS_DEFAULT_VALUES[1], true),
    Parameter<uint32_t>(2, PARAMETERS_DEFAULT_VALUES[2], true),
    Parameter<uint32_t>(3, PARAMETERS_DEFAULT_VALUES[3], MIN_REPETITIONS_NUMBER, MAX_REPETITIONS_NUMBER, 1, false)
  }
  ) {
  }

Configuration::Configuration(uint16_t validityCheck, const Parameter<uint32_t>* parameters) {
  validityCheck_ = validityCheck;

  for (int i=0; i<PARAMETERS_NUMBER; i++) {
    parameters_[i] = parameters[i];
  }
}

Configuration::Configuration(const ConfigurationData_t& confData) {
  Parameter<uint32_t> tmpParameters[PARAMETERS_NUMBER] = {
    Parameter<uint32_t>(0, confData.pumpOnDuration_, true),
    Parameter<uint32_t>(1, confData.buzzerDelay_, true),
    Parameter<uint32_t>(2, confData.pumpOffDuration_, true),
    Parameter<uint32_t>(3, confData.repetitionsNumber_, MIN_REPETITIONS_NUMBER, MAX_REPETITIONS_NUMBER, 1, false)
  };

  validityCheck_ = confData.validityCheck_;
  for (int i=0; i<PARAMETERS_NUMBER; i++) {
    parameters_[i] = tmpParameters[i];
  }
}

Configuration::Configuration(const Configuration& right) {
  validityCheck_ = right.validityCheck_;
  for (int i=0; i<PARAMETERS_NUMBER; i++) {
    parameters_[i] = right.parameters_[i];
  }
}

Configuration& Configuration::operator=(const Configuration& right) {
  if (this != &right) {
    validityCheck_ = right.validityCheck_;
    for (int i=0; i<PARAMETERS_NUMBER; i++) {
      parameters_[i] = right.parameters_[i];
    }
  }

  return *this;
}

bool Configuration::operator==(const Configuration& right) const {
  bool result = validityCheck_ == right.validityCheck_;

  if (result) {
    for (int i=0; i<PARAMETERS_NUMBER; i++) {
      result = parameters_[i] == right.parameters_[i];
      if (!result) {
        break;
      }
    }
  }

  return result;
}

bool Configuration::operator!=(const Configuration& right) const {
  return !operator==(right);
}

Parameter<uint32_t> Configuration::getParameter(uint8_t index) const {
  if (index < PARAMETERS_NUMBER) {
    return parameters_[index];
  }

  return Parameter<uint32_t>();
}

uint16_t Configuration::getValidityCheck() const {
  return validityCheck_;
}

void Configuration::setValidityCheck(uint16_t validityCheck) {
  validityCheck_ = validityCheck;
}

void Configuration::setParameter(uint8_t index, const Parameter<uint32_t>& param) {
  if (index < PARAMETERS_NUMBER) {
    parameters_[index] = param;
  }
}

bool Configuration::isValid() const {
  bool result = validityCheck_ == VALIDITY_CHECK_VALUE;
  //Serial.print(F("validityCheck: "));
  //Serial.println(validityCheck_);

  if (result) {
    for (int i=0; i<PARAMETERS_NUMBER; i++) {
      result = parameters_[i].isValid();

      if (!result) {
        break;
      }
    }
  }

  return result;
}

void Configuration::incrementParameter(uint8_t index) {
  if (index < PARAMETERS_NUMBER) {
    ++parameters_[index];
  }
}

void Configuration::decrementParameter(uint8_t index) {
  if (index < PARAMETERS_NUMBER) {
    --parameters_[index];
  }
}

ConfigurationData_t Configuration::getConfigurationData() const {
  ConfigurationData_t result;

  result.validityCheck_ = validityCheck_;
  result.pumpOnDuration_ = parameters_[PUMP_ON_DURATION_INDEX].value_;
  result.buzzerDelay_ = parameters_[BUZZER_DELAY_INDEX].value_;
  result.pumpOffDuration_ = parameters_[PUMP_OFF_DURATION_INDEX].value_;
  result.repetitionsNumber_ = parameters_[REPETITIONS_NUMBER_INDEX].value_;

  return result;
}

ConfigurationManager* ConfigurationManager::instance_ = nullptr;

ConfigurationManager* ConfigurationManager::getInstance() {
  if (instance_ == nullptr) {
    instance_ = new ConfigurationManager();
  } 

  return instance_;
}

bool ConfigurationManager::loadConfig() {
  bool result = false;

  ConfigurationData_t configurationData;
  //Variable to store custom object read from EEPROM.
  EEPROM.get(EEPROM_INITIAL_ADDRESS, configurationData);
  Configuration tmpConfiguration(configurationData);

  if (tmpConfiguration.isValid()) {
    Serial.println(F("Configuration validity check result: ok"));
  } else {
    Serial.println(F("Configuration validity check result: not ok"));
    Serial.println(F("Writing default values..."));
    ConfigurationData_t defaultConfig;
    Serial.println(F("Default values correctly written in EEPROM"));

    configuration_ = Configuration(defaultConfig);
  }
  
  return result;
}

bool ConfigurationManager::isValid() const {
  return configuration_.isValid();
}

// double ConfigurationManager::convert(uint32_t value) {
//   double tmp = value;
//   tmp /= 1000;
//   return tmp;
// }

//String ConfigurationManager::toString(const Parameter<uint32_t>& parameter, bool printName) {
const char* ConfigurationManager::toString(const Parameter<uint32_t>& parameter, bool printName) {
  return parameter.toString(&Parameter<uint32_t>::convert, printName);
}

//String ConfigurationManager::getParameterAsString(uint8_t paramIndex, bool printName) {
const char* ConfigurationManager::getParameterAsString(uint8_t paramIndex, bool printName) {
  static const char* result = "";
  
  if (paramIndex < PARAMETERS_NUMBER) {
    result = toString(configuration_.parameters_[paramIndex], printName);
    //Serial.println(result.c_str());
  }

  return result;
}

//String ConfigurationManager::getRawParameterAsString(uint8_t paramIndex, bool printName) {
const char* ConfigurationManager::getRawParameterAsString(uint8_t paramIndex, bool printName) {
  static const char* result = "";
  
  if (paramIndex < PARAMETERS_NUMBER) {   
    // auto lambda = [] (uint32_t x) {
    //   char paramBuffer[16];
    //   char format[16];
    //   sprintf(format, "%c%d%c", '%', FIELD_WIDTH, 'd');
    //   sprintf(paramBuffer, format, x);
      
    //   return String(paramBuffer);
    // };

    //result = configuration_.parameters_[paramIndex].toString(lambda, printName);
    result = configuration_.parameters_[paramIndex].toString(&Parameter<uint32_t>::convertRaw, printName);
  }

  return result;
}

//String ConfigurationManager::formatParameter(uint8_t paramIndex, bool printName) {
const char* ConfigurationManager::formatParameter(uint8_t paramIndex, bool printName) {
  static const char* result = "";
  
  if (paramIndex < PARAMETERS_NUMBER) {   
    if (configuration_.parameters_[paramIndex].isPrintAsFloat()) {
      result = getParameterAsString(paramIndex, printName);
    } else {
      result = getRawParameterAsString(paramIndex, printName);
    }
  }

  return result;
}

void ConfigurationManager::printConfigurationData() {
  for (int i=0; i<PARAMETERS_NUMBER; i++) {
    //Serial.println(formatParameter(i, true).c_str());
    Serial.println(formatParameter(i, true));
  }
}

Configuration ConfigurationManager::getConfiguration() const {
  return configuration_;
}

//String ConfigurationManager::getParameterName(uint8_t paramIndex) const {
const char* ConfigurationManager::getParameterName(uint8_t paramIndex) const {
  //String result = "";
  static char* result = "";
  
  if (paramIndex < PARAMETERS_NUMBER) {
    result = configuration_.parameters_[paramIndex].getName();  
  }

  return result;
}

// #include <SmartButton.h>

// using namespace smartbutton;

#define CONFIG_TIMEOUT 5000

enum class ButtonObject : uint8_t {
  CONFIG,
  INCREMENT,
  DECREMENT,
  START_STOP
};

enum class ButtonEvent : uint8_t {
  BUTTON_CLICK,
  BUTTON_LONG_PRESS_END
};

class State;

class Context {
  State* currentState_ = nullptr;
  Adafruit_SSD1306* display_ = nullptr;
public:
  Context(Adafruit_SSD1306* display) : display_(display) {}
  Context(const Context&) = delete;
  Context& operator=(const Context&) = delete;
  virtual ~Context() = default;

  void setState(State* state);

  void handle();

  void refreshDisplay();

  //void handleEvent(ButtonObject button, SmartButton::Event event);
  void handleEvent(ButtonObject button, ButtonEvent event);

};

class State {
protected:
  Context* context_ = nullptr;
  static AsyncTimer stateTimer_;
  
  //static State* instance_;
  State() = default;
public:
  
  State(const State&) = delete;
  State& operator=(const State&) = delete;
  virtual ~State() = default;
  // virtual void handleButtonPressed(ButtonObject button) {};
  // virtual void handleButtonReleased(ButtonObject button) {};
  virtual void handleButtonClick(ButtonObject button) {};
  virtual void handleButtonHold(ButtonObject button) {};
  // virtual void handleButtonLongHold(ButtonObject button) {};
  // virtual void handleButtonHoldRepeat(ButtonObject button) {};
  // virtual void handleButtonLongHoldRepeat(ButtonObject button) {};

  virtual void onExit();
  virtual void onEnter();

  virtual void display(Adafruit_SSD1306* display) {};

  void setContext(Context* context);

  void handle();

  //static void drawCentreString(Adafruit_SSD1306* display, const String &buf, int x, int y);
  static void drawCentreString(Adafruit_SSD1306* display, const char* buf, int x, int y);
};
AsyncTimer State::stateTimer_ = AsyncTimer(2);
class InitState : public State {
  friend class State;

  InitState() = default;
  InitState(const InitState&) = delete;
  InitState& operator=(const InitState&) = delete;

  static InitState* instance_;
public:
  virtual ~InitState() = default;
  static InitState* getInstance();

  virtual void handleButtonClick(ButtonObject button) override;
  virtual void handleButtonHold(ButtonObject button) override;

  virtual void display(Adafruit_SSD1306* display) override;
};

class ConfigViewState : public State {
  friend class State;

  ConfigViewState() = default;
  ConfigViewState(const ConfigViewState&) = delete;
  ConfigViewState& operator=(const ConfigViewState&) = delete;

  unsigned short timerId_ = 0;
  static ConfigViewState* instance_;
public:
  virtual ~ConfigViewState() = default;
  static ConfigViewState* getInstance();

  virtual void handleButtonClick(ButtonObject button) override;
  virtual void handleButtonHold(ButtonObject button) override;

  virtual void onEnter() override;
  virtual void display(Adafruit_SSD1306* display) override;
};

class RunningState : public State {
  friend class State;

  RunningState() = default;
  RunningState(const RunningState&) = delete;
  RunningState& operator=(const RunningState&) = delete;

  unsigned short buzzerTimerId_ = 0;
  unsigned short stopTimerId_ = 0;
  static RunningState* instance_;
public:
  virtual ~RunningState() = default;
  static RunningState* getInstance();

  virtual void handleButtonClick(ButtonObject button) override;
};

class ConfigEditState : public State {
  friend class State;

  //bool holdFilter_ = true;
  uint8_t parameterIndex_ = -1;
  bool show_ = true;
  Configuration currentConfiguration_;
  
  ConfigEditState() = default;
  ConfigEditState(const ConfigEditState&) = delete;
  ConfigEditState& operator=(const ConfigEditState&) = delete;

  unsigned short timerId_ = 0;
  unsigned short refreshTimerId_ = 0;
  static ConfigEditState* instance_;
public:
  virtual ~ConfigEditState() = default;
  static ConfigEditState* getInstance();

  virtual void handleButtonClick(ButtonObject button) override;
  virtual void handleButtonHold(ButtonObject button) override;
  //virtual void handleButtonReleased(ButtonObject button) override;

  virtual void onEnter() override;
  virtual void display(Adafruit_SSD1306* display) override;
  //virtual void onExit() override;
};

#define REFRESH_DISPLAY_TIMEOUT 400

void Context::setState(State* state) {
  Serial.println(F("Context::setState"));
  if (currentState_ != nullptr) {
    currentState_->onExit();

    //delete currentState_;
  }

  state->setContext(this);
  
  currentState_ = state;

  state->onEnter();
}

void Context::handle() {
  currentState_->handle();
}

// void Context::handleEvent(ButtonObject button, SmartButton::Event event) {
//   switch(event) {
//     case SmartButton::Event::RELEASED:
//       currentState_->handleButtonReleased(button);
//       break;
//     case SmartButton::Event::PRESSED:
//       currentState_->handleButtonPressed(button);
//       break;
//     case SmartButton::Event::CLICK:
//       currentState_->handleButtonClick(button);
//       break;
//     case SmartButton::Event::HOLD:
//       currentState_->handleButtonHold(button);
//       break;
//     case SmartButton::Event::HOLD_REPEAT:
//       currentState_->handleButtonHoldRepeat(button);
//       break;
//     case SmartButton::Event::LONG_HOLD:
//       currentState_->handleButtonLongHold(button);
//       break;
//     case SmartButton::Event::LONG_HOLD_REPEAT:
//       currentState_->handleButtonLongHoldRepeat(button);
//       break;
//   }
// }

void Context::handleEvent(ButtonObject button, ButtonEvent event) {
  switch(event) {
    case ButtonEvent::BUTTON_CLICK:
      currentState_->handleButtonClick(button);
      break;
    case ButtonEvent::BUTTON_LONG_PRESS_END:
      currentState_->handleButtonHold(button);
      break;
  }
}

void Context::refreshDisplay() {
  Serial.println(F("Context::refreshDisplay"));
  if (currentState_ != nullptr) {
    currentState_->display(display_);
  }
}

void State::setContext(Context* context) {
  this->context_ = context;
}

void State::onEnter() {
  Serial.println(F("State::onEnter"));
  this->context_->refreshDisplay();
}

void State::onExit() {
  Serial.println(F("State::onExit"));
  stateTimer_.cancelAll();
}

void State::handle() {
  stateTimer_.handle();
}

//void State::drawCentreString(Adafruit_SSD1306* display, const String &buf, int x, int y)
void State::drawCentreString(Adafruit_SSD1306* display, const char* buf, int x, int y)
{
    int16_t x1, y1;
    uint16_t w, h;
    display->getTextBounds(buf, x, y, &x1, &y1, &w, &h); //calc width of new string
    display->setCursor(x - w / 2, y);
    display->print(buf);
}

InitState* InitState::instance_ = nullptr;

InitState* InitState::getInstance() {
  //return State::getInstance<InitState>();
  if (instance_ == nullptr) {
    instance_ = new InitState();
  } 

  return instance_;
}

void InitState::handleButtonClick(ButtonObject button) {
  switch(button) {
    case ButtonObject::CONFIG:
      context_->setState(ConfigViewState::getInstance());
      break;
    case ButtonObject::START_STOP:
      context_->setState(RunningState::getInstance());
      break;
    default:
      break;
  }
}

void InitState::handleButtonHold(ButtonObject button) {
  if (button == ButtonObject::CONFIG) {
    context_->setState(ConfigEditState::getInstance());
  }
}

void InitState::display(Adafruit_SSD1306* display) {
  Serial.println(F("InitState::display"));
  display->clearDisplay();

  display->setTextSize(1);             // Normal 1:1 pixel scale
  display->setTextColor(SSD1306_WHITE);        // Draw white text
  display->setCursor(0,display->height()-8);             
  display->println(F("Config          Start"));
  
  display->display();
}

ConfigViewState* ConfigViewState::instance_ = nullptr;

ConfigViewState* ConfigViewState::getInstance() {
  //return State::getInstance<ConfigViewState>();
  if (instance_ == nullptr) {
    instance_ = new ConfigViewState();
  } 

  return instance_;
}

void ConfigViewState::handleButtonClick(ButtonObject button) {
  if (button == ButtonObject::CONFIG) {
    context_->setState(InitState::getInstance());
  }
}

void ConfigViewState::handleButtonHold(ButtonObject button) {
  if (button == ButtonObject::CONFIG) {
    context_->setState(ConfigEditState::getInstance());
  }
}

void ConfigViewState::onEnter() {
  Serial.println(F("ConfigViewState::onEnter"));
  State::onEnter();
  timerId_ = stateTimer_.setTimeout([=]() { context_->setState(InitState::getInstance()); }, CONFIG_TIMEOUT);
}

void ConfigViewState::display(Adafruit_SSD1306* display) {
  Serial.println(F("ConfigViewState::display"));
  display->clearDisplay();

  display->setTextSize(1);             // Normal 1:1 pixel scale
  display->setTextColor(SSD1306_WHITE);        // Draw white text
  display->setCursor(0,0);             // Start at top-left corner
  //display.println(F("  RD   BD   SD    RN"));
  display->println(F("  ON   BD   OFF   RN"));
  //Serial.println(ConfigurationManager::getInstance()->getParameterAsString(PUMP_ON_DURATION_INDEX).c_str());
  // display->print(ConfigurationManager::getInstance()->getParameterAsString(PUMP_ON_DURATION_INDEX).c_str());
  // display->print(ConfigurationManager::getInstance()->getParameterAsString(BUZZER_DELAY_INDEX).c_str());
  // display->print(ConfigurationManager::getInstance()->getParameterAsString(PUMP_OFF_DURATION_INDEX).c_str());
  // display->print(ConfigurationManager::getInstance()->getRawParameterAsString(REPETITIONS_NUMBER_INDEX).c_str());
  display->print(ConfigurationManager::getInstance()->getParameterAsString(PUMP_ON_DURATION_INDEX));
  display->print(ConfigurationManager::getInstance()->getParameterAsString(BUZZER_DELAY_INDEX));
  display->print(ConfigurationManager::getInstance()->getParameterAsString(PUMP_OFF_DURATION_INDEX));
  display->print(ConfigurationManager::getInstance()->getRawParameterAsString(REPETITIONS_NUMBER_INDEX));

  display->display();
}

RunningState* RunningState::instance_ = nullptr;

RunningState* RunningState::getInstance() {
  //return State::getInstance<RunningState>();
  if (instance_ == nullptr) {
    instance_ = new RunningState();
  } 

  return instance_;
}

void RunningState::handleButtonClick(ButtonObject button) {
  if (button == ButtonObject::START_STOP) {
    context_->setState(InitState::getInstance());
  }
}

ConfigEditState* ConfigEditState::instance_ = nullptr;

ConfigEditState* ConfigEditState::getInstance() {
  //return State::getInstance<ConfigEditState>();
  if (instance_ == nullptr) {
    instance_ = new ConfigEditState();
  } 

  return instance_;
}

void ConfigEditState::onEnter() {
  State::onEnter();
  //this->holdFilter_ = true;
  parameterIndex_ = 0;
  currentConfiguration_ = ConfigurationManager::getInstance()->getConfiguration();
  timerId_ = stateTimer_.setTimeout([=]() { context_->setState(InitState::getInstance()); }, CONFIG_TIMEOUT);
  refreshTimerId_ = stateTimer_.setInterval([=]() { context_->refreshDisplay(); }, REFRESH_DISPLAY_TIMEOUT);
}

void ConfigEditState::display(Adafruit_SSD1306* display) {
  display->clearDisplay();

  display->setTextSize(1);             // Normal 1:1 pixel scale
  display->setTextColor(SSD1306_WHITE);        // Draw white text
  display->setCursor(0,0);             // Start at top-left corner
  drawCentreString(display, ConfigurationManager::getInstance()->getParameterName(parameterIndex_), display->width()/2, 0);
  //display->setCursor(8,8);
  
  display->setTextSize(3);
  //Serial.println(parameterIndex_);
  if (show_) {
    drawCentreString(display, ConfigurationManager::getInstance()->formatParameter(parameterIndex_), display->width()/2, 12);
    //display->println(ConfigurationManager::getInstance()->formatParameter(parameterIndex_));
  } else {
    drawCentreString(display, "     ", display->width()/2, 12);
    //display->println("     ");
  }
  
  display->display();

  show_ = !show_;
}

void ConfigEditState::handleButtonHold(ButtonObject button) {
  if (button == ButtonObject::CONFIG) {
    //if (!this->holdFilter_) {
      // save
      Serial.println(F("Checking differences..."));
      Configuration savedConfiguration = ConfigurationManager::getInstance()->getConfiguration();
      if (currentConfiguration_ != savedConfiguration) {
        Serial.println(F("Saving data to EEPROM..."));
        EEPROM.put(EEPROM_INITIAL_ADDRESS, currentConfiguration_.getConfigurationData());
        Serial.println(F("Data saved to EEPROM..."));
      } else {
        Serial.println(F("No change occurred"));
      }

      context_->setState(InitState::getInstance());
    //}
  }
}

// void ConfigEditState::handleButtonReleased(ButtonObject button) {
//   if (button == ButtonObject::CONFIG) {
//     this->holdFilter_ = false;
//     stateTimer_.reset(refreshTimerId_);
//     context_->refreshDisplay();
//   }
// }

void ConfigEditState::handleButtonClick(ButtonObject button) {
  stateTimer_.reset(timerId_);
  switch(button) {
    case ButtonObject::CONFIG:
      // next param
      parameterIndex_++;
      parameterIndex_ = parameterIndex_ % PARAMETERS_NUMBER;
      stateTimer_.reset(refreshTimerId_);
      context_->refreshDisplay();
      break;
    case ButtonObject::INCREMENT:
      // param++
      currentConfiguration_.incrementParameter(parameterIndex_);
      stateTimer_.reset(refreshTimerId_);
      context_->refreshDisplay();
      break;
    case ButtonObject::DECREMENT:
      // param--
      currentConfiguration_.decrementParameter(parameterIndex_);
      stateTimer_.reset(refreshTimerId_);
      context_->refreshDisplay();
      break;
    default:
      break;
  }
}



#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library. 
// On an arduino UNO:       A4(SDA), A5(SCL)
// On an arduino MEGA 2560: 20(SDA), 21(SCL)
// On an arduino LEONARDO:   2(SDA),  3(SCL), ...
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Context context(&display);

#define BUTTON_PIN 8
// using namespace smartbutton;
// class ButtonInterface: public SmartButtonInterface {
// public:
//     virtual void event(SmartButton *button, SmartButton::Event event, int clickCounter)
//     {
//         context.handleEvent(ButtonObject::CONFIG, event);
//     }

//     /*void toggle() {
//         digitalWrite(LED_PIN, !digitalRead(LED_PIN));
//     }*/

//     virtual bool isPressed(SmartButton *button)
//     {
//         return (digitalRead(BUTTON_PIN) == LOW) ? true : false;
//     }
// };

// ButtonInterface buttonInterface;

// SmartButton button(&buttonInterface);

EButton* button;

void singleClick(EButton &btn) {
	//Serial.println(F("We have a click!"));
  context.handleEvent(ButtonObject::CONFIG, ButtonEvent::BUTTON_CLICK);
}

// void doubleClick(EButton &btn) {
// 	Serial.println("We have a double click!");
// }

void longPressedEnd(EButton &btn) {
	//Serial.println(F("We have a long pressed end!"));
  context.handleEvent(ButtonObject::CONFIG, ButtonEvent::BUTTON_LONG_PRESS_END);
}

#ifdef __arm__
// should use uinstd.h to define sbrk but Due causes a conflict
extern "C" char* sbrk(int incr);
#else  // __ARM__
extern char *__brkval;
#endif  // __arm__

int freeMemory() {
  char top;
#ifdef __arm__
  return &top - reinterpret_cast<char*>(sbrk(0));
#elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
  return &top - __brkval;
#else  // __arm__
  return __brkval ? &top - __brkval : &top - __malloc_heap_start;
#endif  // __arm__
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(19200);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
	Serial.println(F("Serial ok!"));
	Serial.println(freeMemory());

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }
  Serial.println(freeMemory());

  Serial.println(F("Checking configuration..."));
  ConfigurationManager::getInstance()->loadConfig();
  ConfigurationManager::getInstance()->printConfigurationData();

  // Show initial display buffer contents on the screen --
  // the library initializes this with an Adafruit splash screen.
  display.display();
  delay(2000); // Pause for 2 seconds

  // Clear the buffer
  display.clearDisplay();

  //pinMode(BUTTON_PIN, INPUT_PULLUP);  // Digital input with pull-up resistors (normal high)

  //button.begin();                     // Initialize and register smart button

  button = new EButton(BUTTON_PIN);
  button->attachSingleClick(singleClick);
	//button.attachDoubleClick(doubleClick);
  button->attachLongPressEnd(longPressedEnd);

  context.setState(InitState::getInstance());

  Serial.println(freeMemory());
}

void loop() {
  // put your main code here, to run repeatedly:
  context.handle();
  //SmartButton::service();             // Asynchronous service routine, should be called periodically
	button->tick();
}
nano:12
nano:11
nano:10
nano:9
nano:8
nano:7
nano:6
nano:5
nano:4
nano:3
nano:2
nano:GND.2
nano:RESET.2
nano:0
nano:1
nano:13
nano:3.3V
nano:AREF
nano:A0
nano:A1
nano:A2
nano:A3
nano:A4
nano:A5
nano:A6
nano:A7
nano:5V
nano:RESET
nano:GND.1
nano:VIN
nano:12.2
nano:5V.2
nano:13.2
nano:11.2
nano:RESET.3
nano:GND.3
Loading
ssd1306
btn1:1.l
btn1:2.l
btn1:1.r
btn1:2.r