/**
 * Implementation of AutoConnectElementBasis classes.
 * @file AutoConnectElementImpl.h
 * @author hieromon@gmail.com
 * @version  0.9.11
 * @date 2019-06-25
 * @copyright  MIT license.
 */

#ifndef _AUTOCONNECTELEMENTBASISIMPL_H_
#define _AUTOCONNECTELEMENTBASISIMPL_H_

#include <stdlib.h>
#include <stdio.h>
#if defined(ARDUINO_ARCH_ESP8266)
#include <regex.h>
#elif defined(ARDUINO_ARCH_ESP32)
#include <regex>
#endif
#include "AutoConnectElementBasis.h"

/**
 * Append post-tag accoring by the post attribute.
 * @param  s  An original string
 * @return    A string that appended the post tag
 */
const String AutoConnectElementBasis::posterior(const String& s) const {
  String  html;
  if (post == AC_Tag_BR)
    html = s + String(F("<br>"));
  else if (post == AC_Tag_P)
    html = String("<p>") + s + String(F("</p>"));
  else
    html = s;
  return html;
}

/**
 * Generate an HTML <button> element. The onclick behavior depends on
 * the code held in factionf member.
 * @return  An HTML string.
 */
const String AutoConnectButtonBasis::toHTML(void) const {
  String  html = String("");

  if (enable) {
    html = String(F("<button type=\"button\" name=\"")) + name + String(F("\" value=\"")) + value + String(F("\" onclick=\"")) + action + String("\">") + value + String(F("</button>"));
    html = AutoConnectElementBasis::posterior(html);
  }
  return html;
}

/**
 * Generate an HTML <input type=checkbox> element.
 * A "value" is associated with the input tag and sent by the form
 * action as the value of "name". If the label member is contained, it
 * is placed to the right side of the checkbox to be labeled.
 * f the label member is empty, only the checkbox is placed.
 * @return  An HTML string.
 */
const String AutoConnectCheckboxBasis::toHTML(void) const {
  String  html = String("");

  if (enable) {
    html = String(F("<input type=\"checkbox\" name=\"")) + name + String(F("\" value=\"")) + value + String("\"");
    if (checked)
      html += String(F(" checked"));
    if (label.length())
      html += String(F(" id=\"")) + name + String("\">");
    if (label.length()) {
      String  labelTag = String(F("<label for=\"")) + name + String(F("\">")) + label + String(F("</label>"));
      if (labelPosition == AC_Infront)
        html = labelTag + html;
      else if (labelPosition == AC_Behind)
        html += labelTag;
    }
    html = AutoConnectElementBasis::posterior(html);
  }
  return html;
}

/**
 * Generate an HTML <input type=file> element.
 * The entered value can be obtained using the user callback function
 * registered by AutoConnectAux::on after the form is sent in
 * combination with AutoConnectSubmit.
 * @return String  an HTML string.
 */
const String AutoConnectFileBasis::toHTML(void) const {
  String  html = String("");

  if (enable) {
    if (label.length())
      html = String(F("<label for=\"")) + name + String(F("\">")) + label + String(F("</label>"));
    html += String(F("<input type=\"file\" id=\"")) + name + String(F("\" name=\"")) + name + String("\">");
    html = AutoConnectElementBasis::posterior(html);
  }
  return html;
}

/**
 * Instantiate the upload handler with the specified store type.
 * @param store An enumuration value of ACFile_t
 */
bool AutoConnectFileBasis::attach(const ACFile_t store) {
  AutoConnectUploadFS*  handlerFS;
  AutoConnectUploadSD*  handlerSD;

  // Release previous handler
  detach();
  // Classify a handler type and create the corresponding handler
  switch (store) {
  case AC_File_FS:
    handlerFS = new AutoConnectUploadFS(SPIFFS);
    _upload.reset(reinterpret_cast<AutoConnectUploadHandler*>(handlerFS));
    break;
  case AC_File_SD:
    handlerSD = new AutoConnectUploadSD(SD);
    _upload.reset(reinterpret_cast<AutoConnectUploadHandler*>(handlerSD));
    break;
  case AC_File_Extern:
    break;
  }
  return _upload != false;
}

/**
 * Generate an HTML <input type=text> element.
 * If the value member is contained, it is reflected in the placeholder
 * attribute. The entered value can be obtained using the user callback
 * function registered by AutoConnectAux::on after the form is sent in
 * combination with AutoConnectSubmit.
 * @return String  an HTML string.
 */
const String AutoConnectInputBasis::toHTML(void) const {
  String  html = String("");

  if (enable) {
    if (label.length())
      html = String(F("<label for=\"")) + name + String("\">") + label + String(F("</label>"));
    html += String(F("<input type=\"text\" id=\"")) + name + String(F("\" name=\"")) + name + String("\"");
    if (pattern.length())
      html += String(F(" pattern=\"")) + pattern + String("\"");
    if (placeholder.length())
      html += String(F(" placeholder=\"")) + placeholder + String("\"");
    if (value.length())
      html += String(F(" value=\"")) + value + String("\"");
    html += String(">");
    html = AutoConnectElementBasis::posterior(html);
  }
  return html;
}

/**
 * Evaluate the pattern as a regexp and return whether value matches.
 * Always return true if the pattern is undefined.
 * @return true  The value matches a pattern.
 * @return false The value does not match a pattern.
 */
bool AutoConnectInputBasis::isValid(void) const {
  bool  rc = true;
  if (pattern.length()) {
#if defined(ARDUINO_ARCH_ESP8266)
    regex_t preg;
    if (regcomp(&preg, pattern.c_str(), REG_EXTENDED) != 0) {
      AC_DBG("%s regex compile failed\n", pattern.c_str());
      rc = false;
    }
    else {
      regmatch_t  p_match[1];
      rc = regexec(&preg, value.c_str(), 1, p_match, 0) == 0 ? true : false;
      regfree(&preg);
    }
#elif defined(ARDUINO_ARCH_ESP32)
    const std::regex  re(pattern.c_str());
    rc = std::regex_match(value.c_str(), re);
#endif
  }
  return rc;
}

/**
* Indicate an entry with the specified value in the value's collection.
* @param value     The value to indicates in the collection.
*/
void AutoConnectRadioBasis::check(const String& value) {
  for (std::size_t n = 0; n < _values.size(); n++) {
    if (at(n).equalsIgnoreCase(value)) {
      checked = n + 1;
      break;
    }
  }
}

/**
 * Clear value items of AutoConnectRadio and reallocate new storage.
 * All hold items are released.
 * @param reserve  If 'reserve' is greater than 0, this function
 * allocates new holding storage with the value.
 */
void AutoConnectRadioBasis::empty(const size_t reserve) {
  _values.clear();
  std::vector<String>().swap(_values);
  if (reserve)
    _values.reserve(reserve);
  checked = 0;
}

/**
 * Generate an HTML <input type=radio> element with an <option> element.
 * @return String  an HTML string.
 */
const String AutoConnectRadioBasis::toHTML(void) const {
  String  html = String("");

  if (enable) {
    if (label.length()) {
      html = label;
      if (order == AC_Vertical)
        html += String(F("<br>"));
    }
    uint8_t n = 0;
    for (const String value : _values) {
      n++;
      String  id = name + "_" + String(n);
      html += String(F("<input type=\"radio\" name=\"")) + name + String(F("\" id=\"")) + id + String(F("\" value=\"")) + value + String("\"");
      if (n == checked)
        html += String(F(" checked"));
      html += String(F("><label for=\"")) + id + String("\">") + value + String(F("</label>"));
      if (n <= tags.size())
        html += tags[n - 1];
      if (order == AC_Vertical)
        html += String(F("<br>"));
    }
    html = AutoConnectElementBasis::posterior(html);
  }
  return html;
}

/**
 * Returns current selected value in the radio same group
 */
const String& AutoConnectRadioBasis::value(void) const {
  static const String _nullString = String();
  return checked ? _values.at(checked - 1) : _nullString;
}

/**
 * Clear option items of AutoConnectSelect and reallocate new storage.
 * All hold items are released.
 * @param reserve  If 'reserve' is greater than 0, this function
 * allocates new holding storage with the value.
 */
void AutoConnectSelectBasis::empty(const size_t reserve) {
  _options.clear();
  std::vector<String>().swap(_options);
  if (reserve)
    _options.reserve(reserve);
  selected = 0;
}

/**
* Indicate an entry with the specified value in the value's collection.
* @param value     The value to indicates in the collection.
*/
void AutoConnectSelectBasis::select(const String& value) {
  for (std::size_t n = 0; n < _options.size(); n++) {
    if (at(n).equalsIgnoreCase(value)) {
      selected = n + 1;
      break;
    }
  }
}

/**
 * Generate an HTML <select> element with an <option> element.
 * The attribute value of the <option> element is given to the
 * AutoConnectSelect class as a string array, which would be stored
 * in the 'options' member. If a label member is contained, the <label>
 * element would be generated the preface of <select>.
 * @return String  an HTML string.
 */
const String AutoConnectSelectBasis::toHTML(void) const {
  String  html = String("");

  if (enable) {
    if (label.length())
      html = String(F("<label for=\"")) + name + String("\">") + label + String(F("</label>"));
    html += String(F("<select name=\"")) + name + String(F("\" id=\"")) + name + String("\">");
    uint8_t n = 1;
    for (const String option : _options) {
      html += String(F("<option value=\"")) + option + "\"";
      if (n++ == selected)
        html += String(F(" selected"));
      html += ">" + option + String(F("</option>"));
    }
    html += String(F("</select>"));
    html = AutoConnectElementBasis::posterior(html);
  }
  return html;
}

/**
 * Returns current selected value in the radio same group
 */
const String& AutoConnectSelectBasis::value(void) const {
  static const String _nullString = String();
  return selected ? _options.at(selected - 1) : _nullString;
}

/**
 * Generate an HTML <input type=button> element. This element is used
 * for form submission. An 'onclick' attribute calls fixed JavaScript
 * code as 'sa' named and it's included in the template.
 * @return String  an HTML string.
 */
const String AutoConnectSubmitBasis::toHTML(void) const {
  String  html = String("");

  if (enable) {
    html = String(F("<input type=\"button\" name=\"")) + name + String(F("\" value=\"")) + value + String(F("\" onclick=\"_sa('")) + uri + String("')\">");
    html = AutoConnectElementBasis::posterior(html);
  }
  return html;
}

/**
 * Generate an HTML text element from a string of the value member. If a style
 * exists, it gives a style attribute.
 * @return String  an HTML string.
 */
const String AutoConnectTextBasis::toHTML(void) const {
  String  html = String("");

  if (enable) {
    html = String(F("<div id=\"")) + name + String('"');
    String  value_f = value;

    if (style.length())
      html += String(F(" style=\"")) + style + String("\"");
    html += String(">");
    if (format.length()) {
      int   buflen = (value.length() + format.length() + 16 + 1) & (~0xf);
      char* buffer;
      if ((buffer = (char*)malloc(buflen))) {
        snprintf(buffer, buflen, format.c_str(), value.c_str());
        value_f = String(buffer);
        free(buffer);
      }
    }
    html += value_f + String(F("</div>"));
    html = AutoConnectElementBasis::posterior(html);
  }
  return html;
}

#endif // _AUTOCONNECTELEMENTBASISIMPL_H_
