import pythoncom
import pywintypes
from win32com.adsi import adsi, adsicon
from win32com.adsi.adsicon import *

options = None  # set to optparse options object

ADsTypeNameMap = {}


def getADsTypeName(type_val):
    # convert integer type to the 'typename' as known in the headerfiles.
    if not ADsTypeNameMap:
        for n, v in adsicon.__dict__.items():
            if n.startswith("ADSTYPE_"):
                ADsTypeNameMap[v] = n
    return ADsTypeNameMap.get(type_val, hex(type_val))


def _guid_from_buffer(b):
    return pywintypes.IID(b, True)


def _sid_from_buffer(b):
    return str(pywintypes.SID(b))


_null_converter = lambda x: x

converters = {
    "objectGUID": _guid_from_buffer,
    "objectSid": _sid_from_buffer,
    "instanceType": getADsTypeName,
}


def log(level, msg, *args):
    if options.verbose >= level:
        print("log:", msg % args)


def getGC():
    cont = adsi.ADsOpenObject(
        "GC:", options.user, options.password, 0, adsi.IID_IADsContainer
    )
    enum = adsi.ADsBuildEnumerator(cont)
    # Only 1 child of the global catalog.
    for e in enum:
        gc = e.QueryInterface(adsi.IID_IDirectorySearch)
        return gc
    return None


def print_attribute(col_data):
    prop_name, prop_type, values = col_data
    if values is not None:
        log(2, "property '%s' has type '%s'", prop_name, getADsTypeName(prop_type))
        value = [converters.get(prop_name, _null_converter)(v[0]) for v in values]
        if len(value) == 1:
            value = value[0]
        print(f" {prop_name}={value!r}")
    else:
        print(f" {prop_name} is None")


def search():
    gc = getGC()
    if gc is None:
        log(0, "Can't find the global catalog")
        return

    prefs = [(ADS_SEARCHPREF_SEARCH_SCOPE, (ADS_SCOPE_SUBTREE,))]
    hr, statuses = gc.SetSearchPreference(prefs)
    log(3, "SetSearchPreference returned %d/%r", hr, statuses)

    if options.attributes:
        attributes = options.attributes.split(",")
    else:
        attributes = None

    h = gc.ExecuteSearch(options.filter, attributes)
    hr = gc.GetNextRow(h)
    while hr != S_ADS_NOMORE_ROWS:
        print("-- new row --")
        if attributes is None:
            # Loop over all columns returned
            while 1:
                col_name = gc.GetNextColumnName(h)
                if col_name is None:
                    break
                data = gc.GetColumn(h, col_name)
                print_attribute(data)
        else:
            # loop over attributes specified.
            for a in attributes:
                try:
                    data = gc.GetColumn(h, a)
                    print_attribute(data)
                except adsi.error as details:
                    if details[0] != E_ADS_COLUMN_NOT_SET:
                        raise
                    print_attribute((a, None, None))
        hr = gc.GetNextRow(h)
    gc.CloseSearchHandle(h)


def main():
    global options
    from optparse import OptionParser

    parser = OptionParser()
    parser.add_option(
        "-f", "--file", dest="filename", help="write report to FILE", metavar="FILE"
    )
    parser.add_option(
        "-v",
        "--verbose",
        action="count",
        default=1,
        help="increase verbosity of output",
    )
    parser.add_option(
        "-q", "--quiet", action="store_true", help="suppress output messages"
    )

    parser.add_option("-U", "--user", help="specify the username used to connect")
    parser.add_option("-P", "--password", help="specify the password used to connect")
    parser.add_option(
        "",
        "--filter",
        default="(&(objectCategory=person)(objectClass=User))",
        help="specify the search filter",
    )
    parser.add_option(
        "", "--attributes", help="comma sep'd list of attribute names to print"
    )

    options, args = parser.parse_args()
    if options.quiet:
        if options.verbose != 1:
            parser.error("Can not use '--verbose' and '--quiet'")
        options.verbose = 0

    if args:
        parser.error("You need not specify args")

    search()


if __name__ == "__main__":
    main()
