""" ctkWrapPythonQt

CLI allowing to generate PythonQt decorator for constructor and destructor of classes.

The CLI expects one class per header file with a class name matching the filename.

It generates the files `<namespace>_<target>.h` and `<namespace>_<target>_init.cpp`

where:

* `<namespace>` is a string like `org_commontk`

* `<target>` is a string like `CTKWidgets`
"""
from __future__ import print_function

import errno
import os
import re
from string import Template

PYTHONQT_WRAPPER_WITH_PARENT = Template("""
//-----------------------------------------------------------------------------
class PythonQtWrapper_${className} : public QObject
{
Q_OBJECT
public:
public Q_SLOTS:
  ${className}* new_${className}(${parentClassName}*  parent = 0)
    {
    return new ${className}(parent);
    }
  void delete_${className}(${className}* obj) { delete obj; }
};
""")

PYTHONQT_WRAPPER_WITHOUT_PARENT = Template("""
//-----------------------------------------------------------------------------
class PythonQtWrapper_${className} : public QObject
{
Q_OBJECT
public:
public Q_SLOTS:
  ${className}* new_${className}()
    {
    return new ${className}();
    }
  void delete_${className}(${className}* obj) { delete obj; }
};
""")

def _mkdir_p(path):
    """See """
    try:
        os.makedirs(path)
    except OSError as exc:
        if exc.errno == errno.EEXIST and os.path.isdir(path):
            pass
        else: raise

def ctk_wrap_pythonqt(target, namespace, output_dir, input_files, extra_verbose):
    if extra_verbose:
        print("target: %s" % target)
        print("namespace: %s" % namespace)
        print("output_dir: %s" % output_dir)
        print("input_files: %s" % input_files)

    _mkdir_p(output_dir)

    includes = []
    pythonqtWrappers = []
    registerclasses = []
    namespace = namespace.replace('.', '_')

    for input_file in input_files:
        filename = os.path.basename(input_file)
        if extra_verbose:
            print("Wrapping %s" % filename)

        # what is the filename without the extension
        filename_we = os.path.splitext(filename)[0]

        # Extract classname - NOTE: We assume the filename matches the associated class
        className = filename_we
        if extra_verbose:
            print("\tclassName:%s" % className)

        # Extract parent classname
        parentClassName = None

        # Read input files
        with open(input_file) as f:
            content = f.read()

            # Skip wrapping if file do NOT contain Q_OBJECT
            if 'Q_OBJECT' not in content:
                if extra_verbose:
                    print("\tskipping - No Q_OBJECT macro")
                continue

            # Skip wrapping if constructor doesn't match:
            #    my_class()
            #    my_class(QObject* newParent ...)
            #    my_class(QWidget* newParent ...)
            # Constructor with either QWidget or QObject as first parameter
            regex = r"[^~]%s[\s\n]*\([\s\n]*((QObject|QWidget)[\s\n]*\*[\s\n]*\w+[\s\n]*(\=[\s\n]*(0|NULL|nullptr)|,.*\=.*\)|\)|\)))" % className
            res = re.search(regex, content, re.MULTILINE)
            if res is None:
                if extra_verbose:
                    print("\tskipping - Missing expected constructor signature")
                continue

            # Skip wrapping if object has a virtual pure method
            # "x3b" is the unicode for semicolon
            regex = r"virtual[\w\n\s\*\(\)]+\=[\s\n]*(0|NULL|nullptr)[\s\n]*\x3b"
            res = re.search(regex, content, re.MULTILINE)
            if res is not None:
                if extra_verbose:
                    print("skipping - Contains a virtual pure method")
                continue

            if parentClassName is None:
                # Does constructor signature is of the form: myclass()
                regex = r"[^~]%s[\s\n]*\([\s\n]*\)" % className
                res = re.search(regex, content, re.MULTILINE)

                if res is not None:
                    parentClassName = ""
                    if extra_verbose:
                        print("\tconstructor of the form: %s()" % className)

            if parentClassName is None:
                # Does constructor signature is of the form: myclass(QObject * parent ...)
                regex = r"%s[\s\n]*\([\s\n]*QObject[\s\n]*\*[\s\n]*\w+[\s\n]*(\=[\s\n]*(0|NULL|nullptr)|,.*\=.*\)|\))" % className
                res = re.search(regex, content, re.MULTILINE)
                if res is not None:
                    parentClassName = "QObject"
                    if extra_verbose:
                        print("\tconstructor of the form: %s(QObject * parent ... )" % className)

            if parentClassName is None:
                # Does constructor signature is of the form: myclass(QWidget * parent ...)
                regex = r"%s[\s\n]*\([\s\n]*QWidget[\s\n]*\*[\s\n]*\w+[\s\n]*(\=[\s\n]*(0|NULL|nullptr)|,.*\=.*\)|\))" % className
                res = re.search(regex, content, re.MULTILINE)
                if res is not None:
                    parentClassName = "QWidget"
                    if extra_verbose:
                        print("\tconstructor of the form: %s(QWidget * parent ... )" % className)

        if parentClassName is not None:
            includes.append('#include "%s.h"' % filename_we)

        # Generate PythonQtWrapper class
        if parentClassName == "QObject" or parentClassName == "QWidget":
            pythonqtWrappers.append(
                PYTHONQT_WRAPPER_WITH_PARENT.substitute(className = className, parentClassName = parentClassName))

        elif parentClassName == "":
            pythonqtWrappers.append(PYTHONQT_WRAPPER_WITHOUT_PARENT.substitute(className = className))

        else: # Case parentClassName is None
            raise Exception("Problem wrapping %s" % input_file)

        # Generate code allowing to register the class metaobject and its associated "light" wrapper
        registerclasses.append(
        Template("""
  PythonQt::self()->registerClass(
  &${className}::staticMetaObject, "${target}",
  PythonQtCreateObject<PythonQtWrapper_${className}>);
  """).substitute(className = className, target = target))

    output_header = output_dir + "/" + namespace + "_" + target + ".h"
    if extra_verbose:
        print("output_header: %s" % output_header)
    # Write master include file
    with open(output_header, "w") as f:
        f.write(Template(
"""
//
// File auto-generated by ctkWrapPythonQt.py
//

#ifndef __${namespace}_${target}_h
#define __${namespace}_${target}_h

#include <PythonQtPythonInclude.h>

//-----------------------------------------------------------------------------
// AUTOGEN/mocs_compilation.cpp cross-TU collisions:
//
// With CMake AUTOMOC enabled, Qt generates a single Translation Unit (TU)
//   <target>_autogen/mocs_compilation*.cpp
// that #includes all moc_*.cpp files together. For CTKDICOMWidgets this
// aggregates moc sources that indirectly pull in both Python.h (via PythonQt)
// and DCMTK's osconfig.h.
//
// Across platforms, pyconfig.h (from Python.h) and dcmtk/config/osconfig.h
// define overlapping feature macros (HAVE_IO_H, HAVE_TEMPNAM, HAVE_GETPID,
// HAVE_STRERROR, ...). When each moc lived in its own TU this went largely
// unnoticed; merged into one TU, the redefinitions trigger a burst of
// warnings everywhere.
//
// On Windows specifically, pyconfig.h may also typedef common types such as
// pid_t, which collides with DCMTK's typedef and can cause MSVC C2632
// ("int followed by int is illegal") when both land in the same TU. On
// Unix-like systems pid_t is already provided by <sys/types.h>, so the type
// conflict does not escalate to an error there.
//
// Mitigation: include Python headers first, then proactively drop a small
// set of Python-defined HAVE_* macros so DCMTK can (re)define them without
// conflict - recreating the pre-AUTOMOC isolation of TUs. If additional
// collisions surface in the future, extend the list below.
#undef HAVE_STAT
#undef HAVE_FTIME
#undef HAVE_GETPID
#undef HAVE_IO_H
#undef HAVE_STRERROR
#undef HAVE_SYS_UTIME_H
#undef HAVE_TEMPNAM
#undef HAVE_TMPNAM
#undef HAVE_LONG_LONG
#undef HAVE_INT64_T

#include <QObject>
${includes}
${pythonqtWrappers}
#endif
""").substitute(namespace = namespace, target = target, includes = '\n'.join(includes), pythonqtWrappers = '\n'.join(pythonqtWrappers)))

    output_cpp = output_dir + "/" + namespace + "_" + target + "_init.cpp"
    if extra_verbose:
        print("output_cpp: %s" % output_cpp)
    with open(output_cpp , "w") as f:
        # Write wrapper header
        f.write(Template(
"""
//
// File auto-generated by ctkWrapPythonQt.py
//

#include <PythonQt.h>
// XXX Avoid  warning: "HAVE_XXXX" redefined
#undef HAVE_STAT
#undef HAVE_FTIME
#undef HAVE_GETPID
#undef HAVE_IO_H
#undef HAVE_STRERROR
#undef HAVE_SYS_UTIME_H
#undef HAVE_TEMPNAM
#undef HAVE_TMPNAM
#undef HAVE_LONG_LONG
#undef HAVE_INT64_T
#include "${namespace}_${target}.h"

void PythonQt_init_${namespace}_${target}(PyObject* module)
{
  Q_UNUSED(module);
  ${registerclasses}
}
""").substitute(namespace = namespace, target = target, registerclasses = '\n'.join(registerclasses)))

if __name__ == '__main__':
    from optparse import OptionParser
    usage = "usage: %prog [options] <output_file> <input_file> [<input_file1> [...]]"
    parser = OptionParser(usage=usage)
    parser.add_option("-t", "--target",
                      dest="target", action="store", type="string",
                      help="Name of the associated library")
    parser.add_option("-n", "--namespace",
                      dest="namespace", action="store", type="string",
                      help="Wrapping namespace")
    parser.add_option("--output-dir",
                      dest="output_dir", action="store", type="string",
                      help="Output directory")
    parser.add_option("-v", "--verbose",
                      dest="verbose", action="store_true",
                      help="Print verbose information")
    parser.add_option("--extra-verbose",
                      dest="extra_verbose", action="store_true",
                      help="Print extra verbose information")

    (options, args) = parser.parse_args()

    #if len(args) < 2:
    #    parser.error("arguments '%s' are required !" % '<output_file> <input_file>')

    if options.extra_verbose:
        options.verbose = True

    ctk_wrap_pythonqt(options.target, options.namespace, options.output_dir, args, options.extra_verbose)

    if options.verbose:
        print("Wrapped %d files" % len(args))
