#!/usr/bin/env python

import cStringIO
import difflib
import glob
import os
import pprint
import re
import shutil
import sys

from distutils.dep_util import newer

from declaration import *
from gccxmlparser import GccXmlParser
from boilerplate import head, keywords, tail, sip_spec_for
import fill


def get_exported_classes(headers, export_specifier):
    result = {}

    pattern = re.compile(
        r'\s*(class)\s+(%s)\s+(?P<name>\w+)' % export_specifier
        )

    for header in headers:
        classes = []
        for line in open(header):
            match = re.match(pattern, line)
            if match:
                table = match.groupdict()
                classes.append(table['name'])
        result[header] = classes

    return result

# get_exported_classes

def get_qt_access(headers):
    result = {}
    pattern = re.compile(r'\s*Q_OBJECT'
                        '|\s*public\s+slots\s*:'
                        '|\s*protected\s+slots\s*:'
                        '|\s*private\s+slots\s*:'
                        '|\s*signals\s*:'
                        '|\s*public\s*:'
                        '|\s*protected\s*:'
                        '|\s*private\s*:'
                        )
    for header in headers:
        tags = []
        for i, line in enumerate(open(header)):
            match = re.match(pattern, line)
            if match:
                if match.group(0).endswith('Q_OBJECT'):
                    tags.append((i+1, 'Q_OBJECT'))
                else:
                    # remove trailing ':' and collapse multiple spaces
                    tags.append((i+1, ' '.join(match.group(0)[:-1].split())))
        result[header] = tags

    return result

# get_qt_access()


def run_gccxml(gccxml, input_files, exported_classes, xmldir, force=False):
    output_files = []
    for header_file in input_files:
        class_names = exported_classes.get(header_file, [])
        if class_names == []:
            continue
        xml_file = os.path.join(
            xmldir,
            os.path.splitext(os.path.basename(header_file))[0]) + '.xml'
        output_files.append((header_file, xml_file))

        if newer(header_file, xml_file) or force:
            command = gccxml % (header_file, xml_file, ','.join(class_names))
            print command
            os.system(command)
            
    return output_files

# run_gccxml()


def xml2sip(header_file, xml_file, sip_file,
            exported_classes, qt_access, substitutions, force=False):
    if newer(xml_file, sip_file) or force:
        parser = GccXmlParser()
        parser.parse(xml_file)

        print "Handling", header_file

        declarations = []
        chunks = [head]
        contents = []
        skip = ['QwtCPointerData']

        name2key = {}
        for key, (what, declaration) in parser.elements.iteritems():
            if (isinstance(declaration, Class)
                and declaration.name in exported_classes
                and declaration.name not in skip
                and declaration.location[0] == header_file
                and type(declaration) != NestedClass
                ):
                name2key[declaration.name] = key

        names = name2key.keys()
        names.sort()

        for name in names:
            what, declaration = parser.elements[name2key[name]]
            print '..', declaration.name, '..'
            contents.append(declaration.name)
            declaration.qtfy(qt_access)
            chunks.append(declaration.sipify(
                excluded_bases=[],
                ))

        if not contents:
            print "Skipping", header_file
            return

        keywords['description'] = sip_spec_for(contents)
        chunks[0] %= keywords
        chunks.append(tail)

        text = '\n\n'.join(chunks)
        for old, new in substitutions:
            text = text.replace(old, new)

        lines = text.split('\n')
        for (i, line) in enumerate(lines):
            target = line[:]
            if target.startswith('    const') and target.endswith(' const;'):
                target = target.replace('    const ', '    ', 1)
                target = target.replace(' const;', ';')
                if target != line and -1 != text.find(target):
                    lines[i] = line.replace(
                        '    const', '    // signature: const')

        text = '\n'.join(lines)

        open(sip_file, 'w').write(text)

# xml2sip()


def run_xml2sip(input_files, sipdir,
                exported_classes, qt_access, substitutions, force=False):
    output_files = []
    for header_file, xml_file in input_files:
        sip_file = os.path.join(
            sipdir,
            os.path.splitext(os.path.basename(xml_file))[0]) + '.sip'
        output_files.append((header_file, xml_file, sip_file))

        if not (newer(xml_file, sip_file) or force):
            continue

        xml2sip(header_file,
                xml_file,
                sip_file,
                exported_classes[header_file],
                qt_access[header_file],
                substitutions)
        
    return output_files

# run_xml2sip()


def run_h2sip(pyqwt, sources, mix, gccxml, substitutions, force=False):
    for d in (os.path.join('xml', mix), os.path.join('sip', mix)):
        if not os.path.exists(d):
            os.makedirs(d)
    headers = glob.glob(os.path.join(os.pardir, pyqwt, sources))
    exported_classes = get_exported_classes(headers, 'QWT_EXPORT')
    qt_access = get_qt_access(headers)
    xml_files = run_gccxml(gccxml,
                           headers,
                           exported_classes,
                           os.path.join('xml', mix),
                           force)
    sip_files = run_xml2sip(xml_files,
                            os.path.join('sip', mix),
                            exported_classes,
                            qt_access,
                            substitutions,
                            force)

# def run_h2sip()


def merge(texts):
    if len(texts) == 1:
        return texts.values()[0]
    timelines = sorted(texts.keys())
    timelines.append('')
    heads = []
    bodies = []
    tails = []
    for (t0, t1) in zip(timelines[:-1], timelines[1:]):
        text = texts[t0]
        i = text.find(os.linesep*3)+len(os.linesep)*3
        j = text.find(os.linesep*3+'//', i)
        head = text[:i-len(os.linesep)]
        if head not in heads:
            heads.append(head)
        body = text[i:j]
        body = ('\n%%If (%s - %s)\n'
                '%s'
                '\n%%End // (%s - %s)\n') % (t0, t1, body, t0, t1)
        bodies.append(body)
        tail = text[j+len(os.linesep):]
        if tail not in tails:
            tails.append(tail)
    chunks = []
    chunks.extend(heads)
    chunks.extend(bodies)
    chunks.extend(tails)
    return ''.join(chunks)

# merge()


def main():
    force = False
    run_h2sip('pyqwt4',
              os.path.join('qwt-4.2.0', 'include', '*.h'),
              '4.2.0-3.3.8',
              ('gccxml'
               ' -I.'
               ' -I/home/gav/usr/lib/qt3.3/include'
               ' %s -fxml=%s -fxml-start=%s'),
              (('QMemArray<double>', 'QwtArrayDouble'),
               ('QMemArray<int>', 'QwtArrayInt',),
               ('QMemArray<long int>', 'QwtArrayLong',),
               ('QMemArray<QwtDoublePoint>', 'QwtArrayQwtDoublePoint'),
               ('QwtPlotItemList', 'QValueList<QwtPlotItem*>'),
               ),
              force)
 
    for sources, mix in (
        (os.path.join('qwt-5.0.0', 'src', '*.h'), '5.0.0-3.3.8'),
        (os.path.join('qwt-5.0.1', 'src', '*.h'), '5.0.1-3.3.8'),
        (os.path.join('qwt-5.0.2', 'src', '*.h'), '5.0.2-3.3.8'),
        (os.path.join('qwt-5.1.0', 'src', '*.h'), '5.1.0-3.3.8'),
        (os.path.join('qwt-5.1.1', 'src', '*.h'), '5.1.1-3.3.8'),
        (os.path.join('qwt-5.1.2', 'src', '*.h'), '5.1.2-3.3.8'),
        (os.path.join('qwt-5.2.0', 'src', '*.h'), '5.2.0-3.3.8'),
        (os.path.join('qwt-5.0', 'src', '*.h'), '5.0.3-3.3.8'),
        (os.path.join('qwt-5.1', 'src', '*.h'), '5.1.3-3.3.8'),
        (os.path.join('qwt-5.2', 'src', '*.h'), '5.2.1-3.3.8'),
        ):
        run_h2sip('pyqwt5',
                  sources,
                  mix,
                  ('gccxml'
                   ' -I.'
                   ' -I/home/gav/usr/lib/qt3.3/include'
                   ' %s -fxml=%s -fxml-start=%s'),
                  (('QMemArray<double>', 'QwtArrayDouble'),
                   ('QMemArray<int>', 'QwtArrayInt',),
                   ('QMemArray<QwtDoublePoint>', 'QwtArrayQwtDoublePoint'),
                   ('QwtPlotItemList', 'QValueList<QwtPlotItem*>'),
                   ),
                  force)

    for sources, mix in (
        (os.path.join('qwt-5.0.0', 'src', '*.h'), '5.0.0-4.6.3'),
        (os.path.join('qwt-5.0.1', 'src', '*.h'), '5.0.1-4.6.3'),
        (os.path.join('qwt-5.0.2', 'src', '*.h'), '5.0.2-4.6.3'),
        (os.path.join('qwt-5.1.0', 'src', '*.h'), '5.1.0-4.6.3'),
        (os.path.join('qwt-5.1.1', 'src', '*.h'), '5.1.1-4.6.3'),
        (os.path.join('qwt-5.1.2', 'src', '*.h'), '5.1.2-4.6.3'),
        (os.path.join('qwt-5.2.0', 'src', '*.h'), '5.2.0-4.6.3'),
        (os.path.join('qwt-5.0',   'src', '*.h'), '5.0.3-4.6.3'),
        (os.path.join('qwt-5.1',   'src', '*.h'), '5.1.3-4.6.3'),
        (os.path.join('qwt-5.2',   'src', '*.h'), '5.2.1-4.6.3'),
        ):
        run_h2sip('pyqwt5',
                  sources,
                  mix,
                  ('gccxml'
                   ' -I.'
                   ' -I/usr/include/qt4'
                   ' -I/usr/include/qt4/QtCore'
                   ' -I/usr/include/qt4/QtGui'
                   ' %s -fxml=%s -fxml-start=%s'),
                  (('QPointFData', 'QwtDoublePointData'),
                   ('QVector<double>', 'QwtArrayDouble'),
                   ('QVector<int>', 'QwtArrayInt'),
                   ('QVector<QPointF>', 'QwtArrayQwtDoublePoint'),
                   ('QwtPlotItemList', 'QList<QwtPlotItem*>'),
                   # FIXME: implement more restrictive replacements.
                   # This undoes replacements which are too greedy!!
                   ('QPolygonFData', 'QwtPolygonFData'),
                   ),
                  force)


    for pyqwt, target in (('pyqwt4', 'qwt4qt3'),
                          ('pyqwt5', 'qwt5qt3'),
                          ('pyqwt5', 'qwt5qt4')):
        for d in (os.path.join('sip', target),
                  os.path.join(os.pardir, pyqwt, 'sip', target)):
            if not os.path.exists(d):
                os.makedirs(d)
        fill.main(target)

    for sip_file in glob.glob(os.path.join('sip', '4.2.0-3.3.8', '*.sip')):
        shutil.copyfile(sip_file, os.path.join(
            '..', 'pyqwt4', 'sip', 'qwt4qt3', os.path.basename(sip_file)))
    for sip_file in glob.glob(os.path.join('sip', 'qwt4qt3', '*.sip')):
        shutil.copyfile(sip_file, os.path.join(
            '..', 'pyqwt4', 'sip', 'qwt4qt3', os.path.basename(sip_file)))

    jobs = {'qwt5qt3': (('Qwt_5_0_0', '5.0.0-3.3.8'),
                        ('Qwt_5_0_1', '5.0.1-3.3.8'),
                        ('Qwt_5_0_2', '5.0.2-3.3.8'),
                        ('Qwt_5_0_3', '5.0.3-3.3.8'),
                        ('Qwt_5_1_0', '5.1.0-3.3.8'),
                        ('Qwt_5_1_1', '5.1.1-3.3.8'),
                        ('Qwt_5_1_2', '5.1.2-3.3.8'),
                        ('Qwt_5_1_3', '5.1.3-3.3.8'),
                        ('Qwt_5_2_0', '5.2.0-3.3.8'),
                        ('Qwt_5_2_1', '5.2.1-3.3.8'),
                        ),
            'qwt5qt4': (('Qwt_5_0_0', '5.0.0-4.6.3'),
                        ('Qwt_5_0_1', '5.0.1-4.6.3'),
                        ('Qwt_5_0_2', '5.0.2-4.6.3'),
                        ('Qwt_5_0_3', '5.0.3-4.6.3'),
                        ('Qwt_5_1_0', '5.1.0-4.6.3'),
                        ('Qwt_5_1_1', '5.1.1-4.6.3'),
                        ('Qwt_5_1_2', '5.1.2-4.6.3'),
                        ('Qwt_5_1_3', '5.1.3-4.6.3'),
                        ('Qwt_5_2_0', '5.2.0-4.6.3'),
                        ('Qwt_5_2_1', '5.2.1-4.6.3'),
                        ),
            }
    for target, pairs in jobs.iteritems():
        sources = set()
        for timeline, mix in pairs:
            sources.update([os.path.basename(source) for source
                            in glob.glob(os.path.join('sip', mix, '*.sip'))])
        for source in sources:
            texts = {}
            old = ''
            for timeline, mix in pairs:
                try:
                    new = open(os.path.join('sip', mix, source)).read()
                    if new != old:
                        texts[timeline] = new
                        old = new
                except IOError:
                    pass
            text = merge(texts)
            if isinstance(text, str):
                open(os.path.join('sip', target, source), 'w').write(text)
        for sip_file in glob.glob(os.path.join('sip', target, '*.sip')):
            shutil.copyfile(sip_file, os.path.join('..', 'pyqwt5', sip_file))

# main()

if __name__ == '__main__':
    main()

# Local Variables: ***
# mode: python ***
# End: ***
