/*
;    Project:       Open Vehicle Monitor System
;    Date:          14th March 2017
;
;    Changes:
;    1.0  Initial release
;
;    (C) 2011       Michael Stegen / Stegen Electronics
;    (C) 2011-2017  Mark Webb-Johnson
;    (C) 2011        Sonny Chen @ EPRO/DX
;
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in
; all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
; THE SOFTWARE.
*/

#ifndef __OVMS_CELLULAR_H__
#define __OVMS_CELLULAR_H__

#include <map>
#include <vector>
#include <string>
#include <driver/uart.h>
#include "ovms.h"
#include "ovms_utils.h"
#include "pcp.h"
#include "ovms_events.h"
#include "gsmmux.h"
#include "gsmpppos.h"
#include "gsmnmea.h"
#include "ovms_buffer.h"
#include "ovms_command.h"

using namespace std;

#define CELLULAR_NETREG_COUNT 3

class modemdriver;  // Forward declaration

class modem : public pcp, public InternalRamAllocated
  {
  public:
    modem(const char* name, uart_port_t uartnum, int baud, int rxpin, int txpin, int pwregpio, int dtregpio);
    ~modem();

  protected:
    TaskHandle_t m_task;
    volatile QueueHandle_t m_queue;
    int m_baud;
    int m_rxpin;
    int m_txpin;
    int m_pwregpio;
    int m_dtregpio;

  public:
    typedef enum
      {
      None,               // Initialised, and idle
      CheckPowerOff,      // Check modem is powered off, then => PoweredOff
      PoweringOn,         // Power on modem, then => PoweredOn
      Identify,           // Identify the modem model
      PoweredOn,          // Check modem activity, then => MuxStart
      MuxStart,           // Start mux, then => NetWait
      NetWait,            // Wait for cellular service, then => NetStart
      NetStart,           // Start network, either =>NetHold, or =>...
      NetLoss,            // Handle loss of network connectivity
      NetHold,            // MUX established, but no data
      NetSleep,           // PowerMode=Sleep: MUX established, but no data
      NetMode,            // MUX and data established, start PPP
      NetDeepSleep,       // PowerMode=DeepSleep: power save
      PoweringOff,        // Power off modem, then => CheckPowerOff
      PoweredOff,         // Maintain a powered off state
      PowerOffOn,         // Power off modem, then => PoweringOn
      Development         // Let developer handle modem themselves
      } modem_state1_t;
    typedef enum
      {
      SETSTATE = UART_EVENT_MAX+1000,
      TICKER1,
      SHUTDOWN
      } event_type_t;
    typedef enum
      {
      Unknown = 0,
      NotRegistered = 1,
      DeniedRegistration = 2,
      Searching = 3,
      Registered = 4,
      RegisteredEmergencyServices = 5,
      RegisteredRoamingSMS = 6,
      RegisteredRoaming = 7,
      RegisteredHomeSMS = 8,
      RegisteredHome = 9
      } network_registration_t;
    typedef enum
      {
      NRT_GSM = 0,
      NRT_GPRS = 1,
      NRT_EPS = 2
      } network_regtype_t;
    typedef struct
      {
      event_type_t type;
      union
        {
        modem_state1_t newstate; // For SETSTATE
        } data;
      } modem_event_t;
    typedef union
      {
      uart_event_t uart;
      modem_event_t event;
      } modem_or_uart_event_t;

  public:
    OvmsBuffer             m_buffer;

    modem_state1_t         m_state1;
    int                    m_state1_ticker;
    modem_state1_t         m_state1_timeout_goto;
    int                    m_state1_timeout_ticks;
    int                    m_state1_userdata;
    int                    m_line_unfinished;

    std::string            m_line_buffer;
    network_registration_t m_netreg;
    network_registration_t m_netreg_d[CELLULAR_NETREG_COUNT];
    std::string            m_provider;
    int                    m_sq;
    bool                   m_pincode_required;

    uart_port_t            m_uartnum;
    int                    m_err_uart_fifo_ovf;
    int                    m_err_uart_buffer_full;
    int                    m_err_uart_parity;
    int                    m_err_uart_frame;
    int                    m_err_driver_buffer_full;

    modemdriver*           m_driver;
    std::string            m_model;
    int                    m_mux_channels;
    int                    m_mux_channel_CTRL;
    int                    m_mux_channel_NMEA;
    int                    m_mux_channel_DATA;
    int                    m_mux_channel_POLL;
    int                    m_mux_channel_CMD;

    GsmMux*                m_mux;
    GsmPPPOS*              m_ppp;
    GsmNMEA*               m_nmea;

    bool                   m_cmd_running;
    std::string            m_cmd_output;

  public:
    // Modem power control and initialisation
    virtual void SetPowerMode(PowerMode powermode);
    void AutoInit();
    void Restart();
    void PowerOff();
    void PowerCycle();
    void PowerSleep(bool onoff);
    void SupportSummary(OvmsWriter* writer, bool debug=false);

  public:
    // Modem API functionality
    void tx(uint8_t* data, size_t size);
    void tx(const char* data, ssize_t size = -1);
    void muxtx(int channel, uint8_t* data, size_t size);
    void muxtx(int channel, const char* data, ssize_t size = -1);
    bool txcmd(const char* data, ssize_t size = -1);

  public:
    // Modem state diagram implementation
    void SetState1(modem_state1_t newstate);
    modem_state1_t GetState1();
    void State1Leave(modem_state1_t oldstate);
    void State1Enter(modem_state1_t newstate);
    modem_state1_t State1Activity();
    modem_state1_t State1Ticker1();
    bool StandardIncomingHandler(int channel, OvmsBuffer* buf);
    void StandardDataHandler(int channel, OvmsBuffer* buf);
    void StandardLineHandler(int channel, OvmsBuffer* buf, std::string line);
    bool IdentifyModel();

  public:
    // High level API functions
    void StartTask();
    void StopTask();
    void StartNMEA();
    void StopNMEA();
    void StartMux();
    void StopMux();
    void StartPPP();
    void StopPPP();
    void SetCellularModemDriver(const char* ModelType);
    void Task();
    void Ticker(std::string event, void* data);
    void EventListener(std::string event, void* data);
    void IncomingMuxData(GsmMuxChannel* channel);
    void SendSetState1(modem_state1_t newstate);
    bool IsStarted();
    void SetNetworkRegistration(network_regtype_t regtype, network_registration_t netreg);
    void SetProvider(std::string provider);
    void SetSignalQuality(int newsq);
    void ClearNetMetrics();
    void UpdateNetMetrics();

  public:
    // Development assistance functions
    void DevelopmentHexDump(const char* prefix, const char* data, size_t length, size_t colsize=16);
  };

class modemdriver : public InternalRamAllocated
  {
  public:
    modemdriver();
    virtual ~modemdriver();

  public:
    virtual const char* GetModel();
    virtual const char* GetName();
    virtual void Restart();
    virtual void PowerOff();
    virtual void PowerCycle();
    virtual void PowerSleep(bool onoff);

    virtual int GetMuxChannels();
    virtual int GetMuxChannelCTRL();
    virtual int GetMuxChannelNMEA();
    virtual int GetMuxChannelDATA();
    virtual int GetMuxChannelPOLL();
    virtual int GetMuxChannelCMD();
    virtual void StartupNMEA();
    virtual void ShutdownNMEA();
    virtual void StatusPoller();

    virtual bool State1Leave(modem::modem_state1_t oldstate);
    virtual bool State1Enter(modem::modem_state1_t newstate);
    virtual modem::modem_state1_t State1Activity(modem::modem_state1_t curstate);
    virtual modem::modem_state1_t State1Ticker1(modem::modem_state1_t curstate);

  protected:
    unsigned int m_powercyclefactor;
    modem* m_modem;
    int m_statuspoller_step;
  };

template<typename Type> modemdriver* CreateCellularModemDriver()
  {
  return new Type;
  }

class OvmsCellularModemFactory
  {
  public:
    OvmsCellularModemFactory();
    ~OvmsCellularModemFactory();

  public:
    typedef modemdriver* (*FactoryFuncPtr)();
    typedef struct
      {
      FactoryFuncPtr construct;
      const char* name;
      } modemdriver_t;
    typedef map<const char*, modemdriver_t, CmpStrOp> map_modemdriver_t;

    map_modemdriver_t m_drivermap;

  public:
    template<typename Type>
    short RegisterCellularModemDriver(const char* ModelType, const char* ModelName = "")
      {
      FactoryFuncPtr function = &CreateCellularModemDriver<Type>;
        m_drivermap.insert(std::make_pair(ModelType, (modemdriver_t){ function, ModelName }));
      return 0;
      };
    modemdriver* NewCellularModemDriver(const char* ModelType);
  };

extern OvmsCellularModemFactory MyCellularModemFactory;

#endif //#ifndef __OVMS_CELLULAR_H__
