#!/usr/bin/python

include_dirs = [ "/usr/include" ]

output = "-"

import os
import sys

m_template = """/* %(filename)s (auto-generated) */

#include <objc/objc.h>
#include <stdint.h>

#import "%(h_filename)s"

const NSUInteger MAPIStorePropertyGettersCount = %(nbr_getters)d;
const NSUInteger MAPIStoreLastPropertyIdx = %(last_property)d;
const NSUInteger MAPIStoreSupportedPropertiesCount = %(nbr_supported_properties)d;

const enum MAPITAGS MAPIStoreSupportedProperties[] = {
%(supported_properties)s
};

static const uint16_t MAPIStorePropertyGettersIdx[] = {
%(getters_idx)s
};

static const SEL MAPIStorePropertyGetterSelectors[] = {
%(getters)s
};

#include "code-%(filename)s"
"""

h_template = """/* %(filename)s (auto-generated) */

#ifndef %(h_exclusion)s
#define %(h_exclusion)s 1

#import <Foundation/NSObjCRuntime.h>

#include <stdbool.h>
#include <talloc.h>
#include <util/time.h>
#include <gen_ndr/exchange.h>

extern const NSUInteger MAPIStorePropertyGettersCount;
extern const NSUInteger MAPIStoreLastPropertyIdx;

extern const NSUInteger MAPIStoreSupportedPropertiesCount;
extern const enum MAPITAGS MAPIStoreSupportedProperties[];

#import "MAPIStoreObject.h"

@interface MAPIStoreObject (MAPIStorePropertySelectors)

%(prototypes)s

@end

#include "code-%(filename)s"

#endif /* %(h_exclusion)s */
"""

# hack: some properties have multiple and incompatible types. Sometimes those
# props are not related at all...
bannedProps = set(["PidTagBodyHtml", "PidTagFavAutosubfolders",
                   "PidTagAttachDataObj", "PidTagAclTable", "PidTagAclData",
                   "PidTagRulesTable", "PidTagRulesData",
                   "PidTagDisableWinsock",
                   "PidTagHierarchyServer", "PidTagOfflineAddrbookEntryid",
                   "PidTagShorttermEntryidFromObject",
                   "PidTagNormalMessageSizeExtended",
                   "PidTagAssocMessageSizeExtended",
                   "PidTagMessageSizeExtended",
                   "PidTagOabContainerGuid",
                   "PidTagOfflineAddressBookMessageClass", "PidTagScriptData",
                   "PidTagOfflineAddressBookTruncatedProperties",
                   "PidTagOfflineAddressBookContainerGuid",
                   "PidTagOfflineAddressBookDistinguishedName",
                   "PidTagOfflineAddressBookShaHash",
                   "PidTagSenderTelephoneNumber",
                   "PidTagGatewayNeedsToRefresh",
                   "PidTagWlinkType", "PidTagWlinkFlags",
                   "PidTagWlinkGroupClsid", "PidTagWlinkGroupName",
                   "PidTagWlinkGroupHeaderID",
                   "PidTagScheduleInfoDelegatorWantsCopy",
                   "PidTagWlinkOrdinal",
                   "PidTagWlinkSection", "PidTagWlinkCalendarColor",
                   "PidTagWlinkAddressBookEID", "PidTagWlinkFolderType",
                   "PidTagScheduleInfoDelegateNames",
                   "PidTagScheduleInfoDelegateEntryIds", 
                   "PidTagBusiness2TelephoneNumbers",
                   "PidTagHome2TelephoneNumbers",
                   "PidTagAttachDataObject",
                   "PidTagShorttermEntryIdFromObject",
                   ])

def ParseExchangeH(names, lines):
    state = 0
    maxlines = len(lines)
    x = 0
    while x < maxlines and state != 3:
        stripped = lines[x].strip()
        if state == 0:
            if stripped == "enum MAPITAGS":
                state = 1
        elif state == 1:
            if stripped == "{":
                state = 2
        elif state == 2:
            if stripped == "}":
                state = 3
            else:
                ParseExchangeHDefinition(names, stripped)
        x = x + 1

def ParseExchangeHDefinition(names, line):
    stripped = line.strip()
    eqIdx = stripped.find("=")
    if eqIdx == -1:
        raise Exception, "line does not contain a '='"

    propName = stripped[0:eqIdx]
    if not propName.endswith("_Error") and not propName.endswith("_string8") \
            and propName not in bannedProps:
        intIdx = stripped.find("(int", eqIdx)
        valueIdx = stripped.find("0x", intIdx + 1)
        endIdx = stripped.find(")", valueIdx)
        value = int(stripped[valueIdx:endIdx], 16)
        if value < 0x80000000:
            names[propName] = value

def ParseMapistoreNameIDH(names, lines):
    for line in lines:
        stripped = line.strip()
        if stripped.startswith("#define Pid"):
            ParseMapistoreNameIDHDefinition(names, stripped)

def ParseMapistoreNameIDHDefinition(names, line):
    stripped = line.strip()
    pidIdx = stripped.find("Pid")
    if pidIdx == -1:
        raise Exception, "line does not contain a 'Pid'"
    valueIdx = stripped.find("0x")
    propName = stripped[pidIdx:valueIdx].strip()
    if not propName.startswith("PidLidUnknown") and propName not in bannedProps:
        value = int(stripped[valueIdx:], 16)
        names[propName] = value

def FindHFile(filename):
    found = None

    for dirname in include_dirs:
        full_filename = "%s/%s" % (dirname, filename)
        if os.path.exists(full_filename):
            found = full_filename

    if found is None:
        raise Exception, "'%s' not found in include dirs" % filename

    return found

def ProcessHeaders(names, hdict):
    for filename in hdict:
        header_filename = FindHFile(filename)
        header_file = open(header_filename, "r")
        lines = header_file.readlines()
        header_file.close()
        hdict[filename](names, lines)

if __name__ == "__main__":
    arg_count = len(sys.argv)
    x = 0
    while x < arg_count:
        arg = sys.argv[x]
        argname = None
        if arg.startswith("-"):
            argname = arg[1]
            if len(arg) == 2:
                argvalue = sys.argv[x + 1]
                x = x + 1
            else:
                argvalue = arg[2:]
        x = x + 1

        if argname == "o":
            output = argvalue
        elif argname == "I":
            include_dirs.append(argvalue)

    names = {}
    ProcessHeaders(names,
                   {"gen_ndr/exchange.h": ParseExchangeH,
                    "mapistore/mapistore_nameid.h": ParseMapistoreNameIDH})

    getters = []
    getters_idx = []
    # setters = []
    # preferred_types = []
    prototypes = []
    
    for x in xrange(0x10000):
        getters_idx.append("  0xffff")
        # setters.append("  NULL")

    prop_types = {}
    # sanitization: only take unicode version of text properties
    for name, prop_tag in names.iteritems():
        prop_id = prop_tag >> 16
        prop_type = prop_tag & 0xffff
        if not prop_id in prop_types:
            prop_types[prop_id] = []
        prop_types[prop_id].append(prop_type)
        if (prop_type & 0xfff) == 0x001e:
            prop_tag = (prop_tag & 0xfffff000) | 0x001f
        names[name] = prop_tag

    #sanitization: report multiple types for the same keynames
    for prop_id, xtypes in prop_types.iteritems():
        cnt = len(xtypes)
        if cnt > 1:
            print "%d types available for prop id 0x%.4x: %s" % (cnt, prop_id, ", ".join(["%.4x" % x for x in xtypes]))

    supported_properties = []
    current_getter_idx = 0
    highest_prop_idx = 0
    for name, prop_tag in names.iteritems():
        supported_properties.append("  0x%.8x" % prop_tag);
        prop_idx = (prop_tag & 0xffff0000) >> 16
        getters_idx[prop_idx] = "  0x%.4x" % current_getter_idx
        if prop_idx > highest_prop_idx:
            highest_prop_idx = prop_idx
        getters.append("  @selector (get%s:inMemCtx:)" % name)
        # preferred_types.append("  0x%.4x" % (prop_tag & 0xffff))
        prototypes.append("- (enum mapistore_error) get%s: (void **) data inMemCtx: (TALLOC_CTX *) memCtx;" % name)
        current_getter_idx = current_getter_idx + 1
        # setters[prop_idx] = "  @selector (set%s:)" % name
        # prototypes.append("- (int) set%s: (void **) data;" % name)
        # prototypes.append("")

    filename = "%s.m" % output
    h_filename = "%s.h" % output
    outf = open(filename, "wb+")
    outf.write(m_template % {"getters_idx": ",\n".join(getters_idx),
                             "getters": ",\n".join(getters),
                             "nbr_getters": len(getters),
                             "last_property": highest_prop_idx,
                             "nbr_supported_properties": len(supported_properties),
                             "supported_properties": ",\n".join(supported_properties),
                             "filename": filename,
                             "h_filename": h_filename})
    outf.close()

    outf = open(h_filename, "wb+")
    exclusion = ""
    for x in h_filename.upper():
        if ord(x) < 65 or ord(x) > 90:
            x = "_"
        exclusion = exclusion + x
    outf.write(h_template % {"prototypes": "\n".join(prototypes),
                             "h_exclusion": exclusion,
                             "filename": h_filename })
    outf.close()
