/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */

/*
 * ----------------------------------------------------------------------------------
 * The code in this file is copied (with a few modifications) from:
 *     ArduinoOTA.h by Ivan Grokhotkov and Miguel Angel Ajo
 * ----------------------------------------------------------------------------------
 * This library is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any later
 * version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along
 * with this library; if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 * ----------------------------------------------------------------------------------
 */


#ifndef ECXX_PLATFORM_NODEMCU_OTA_PROGRAMMING_H
#define ECXX_PLATFORM_NODEMCU_OTA_PROGRAMMING_H


#include <ESP8266mDNS.h>

#include <eCxx/eCxx_Internal.h>

#include "Updater.h"
#include "WString.h"


//
// Forward declaration of UdpContext class
//
class UdpContext;


namespace eCxx {

namespace NodeMCU {


//
// OTA state
//
enum class OTAState : uint8_t {
    Idle,
    WaitAuth,
    RunUpdate
};


//
// OTA error
//
using OTAError = eCxx::Application::OTAError;


//
// OTA programming support
//
class OTAProgramming {
    public:
        using Callback         = eCxx::FunctionalVoidCallback;
        using CallbackProgress = eCxx::FunctionalVoidCallback_Size_Size;
        using CallbackError    = eCxx::function<void(OTAError)>;

    public:
        OTAProgramming();
        ~OTAProgramming();

        void setHostname(const char* hostname);     // Default esp8266-XXXXXXXX
        void setPort(uint16_t port);                // Default 8266
        void setPassword(const char* password);     // Default none
        void setPasswordMD5(const char* password);  // Default none

        inline void setRebootOnSuccess(bool reboot) // Default true
        { _rebootOnSuccess = reboot; }

        inline void onStart(Callback fn)
        { _startCB = fn; }

        inline void onProgress(CallbackProgress fn)
        { _progressCB = fn; }

        inline void onEnd(Callback fn)
        { _endCB = fn; }

        inline void onError(CallbackError fn)
        { _errorCB = fn; }

        void begin();
        void handle();

        void reinitializeMDNS();

        inline int32_t getCommand() const
        { return _cmd; }

        inline const WString& getHostname() const
        { return _hostname; }

    private:
        WString          _hostname;
        uint16_t         _port;
        WString          _password;
        bool             _rebootOnSuccess;

        WString          _nonce;
        UdpContext*      _udpContext;
        bool             _initialized;
        OTAState         _state;

        int32_t          _cmd;
        int32_t          _size;
        WString          _md5;

        IPAddress        _otaIP;
        uint16_t         _otaPort;
        uint16_t         _otaUDPPort;

        Callback         _startCB;
        CallbackProgress _progressCB;
        Callback         _endCB;
        CallbackError    _errorCB;

        int32_t parseInt();
        WString readStringUntil(char end);

        void _onRx();
        void _runUpdate();
};


//
// Declaration of the global instance for the OTAProgramming class
//
extern OTAProgramming OTA;


} // namespace NodeMCU

} // namespace eCxx


#endif
