/**
 *	Declaration of AutoConnect class and accompanying AutoConnectConfig class.
 *	@file	AutoConnect.h
 *	@author	hieromon@gmail.com
 *	@version	0.9.1
 *	@date	2018-02-13
 *	@copyright	MIT license.
 */

#ifndef _AUTOCONNECT_H_
#define _AUTOCONNECT_H_

#include <vector>
#include <memory>
#include <functional>
#include <DNSServer.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <EEPROM.h>
#include <PageBuilder.h>
extern "C" {
#include <user_interface.h>
}
#include "AutoConnectPage.h"

// Uncomment the following AC_DEBUG to enable debug output.
//#define AC_DEBUG

// Debug output destination can be defined externally with AC_DEBUG_PORT
#ifndef AC_DEBUG_PORT
#define AC_DEBUG_PORT Serial
#endif
#ifdef AC_DEBUG
#define AC_DBG(...) do {AC_DEBUG_PORT.print("[AC] "); AC_DEBUG_PORT.printf( __VA_ARGS__ );} while (0)
#else
#define AC_DBG(...)
#endif

#ifndef AUTOCONNECT_APID
#define AUTOCONNECT_APID  "esp8266ap"
#endif

#ifndef AUTOCONNECT_PSK
#define AUTOCONNECT_PSK   "12345678"
#endif

#ifndef AUTOCONNECT_AP_IP
#define AUTOCONNECT_AP_IP 0x01F4A8C0  //*< 192.168.244.1 */
#endif // !AUTOCONNECT_AP_IP
#ifndef AUTOCONNECT_AP_GW
#define AUTOCONNECT_AP_GW 0x01F4A8C0  //*< 192.168.244.1 */
#endif // !AUTOCONNECT_AP_GW
#ifndef AUTOCONNECT_AP_NM
#define AUTOCONNECT_AP_NM 0x00FFFFFF  //*< 255.255.255.0 */
#endif // !AUTOCONNECT_AP_NM

#ifndef AUTOCONNECT_URI
#define AUTOCONNECT_URI         "/_ac"
#endif

#ifndef AUTOCONNECT_HOMEURI
#define AUTOCONNECT_HOMEURI     "/"
#endif

#ifndef AUTOCONNECT_MENU_TITLE
#define AUTOCONNECT_MENU_TITLE  "AutoConnect"
#endif
#define AUTOCONNECT_MENU_TITLE_CONNETED  "Connected"

#define AUTOCONNECT_URI_CONFIG  AUTOCONNECT_URI "/config"
#define AUTOCONNECT_URI_CONNECT AUTOCONNECT_URI "/connect"
#define AUTOCONNECT_URI_RESULT  AUTOCONNECT_URI "/result"
#define AUTOCONNECT_URI_OPEN    AUTOCONNECT_URI "/open"
#define AUTOCONNECT_URI_DISCON  AUTOCONNECT_URI "/disc"
#define AUTOCONNECT_URI_RESET   AUTOCONNECT_URI "/reset"
#define AUTOCONNECT_URI_SUCCESS AUTOCONNECT_URI "/success"
#define AUTOCONNECT_URI_FAIL    AUTOCONNECT_URI "/fail"

#ifndef AUTOCONNECT_TIMEOUT
#define AUTOCONNECT_TIMEOUT     30000
#endif

#ifndef AUTOCONNECT_STARTUPTIME
#define AUTOCONNECT_STARTUPTIME 10
#endif

#ifndef AUTOCONNECT_DNSPORT
#define AUTOCONNECT_DNSPORT 53
#endif

/**< A type to save established credential at WiFi.begin automatically. */
typedef enum AC_SAVECREDENTIAL {
  AC_SAVECREDENTIAL_NEVER,
  AC_SAVECREDENTIAL_AUTO
} AC_SAVECREDENTIAL_t;

class AutoConnectConfig {
 public:
  /**
   *  AutoConnectConfig default constructor.
   *  SSID for the captive portal access point assumes AUTOCONNECT_APID which
   *  assigned from macro. Password is same as above too.
   */
  AutoConnectConfig() :
    apip(AUTOCONNECT_AP_IP),
    gateway(AUTOCONNECT_AP_GW),
    netmask(AUTOCONNECT_AP_NM),
    apid(String(AUTOCONNECT_APID)),
    psk(String(AUTOCONNECT_PSK)),
    channel(1),
    hidden(0),
    autoSave(AC_SAVECREDENTIAL_AUTO),
    autoReset(true),
    uptime(AUTOCONNECT_STARTUPTIME),
    homeUri(AUTOCONNECT_HOMEURI) {}
  /**
   *  Configure by SSID for the captive portal access point and password.
   */
  AutoConnectConfig(const char* ap, const char* password) :
    apip(AUTOCONNECT_AP_IP),
    gateway(AUTOCONNECT_AP_GW),
    netmask(AUTOCONNECT_AP_NM),
    apid(String(ap)),
    psk(String(password)),
    channel(1),
    hidden(0),
    autoSave(AC_SAVECREDENTIAL_AUTO),
    autoReset(true),
    uptime(AUTOCONNECT_STARTUPTIME),
    homeUri(AUTOCONNECT_HOMEURI) {}

  ~AutoConnectConfig() {}

  AutoConnectConfig& operator=(const AutoConnectConfig& o) {
    apip = o.apip;
    gateway = o.gateway;
    netmask = o.netmask;
    apid = o.apid;
    psk = o.psk;
    channel = o.channel;
    hidden = o.hidden;
    autoSave = o.autoSave;
    autoReset = o.autoReset;
    uptime = o.uptime;
    homeUri = o.homeUri;
    return *this;
  }

  IPAddress apip;       /**< SoftAP IP address */
  IPAddress gateway;    /**< SoftAP gateway address */
  IPAddress netmask;    /**< SoftAP subnet mask */
  String    apid;       /**< SoftAP SSID */
  String    psk;        /**< SoftAP password */
  uint8_t   channel;    /**< SoftAP used wifi channel */
  uint8_t   hidden;     /**< SoftAP SSID hidden */
  AC_SAVECREDENTIAL_t  autoSave;  /**< Auto save credential */
  bool      autoReset;  /**< Reset ESP8266 module automatically when WLAN disconnected. */
  int       uptime;     /**< Length of start up time */
  String    homeUri;    /**< A URI of user site */
};

class AutoConnect {
 public:
  AutoConnect();
  AutoConnect(ESP8266WebServer& webServer);
  ~AutoConnect();
  bool  config(AutoConnectConfig& Config);
  bool  config(const char* ap, const char* password = nullptr);
  void  home(String uri);
  bool  begin();
  bool  begin(const char* ssid, const char* passphrase);
  bool  begin(const char* ssid, const char* passphrase, unsigned long timeout);
  void  end();
  void  handleClient();
  void  handleRequest();
  ESP8266WebServer& host();

  typedef std::function<bool(IPAddress)>  DetectExit_ft;
  void  onDetect(DetectExit_ft fn);
  void  onNotFound(ESP8266WebServer::THandlerFunction fn);

 protected:
  enum _webServerAllocateType {
    AC_WEBSERVER_PARASITIC,
    AC_WEBSERVER_HOSTED
  };
  typedef enum _webServerAllocateType  AC_WEBSERVER_TYPE;
  void  _initialize();
  bool  _config();
  void  _startWebServer();
  void  _startDNSServer();
  void  _handleNotFound();
  void  _stopPortal();
  bool  _classifyHandle(HTTPMethod mothod, String uri);
  PageElement*  _setupPage(String uri);

  /** Request handlers implemented by Page Builder */
  String  _induceConnect(PageArgument& args);
  String  _induceDisconnect(PageArgument& args);
  String  _induceReset(PageArgument& args);
  String  _invokeResult(PageArgument& args);

  /** For portal control */
  bool  _captivePortal();
  bool  _isIP(String ipStr);
  wl_status_t _waitForConnect(unsigned long timeout);

  /** Utilities */
  static String        _toMACAddressString(const uint8_t mac[]);
  static unsigned int  _toWiFiQuality(int32_t rssi);
  DetectExit_ft        _onDetectExit;
  ESP8266WebServer::THandlerFunction _notFoundHandler;

  std::unique_ptr<ESP8266WebServer> _webServer;
  std::unique_ptr<DNSServer>        _dnsServer;
  AC_WEBSERVER_TYPE                 _webServerAlloc;

  PageBuilder*  _responsePage;
  PageElement*  _currentPageElement;
  
   /** configurations */
  AutoConnectConfig     _apConfig;
  struct station_config _credential;
  uint8_t       _hiddenSSIDCount;
  unsigned long _portalTimeout;

  /** The control indicators */
  bool  _rfConnect;       /**< URI /connect requested */
  bool  _rfDisconnect;    /**< URI /disc requested */
  bool  _rfReset;         /**< URI /reset requested */

  String        _uri;
  String        _redirectURI;
  IPAddress     _currentHostIP;
  String        _menuTitle;

  /** PegeElements of AutoConnect site. */
  static const char _CSS_BASE[] PROGMEM;
  static const char _CSS_UL[] PROGMEM;
  static const char _CSS_ICON_LOCK[] PROGMEM;
  static const char _CSS_INPUT_BUTTON[] PROGMEM;
  static const char _CSS_INPUT_TEXT[] PROGMEM;
  static const char _CSS_TABLE[] PROGMEM;
  static const char _CSS_LUXBAR[] PROGMEM;
  static const char _ELM_HTML_HEAD[] PROGMEM;
  static const char _ELM_MENU[] PROGMEM;
  static const char _PAGE_STAT[] PROGMEM;
  static const char _PAGE_CONFIGNEW[] PROGMEM;
  static const char _PAGE_OPENCREDT[] PROGMEM;
  static const char _PAGE_SUCCESS[] PROGMEM;
  static const char _PAGE_RESETTING[] PROGMEM;
  static const char _PAGE_DISCONN[] PROGMEM;
  static const char _PAGE_FAIL[] PROGMEM;
  static const char _PAGE_404[] PROGMEM;

  /** Token handlers for PageBuilder */
  String _token_CSS_BASE(PageArgument& args);
  String _token_CSS_UL(PageArgument& args);
  String _token_CSS_ICON_LOCK(PageArgument& args);
  String _token_CSS_INPUT_BUTTON(PageArgument& args);
  String _token_CSS_INPUT_TEXT(PageArgument& args);
  String _token_CSS_TABLE(PageArgument& args);
  String _token_CSS_LUXBAR(PageArgument& args);
  String _token_HEAD(PageArgument& args);
  String _token_MENU(PageArgument& args);
  String _token_ESTAB_SSID(PageArgument& args);
  String _token_WIFI_MODE(PageArgument& args);
  String _token_WIFI_STATUS(PageArgument& args);
  String _token_STATION_STATUS(PageArgument& args);
  String _token_LOCAL_IP(PageArgument& args);
  String _token_SOFTAP_IP(PageArgument& args);
  String _token_GATEWAY(PageArgument& args);
  String _token_NETMASK(PageArgument& args);
  String _token_AP_MAC(PageArgument& args);
  String _token_STA_MAC(PageArgument& args);
  String _token_CHANNEL(PageArgument& args);
  String _token_DBM(PageArgument& args);
  String _token_CPU_FREQ(PageArgument& args);
  String _token_FLASH_SIZE(PageArgument& args);
  String _token_CHIP_ID(PageArgument& args);
  String _token_FREE_HEAP(PageArgument& args);
  String _token_LIST_SSID(PageArgument& args);
  String _token_HIDDEN_COUNT(PageArgument& args);
  String _token_OPEN_SSID(PageArgument& args);
  String _token_UPTIME(PageArgument& args);

  friend class ESP8266WebServer;
};

#endif  // _AUTOCONNECT_H_
