from __future__ import print_function

import sys
import os
import fnmatch
from ctypes import *


# XXX this one may need some tweaking...
def find_library(name, somajor=0):
    env = os.environ.get("LD_LIBRARY_PATH")
    common = ["/lib64", "/lib"]

    if env:
        libdirs = env.split(":") + common
    else:
        libdirs = common

    libdirs = filter(os.path.isdir, libdirs)

    for dir in libdirs:
        files = fnmatch.filter(os.listdir(dir), "lib%s.so.%d" % (name, somajor))
        files = [os.path.join(dir, file) for file in files]

        if files:
            break

    if files:
        return files[0]
    else:
        return None

# find the udev library
libudev = find_library(name="udev", somajor=0)

if not libudev or not os.path.exists(libudev):
    raise ImportError, "No library named %s" % libudev

# load the udev library
libudev = CDLL(libudev)


# create aliases for needed functions and set the return types where needed
libudev_udev_new = libudev.udev_new
libudev_udev_new.argtypes = []
libudev_udev_new.restype = c_void_p
libudev_udev_unref = libudev.udev_unref
libudev_udev_unref.argtypes = [ c_void_p ]

libudev_udev_device_new_from_syspath = libudev.udev_device_new_from_syspath
libudev_udev_device_new_from_syspath.restype = c_void_p
libudev_udev_device_new_from_syspath.argtypes = [ c_void_p, c_char_p ]
libudev_udev_device_unref = libudev.udev_device_unref
libudev_udev_device_unref.argtypes = [ c_void_p ]

libudev_udev_device_get_syspath = libudev.udev_device_get_syspath
libudev_udev_device_get_syspath.restype = c_char_p
libudev_udev_device_get_syspath.argtypes = [ c_void_p ]
libudev_udev_device_get_sysname = libudev.udev_device_get_sysname
libudev_udev_device_get_sysname.restype = c_char_p
libudev_udev_device_get_sysname.argtypes = [ c_void_p ]
libudev_udev_device_get_devpath = libudev.udev_device_get_devpath
libudev_udev_device_get_devpath.restype = c_char_p
libudev_udev_device_get_devpath.argtypes = [ c_void_p ]
libudev_udev_device_get_devtype = libudev.udev_device_get_devtype
libudev_udev_device_get_devtype.restype = c_char_p
libudev_udev_device_get_devtype.argtypes = [ c_void_p ]
libudev_udev_device_get_devnode = libudev.udev_device_get_devnode
libudev_udev_device_get_devnode.restype = c_char_p
libudev_udev_device_get_devnode.argtypes = [ c_void_p ]
libudev_udev_device_get_subsystem = libudev.udev_device_get_subsystem
libudev_udev_device_get_subsystem.restype = c_char_p
libudev_udev_device_get_subsystem.argtypes = [ c_void_p ]
libudev_udev_device_get_sysnum = libudev.udev_device_get_sysnum
libudev_udev_device_get_sysnum.restype = c_char_p
libudev_udev_device_get_sysnum.argtypes = [ c_void_p ]

libudev_udev_device_get_properties_list_entry = libudev.udev_device_get_properties_list_entry
libudev_udev_device_get_properties_list_entry.restype = c_void_p
libudev_udev_device_get_properties_list_entry.argtypes = [ c_void_p ]
libudev_udev_list_entry_get_next = libudev.udev_list_entry_get_next
libudev_udev_list_entry_get_next.restype = c_void_p
libudev_udev_list_entry_get_next.argtypes = [ c_void_p ]

libudev_udev_list_entry_get_name = libudev.udev_list_entry_get_name
libudev_udev_list_entry_get_name.restype = c_char_p
libudev_udev_list_entry_get_name.argtypes = [ c_void_p ]
libudev_udev_list_entry_get_value = libudev.udev_list_entry_get_value
libudev_udev_list_entry_get_value.restype = c_char_p
libudev_udev_list_entry_get_value.argtypes = [ c_void_p ]

libudev_udev_enumerate_new = libudev.udev_enumerate_new
libudev_udev_enumerate_new.restype = c_void_p
libudev_udev_enumerate_new.argtypes = [ c_void_p ]
libudev_udev_enumerate_unref = libudev.udev_enumerate_unref
libudev_udev_enumerate_unref.argtypes = [ c_void_p ]

libudev_udev_enumerate_add_match_subsystem = libudev.udev_enumerate_add_match_subsystem
libudev_udev_enumerate_add_match_subsystem.restype = c_int
libudev_udev_enumerate_add_match_subsystem.argtypes = [ c_void_p, c_char_p ]
libudev_udev_enumerate_scan_devices = libudev.udev_enumerate_scan_devices
libudev_udev_enumerate_scan_devices.restype = c_int
libudev_udev_enumerate_scan_devices.argtypes = [ c_void_p ]
libudev_udev_enumerate_get_list_entry = libudev.udev_enumerate_get_list_entry
libudev_udev_enumerate_get_list_entry.restype = c_void_p
libudev_udev_enumerate_get_list_entry.argtypes = [ c_void_p ]

libudev_udev_device_get_devlinks_list_entry = libudev.udev_device_get_devlinks_list_entry
libudev_udev_device_get_devlinks_list_entry.restype = c_void_p
libudev_udev_device_get_devlinks_list_entry.argtypes = [ c_void_p ]


class UdevDevice(dict):

    def __init__(self, udev, sysfs_path):
        dict.__init__(self)

        # create new udev device from syspath
        udev_device = libudev_udev_device_new_from_syspath(udev, sysfs_path)
        if not udev_device:
            # device does not exist
            return

        # set syspath and sysname properties
        self.syspath = libudev_udev_device_get_syspath(udev_device)
        self.sysname = libudev_udev_device_get_sysname(udev_device)

        # get the devlinks list
        devlinks = []
        devlinks_entry = libudev_udev_device_get_devlinks_list_entry(udev_device)

        while devlinks_entry:
            path = libudev_udev_list_entry_get_name(devlinks_entry)
            devlinks.append(path)

            devlinks_entry = libudev_udev_list_entry_get_next(devlinks_entry)

        # add devlinks list to the dictionary
        self["symlinks"] = devlinks

        # get the first property entry
        property_entry = libudev_udev_device_get_properties_list_entry(udev_device)

        while property_entry:
            name = libudev_udev_list_entry_get_name(property_entry)
            value = libudev_udev_list_entry_get_value(property_entry)

            # lvm outputs values for multiple lvs in one line
            # we want to split them and make a list
            # if the first lv's value is empty we end up with a value starting
            # with name=, prepend a space that our split does the right thing
            if value.startswith("%s=" % name):
                value = " " + value

            if value.count(" %s=" % name):
                value = value.split(" %s=" % name)

            self[name] = value

            # get next property entry
            property_entry = libudev_udev_list_entry_get_next(property_entry)

        # set additional properties
        self.devpath = libudev_udev_device_get_devpath(udev_device)
        self.subsystem = libudev_udev_device_get_subsystem(udev_device)
        self.devtype = libudev_udev_device_get_devtype(udev_device)
        self.sysnum = libudev_udev_device_get_sysnum(udev_device)
        self.devnode = libudev_udev_device_get_devnode(udev_device)

        # cleanup
        libudev_udev_device_unref(udev_device)


class Udev(object):

    def __init__(self):
        self.udev = libudev_udev_new()

    def create_device(self, sysfs_path):
        return UdevDevice(self.udev, sysfs_path)

    def enumerate_devices(self, subsystem=None):
        enumerate = libudev_udev_enumerate_new(self.udev)

        # add the match subsystem
        if subsystem is not None:
            rc = libudev_udev_enumerate_add_match_subsystem(enumerate, subsystem)
            if not rc == 0:
                print("error: unable to add the match subsystem", file=sys.stderr)
                libudev_udev_enumerate_unref(enumerate)
                return []

        # scan the devices
        rc = libudev_udev_enumerate_scan_devices(enumerate)
        if not rc == 0:
            print("error: unable to enumerate the devices", file=sys.stderr)
            libudev_udev_enumerate_unref(enumerate)
            return []

        # create the list of sysfs paths
        sysfs_paths = []

        # get the first list entry
        list_entry = libudev_udev_enumerate_get_list_entry(enumerate)

        while list_entry:
            sysfs_path = libudev_udev_list_entry_get_name(list_entry)
            sysfs_paths.append(sysfs_path)

            # get next list entry
            list_entry = libudev_udev_list_entry_get_next(list_entry)

        # cleanup
        libudev_udev_enumerate_unref(enumerate)

        return sysfs_paths

    def scan_devices(self, sysfs_paths=None):
        if sysfs_paths is None:
            sysfs_paths = self.enumerate_devices()

        for sysfs_path in sysfs_paths:
            device = self.create_device(sysfs_path)

            if device:
                yield device

    def unref(self):
        libudev_udev_unref(self.udev)
        self.udev = None