/*
 * 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 slight modifications) from:
 *     AddrList.h - cycle through lwIP netif's IP addresses like a C++ list
 * ----------------------------------------------------------------------------------
 * Copyright (C) 2018 David Gauchard. All right reserved.
 *
 * 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_ADDR_LIST_H
#define ECXX_PLATFORM_NODEMCU_ADDR_LIST_H


#include <lwip/netif.h>

#include "IPAddress.h"


#if LWIP_IPV6
    #define IF_NUM_ADDRESSES (1 + LWIP_IPV6_NUM_ADDRESSES)
#else
    #define IF_NUM_ADDRESSES (1)
#endif


namespace eCxx {

namespace NodeMCU {


struct NetIFWrapper {
    inline NetIFWrapper(netif* netif)
    : _netif(netif)
    , _num  (-1)
    {}

    inline NetIFWrapper(const NetIFWrapper& o)
    : _netif(o._netif)
    , _num  (o._num)
    {}

    inline NetIFWrapper& operator= (const NetIFWrapper& o)
    {
        _netif = o._netif;
        _num   = o._num;
        return *this;
    }

    inline bool equal(const NetIFWrapper& o)
    { return _netif == o._netif && (!_netif || _num == o._num); }

    // Address properties
    inline IPAddress     addr    () const { return ipFromNetifNum();   }
    inline bool          isLegacy() const { return _num == 0;          }
    inline bool          isLocal () const { return  addr().isLocal();  }
    inline bool          isV4    () const { return  addr().isV4();     }
    inline bool          isV6    () const { return !addr().isV4();     }
    inline const WString toString() const { return  addr().toString(); }

    // Related to legacy address (_num = 0, IPv4)
    inline const IPAddress ipv4   () const { return _netif->ip_addr; }
    inline const IPAddress netmask() const { return _netif->netmask; }
    inline const IPAddress gw     () const { return _netif->gw;      }

    // Common to all addresses of this interface
    inline const WString  ifname    () const { return WString( _netif->name[0] ) + _netif->name[1];              }
    inline const char*    ifhostname() const { return _netif->hostname ? _netif->hostname : emptyString.c_str(); }
    inline const uint8_t* ifmac     () const { return _netif->hwaddr;                                            }
    inline int32_t        ifnumber  () const { return _netif->num;                                               }
    inline bool           ifUp      () const { return !!(_netif->flags & NETIF_FLAG_UP);                         }
    inline const netif*   interface () const { return _netif;                                                    }

    inline const ip_addr_t* ipFromNetifNum() const
    {
#if LWIP_IPV6
        return _num ? &_netif->ip6_addr[_num - 1] : &_netif->ip_addr;
#else
        return &_netif->ip_addr;
#endif
    }

    // lwIP interface
    netif* _netif;

    // Address index within interface
    //      0 : legacy address (IPv4)
    //     >0 : (_num - 1) is IPv6 index for netif->ip6_addr[]
    int32_t _num;
};


class AddressListIterator {
    public:
        inline AddressListIterator (const NetIFWrapper& o)
        : netIf(o)
        {}

        inline AddressListIterator(netif* netif)
        : netIf(netif)
        {
            // # This constructor is called with lwIP's global netif_list, or nullptr.
            // # operator++() is designed to loop through configured addresses.
            // # That's why netIf's _num is initialized to -1 to allow returning the first
            //   usable address to AddressList::begin().
            operator++();
        }

        inline const NetIFWrapper& operator* () const { return  netIf; }
        inline const NetIFWrapper* operator->() const { return &netIf; }

        inline bool operator== (AddressListIterator& o) { return  netIf.equal(*o); }
        inline bool operator!= (AddressListIterator& o) { return !netIf.equal(*o); }

        inline const AddressListIterator operator++(int)
        {
            const AddressListIterator ret = *this;
            operator++();
            return ret;
        }

        inline const AddressListIterator& operator++()
        {
            while(netIf._netif) {
                if(++netIf._num == IF_NUM_ADDRESSES) {
                    // All addresses from current interface have been iterated,
                    // switching to the next interface
                    netIf = NetIFWrapper(netIf._netif->next);
                    continue;
                }
                if( !ip_addr_isany( netIf.ipFromNetifNum() ) ) {
                    // Found an initialized address
                    break;
                }
            }
            return *this;
        }

    private:
        NetIFWrapper netIf;
};


class AddressList {
    public:
        using AddressListConstIterator = const AddressListIterator;
        using const_iterator           = AddressListConstIterator; // For compatibility with the Arduino library part

        inline AddressListConstIterator begin() const
        { return const_iterator(netif_list); }

        inline AddressListConstIterator end() const
        { return const_iterator(nullptr); }

};

inline AddressList::AddressListConstIterator begin(const AddressList& a)
{ return a.begin(); }

inline AddressList::AddressListConstIterator end(const AddressList& a)
{ return a.end(); }


} // namespace NodeMCU

} // namespace eCxx


//
// For compatibility with the Arduino library part
//
namespace esp8266 {
    namespace AddressListImplementation {
        typedef eCxx::NodeMCU::AddressList AddressList;
    }
}

extern esp8266::AddressListImplementation::AddressList addrList;


#endif
