"""
Some routines for retrieving the addresses from the local
network config.
"""

from __future__ import print_function

import itertools
import ctypes


from jaraco.windows.api import errors, inet


def GetAdaptersAddresses():
    size = ctypes.c_ulong()
    res = inet.GetAdaptersAddresses(0, 0, None, None, size)
    if res != errors.ERROR_BUFFER_OVERFLOW:
        raise RuntimeError("Error getting structure length (%d)" % res)
    print(size.value)
    pointer_type = ctypes.POINTER(inet.IP_ADAPTER_ADDRESSES)
    buffer = ctypes.create_string_buffer(size.value)
    struct_p = ctypes.cast(buffer, pointer_type)
    res = inet.GetAdaptersAddresses(0, 0, None, struct_p, size)
    if res != errors.NO_ERROR:
        raise RuntimeError("Error retrieving table (%d)" % res)
    while struct_p:
        yield struct_p.contents
        struct_p = struct_p.contents.next


class AllocatedTable(object):
    """
    Both the interface table and the ip address table use the same
    technique to store arrays of structures of variable length. This
    base class captures the functionality to retrieve and access those
    table entries.

    The subclass needs to define three class attributes:
        method: a callable that takes three arguments - a pointer to
                the structure, the length of the data contained by the
                structure, and a boolean of whether the result should
                be sorted.
        structure: a C structure defininition that describes the table
                format.
        row_structure: a C structure definition that describes the row
                format.
    """

    def __get_table_size(self):
        """
        Retrieve the size of the buffer needed by calling the method
        with a null pointer and length of zero. This should trigger an
        insufficient buffer error and return the size needed for the
        buffer.
        """
        length = ctypes.wintypes.DWORD()
        res = self.method(None, length, False)
        if res != errors.ERROR_INSUFFICIENT_BUFFER:
            raise RuntimeError("Error getting table length (%d)" % res)
        return length.value

    def get_table(self):
        """
        Get the table
        """
        buffer_length = self.__get_table_size()
        returned_buffer_length = ctypes.wintypes.DWORD(buffer_length)
        buffer = ctypes.create_string_buffer(buffer_length)
        pointer_type = ctypes.POINTER(self.structure)
        table_p = ctypes.cast(buffer, pointer_type)
        res = self.method(table_p, returned_buffer_length, False)
        if res != errors.NO_ERROR:
            raise RuntimeError("Error retrieving table (%d)" % res)
        return table_p.contents

    @property
    def entries(self):
        """
        Using the table structure, return the array of entries based
        on the table size.
        """
        table = self.get_table()
        entries_array = self.row_structure * table.num_entries
        pointer_type = ctypes.POINTER(entries_array)
        return ctypes.cast(table.entries, pointer_type).contents


class InterfaceTable(AllocatedTable):
    method = inet.GetIfTable
    structure = inet.MIB_IFTABLE
    row_structure = inet.MIB_IFROW


class AddressTable(AllocatedTable):
    method = inet.GetIpAddrTable
    structure = inet.MIB_IPADDRTABLE
    row_structure = inet.MIB_IPADDRROW


class AddressManager(object):
    @staticmethod
    def hardware_address_to_string(addr):
        hex_bytes = (byte.encode('hex') for byte in addr)
        return ':'.join(hex_bytes)

    def get_host_mac_address_strings(self):
        return (
            self.hardware_address_to_string(addr)
            for addr in self.get_host_mac_addresses()
        )

    def get_host_ip_address_strings(self):
        return itertools.imap(str, self.get_host_ip_addresses())

    def get_host_mac_addresses(self):
        return (entry.physical_address for entry in InterfaceTable().entries)

    def get_host_ip_addresses(self):
        return (entry.address for entry in AddressTable().entries)
