/*
 * 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:
 *     IPAddress.h - base class that provides IPAddress
 * ----------------------------------------------------------------------------------
 * Copyright (C) 2011 Adrian McEwen. 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_IP_ADDRESS_H
#define ECXX_PLATFORM_NODEMCU_IP_ADDRESS_H


#include <lwip/init.h>

#include "Print.h"


#if !LWIP_IPV6
    struct ip_addr: ipv4_addr {};
#endif


// To display a network interface ID using printf()
#define NETIFID_STR        "%c%c%u"
#define NETIFID_VAL(NETIF) ( (NETIF) ? (NETIF)->name[0]        : '-' ), \
                           ( (NETIF) ? (NETIF)->name[1]        : '-' ), \
                           ( (NETIF) ?  netif_get_index(NETIF) : 42  )


namespace eCxx {

namespace NodeMCU {


class IPAddress {
    public:
        // Constructors
        IPAddress();
        IPAddress(const IPAddress& from);
        IPAddress(uint8_t octet1, uint8_t octet2, uint8_t octet3, uint8_t octet4);
        IPAddress(const uint8_t* address);

        inline IPAddress(int32_t  address) { ctor32(address); }
        inline IPAddress(uint32_t address) { ctor32(address); }
        inline IPAddress(long     address) { ctor32(address); }
        inline IPAddress(u32_t    address) { ctor32(address); }

        inline IPAddress(const ipv4_addr& fw_addr) { setV4(); v4() = fw_addr.addr;  }
        inline IPAddress(const ipv4_addr* fw_addr) { setV4(); v4() = fw_addr->addr; }

        void clear();

        IPAddress& operator=(const uint8_t *address);
        IPAddress& operator=(uint32_t address);
        IPAddress& operator=(const IPAddress&) = default;

        inline IPAddress& operator=(const ipv4_addr& fw_addr) { setV4(); v4() = fw_addr.addr;  return *this; }
        inline IPAddress& operator=(const ipv4_addr* fw_addr) { setV4(); v4() = fw_addr->addr; return *this; }

        bool fromString(const char*address);

        inline bool fromString(const WString &address)
        { return fromString( address.c_str() ); }

        // IPv4 wrapper to uint32-view
        inline const u32_t& v4() const { return ip_2_ip4(&_ip)->addr; }
        inline       u32_t& v4()       { return ip_2_ip4(&_ip)->addr; }

        // Overloaded cast operator to allow IPAddress objects to be used where a pointer
        // to a four-byte uint8_t array is expected
        inline operator uint32_t() const { return isV4() ? v4() : 0; }
        inline operator uint32_t()       { return isV4() ? v4() : 0; }
        inline operator u32_t   () const { return isV4() ? v4() : 0; }
        inline operator u32_t   ()       { return isV4() ? v4() : 0; }

        bool isSet() const;

        inline operator bool () const { return isSet(); }
        inline operator bool ()       { return isSet(); }

        bool operator==(const uint8_t* addr) const;

        inline bool operator==(const IPAddress& addr) const { return  ip_addr_cmp(&_ip, &addr._ip); }
        inline bool operator!=(const IPAddress& addr) const { return !ip_addr_cmp(&_ip, &addr._ip); }
        inline bool operator==(uint32_t         addr) const { return    isV4() && v4() == addr;     }
        inline bool operator!=(uint32_t         addr) const { return !( isV4() && v4() == addr );   }
        inline bool operator==(u32_t            addr) const { return    isV4() && v4() == addr;     }
        inline bool operator!=(u32_t            addr) const { return !( isV4() && v4() == addr );   }

        inline int32_t operator>>(int32_t n) const
        { return isV4() ? (v4() >> n) : 0; }

        // Overloaded index operator to allow getting and setting individual octets of the address
        inline uint8_t  operator[](int index) const { return isV4()? *(raw_address() + index): 0; }
        inline uint8_t& operator[](int index)       { setV4(); return *(raw_address() + index);   }

        virtual size_t printTo(Print& p) const;
        WString toString() const;

        // Check if input string(arg) is a valid IPV4 address
        static inline bool isValid(const WString& arg) { return IPAddress().fromString(arg); }
        static inline bool isValid(const char*    arg) { return IPAddress().fromString(arg); }

        inline operator       ip_addr_t () const { return  _ip; }
        inline operator const ip_addr_t*() const { return &_ip; }
        inline operator       ip_addr_t*()       { return &_ip; }

        inline bool isV4() const { return IP_IS_V4_VAL(_ip); }
        inline void setV4() { IP_SET_TYPE_VAL(_ip, IPADDR_TYPE_V4); }

        inline bool isLocal () const { return ip_addr_islinklocal(&_ip); }

#if LWIP_IPV6
        inline IPAddress(const ip_addr_t& lwip_addr) { ip_addr_copy(_ip,  lwip_addr); }
        inline IPAddress(const ip_addr_t* lwip_addr) { ip_addr_copy(_ip, *lwip_addr); }

        inline IPAddress& operator=(const ip_addr_t& lwip_addr) { ip_addr_copy(_ip,  lwip_addr); return *this; }
        inline IPAddress& operator=(const ip_addr_t* lwip_addr) { ip_addr_copy(_ip, *lwip_addr); return *this; }

        inline       uint16_t* raw6()       { setV6(); return reinterpret_cast<uint16_t*>( ip_2_ip6(&_ip) ); }
        inline const uint16_t* raw6() const { return isV6()? reinterpret_cast<const uint16_t*>( ip_2_ip6(&_ip) ) : nullptr; }

        // When not IPv6, ip_addr_t == ip4_addr_t so this one would be ambiguous, required otherwise
        inline operator const ip4_addr_t*() const { return isV4()? ip_2_ip4(&_ip) : nullptr; }

        inline bool isV6() const { return IP_IS_V6_VAL(_ip); }
        inline void setV6()      { IP_SET_TYPE_VAL(_ip, IPADDR_TYPE_V6); }

    protected:
        bool fromString6(const char* address);
#else
        // Allow portable code when IPv6 is not enabled

        inline       uint16_t* raw6()       { return nullptr; }
        inline const uint16_t* raw6() const { return nullptr; }

        inline bool isV6() const { return false; }
        inline void setV6() {}
#endif

    protected:
        bool fromString4(const char* address);

    private:
        ip_addr_t _ip;

        inline void ctor32(uint32_t address)
        { setV4(); v4() = address; }

              uint8_t* raw_address()       { return reinterpret_cast<uint8_t*>( &v4() ); }
        const uint8_t* raw_address() const { return reinterpret_cast<const uint8_t*>( &v4() ); }

        friend class Client;
        friend class Server;
        friend class UDP;
};


} // namespace NodeMCU

} // namespace eCxx


//
// For compatibility with the Arduino library part
//
typedef eCxx::NodeMCU::IPAddress IPAddress;

extern const IPAddress INADDR_ANY;
extern const IPAddress INADDR_NONE;


#endif
