# create include files that allows interviews to dynamically load
# /usr/X11/lib/libX11.dylib
# or, not, if XQuartz is not installed.
# The overall strategy is, for every XFoo function used in InterViews,
# (listed in x11_used_funs),
# declare a (*iv_dynam_pXFoo) with proper prototype from the corresponding
# XFoo in X11/Xlib.h, and fill in
# the pointer when libX11.dylib is dynamically loaded.
# Actually, we declare a pointer to every extern declared in Xlib.h, etc.


ivinc = '../../include/IV-X11'
xlibs = ["Xlib.h", "Xutil.h"]
xincdir = '/usr/X11/include/X11' # my Mac, but see below.

import sys
if len(sys.argv) > 2: # see args of custom command in ../CMakeLists.txt
  ivinc = sys.argv[1]
  xincdir = sys.argv[2]+'/X11'

print ("ivinc " + ivinc)
print ("xincdir " + xincdir)

exceptions_ = ['XSupportsLocale', 'XDefaultString', 'XKeycodeToKeysym']

def exception(line):
    for i in exceptions_:
        if i in line:
            return True
    return False

#do not understand regular expression prog split. But note that our
# prog.split(line) yields for the main cases...
'''
['extern int ', 'XWMGeometry', '(', '\n']
extern int XWMGeometry(
['extern int ', '_Xdebug', ';', '\n']
extern int _Xdebug;
['extern ', 'int', ' (', '*', 'XSynchronize', '(', '\n']
extern int (*XSynchronize(
['extern char **', 'XGetFontPath', '(', '\n']
extern char **XGetFontPath(
'''
# Fortunately the X11 include files are formatted in such a uniform way
# that simple regular expressions are sufficient for analysis and we can
# avoid using full c language parsing.
# Notice that name is always -3 in the list
# that if -2 is ';' then it is a variable
# and if -2 is a '(' then it is usually a function with just a few exceptions
# ie. a '(' prior in the list to the name means the name is a function pointer.
# In the variable case, the replacement for name is *ivdynam_name
# and the assignment is ivdynam_name = &name;
# In the function case, the replacement is (*ivdynam_name)
# and the assignment is ivdynam_name = name; 
# In the function pointer case, the replacement for name is ivdynam_name_t
# with the extern replaced by typedef and the line after the ';' is
# ivdynam_name_t ivdyname_name;
# with or without the extern
# and the assignment is ivdynam_name = name;

def x11_used_funs_():
    lines = [i.rstrip() for i in open("x11_used_funs", "r").readlines()]
    return set(lines)

x11_used_funs = x11_used_funs_()

def varstyle(a):
    if ';' in a[-2]:
      s = 'var'
    elif '(' in ''.join(a[:-3]):
      s = 'pfun'
    else:
      s = 'fun'
    return s

# assignment line
def assign(a):
    name = a[-3]
    s = varstyle(a);
    if s == 'var':
      return 'ivdynam_%s = &%s;' % (name, name)
    return 'ivdynam_%s = %s;' % (name, name)

# #define line
def redef(a):
    name = a[-3]
    return '#undef %s\n#define %s (*ivdynam_%s)' % (name, name, name)

#first line
def declaration(a):
    s = varstyle(a)
    name = 'ivdynam_' + a[-3]
    if s == 'var': name = '*' + name
    elif s == 'fun': name = ('(*' + name + ')')
    elif s == 'pfun': name = name + '_t'
    line =  ' '.join(a[0:-3] + [name, a[-2]])
    if s == 'pfun': # replace extern by typedef
        line = 'typedef ' + ' '.join(line.split()[1:])
        # will declare on line after ';'
    return line

#first line
def definition(a):
    if varstyle(a) == 'pfun':
      return declaration(a)
      # will define on line after ';'
    # remove the 'extern'
    return "IVX11EXTERN " + " ".join(declaration(a).split()[1:])

# these will be included in every file that calls an X11 function.
declare_f = None # this will also contain a copy of all struct etc.
redef_f = None # eg, #define XQueryFont (*ivdynam_XQueryFont)

# these are used to create the file that associated every function with
# a pointer. Note that define_f allows an earlier #include <Xlib.h>
# since it does not (re)define structs.
define_f = None # limited to the extern names. No define or struct
assign_f = None # eg, ivdynam_XQueryFont = XQueryFont;

def f_begin(prefix):
    f = open(ivinc+'/'+prefix+".h", "w")
    f.write('''
/* automatically generated by mkdynam.py */
#ifndef %s_h
#define %s_h
\n''' % (prefix, prefix))
    return f

def f_write(f, text):
    f.write(text + '\n')

def f_end(f):
    f.write('''
#endif
''')
    f.close()


def x11_externs_(lib):
    ivdynam_Xlib_h = []

    import re
    prog = re.compile(r"([a-zA-Z0-9_]*)( *\(|;.*)")
    lines = open(lib, "r").readlines()
    
    state = 'looking for extern'
    declare_on = True
    for i, line in enumerate(lines):
        if "_X_DEPRECATED" in line:
            # Following is XKeycodeToKeysym, which is exception, and when
            # removed the following XLookupKeysym would be deprecated.
            # So just skip this.
            continue
        if "extern" in line and state == 'looking for extern':
            # exceptions where regex doesn't handle format
            declare_on = True
            if exception(line):
              declare_on = False # no output til after next ; for declare_f
              state = 'looking for ;'
              basename = None
              continue
            # assuming form 'extern type name(or;...' with some variation
            result = prog.search(line)
            if result:
                a = prog.split(line)
                name = a[-3]
                if name in x11_used_funs:
                    x11_used_funs.remove(name)
                state = 'looking for ;'
                if ';' in line:
                    state = 'looking for extern'
                if ';' in a[-2]:
                    pass
                else:
                    assert('(' in a[-2])
                basename = a[-3] if varstyle(a) == 'pfun' else None
                f_write(redef_f, redef(a))
                f_write(assign_f, assign(a))
                f_write(define_f, definition(a))
                f_write(declare_f, declaration(a))
            else: # extern with no name. treat as exception
                declare_on = False
                state = 'looking for ;'
                basename = None
        elif state == 'looking for ;':
            if declare_on:
                f_write(define_f, line.rstrip())
                f_write(declare_f, line.rstrip())
            if ';' in line:
                state = 'looking for extern'
                declare_on = True
                if basename != None:
                    s = 'ivdynam_%s_t* ivdynam_%s;' % (basename, basename)
                    basename = None
                    f_write(define_f, "IVX11EXTERN " + s)
                    f_write(declare_f, 'extern ' + s)
        else:
            if declare_on: # only if not in an exception
                f_write(declare_f, line.rstrip())
            # because we don't write to define_f here, that file
            # contains only the full declarations
            pass

def mk():
    global redef_f, assign_f, define_f, declare_f
    redef_f = f_begin("ivx11_redef")
    assign_f = f_begin("ivx11_assign")
    define_f = f_begin("ivx11_define")
    declare_f = f_begin("ivx11_declare")
    n = len(x11_used_funs)
    for lib in xlibs:
        z=x11_externs_(xincdir+'/'+lib)
    print (n)
    print (len(x11_used_funs))
    f_end(redef_f)
    f_end(assign_f)
    f_end(define_f)
    f_end(declare_f)

mk()
