from __future__ import print_function
from Cython.Distutils import build_ext
import subprocess
import os
import sys


class Autogen(build_ext, object):
    NAME = 'admesh'
    HEADERS = [
        'admesh/stl.h',
    ]
    PREFIX = 'stl_'
    PRECUT = len(PREFIX)
    PXD_IGNORE = [
        '',
        'exit_on_error',
        'stats_out',
        'print_neighbors',
        'print_edges',
        'scale_versor',
        'add_facet',
        'allocate',
        'reallocate',
        'count_facets',
        'write_binary_block',
        'put_little_int',
        'put_little_float',
    ]
    PYX_IGNORE = ['open', 'close', 'get_error', 'clear_error', 'repair']

    PXD = 'c' + NAME + '.pxd'
    _PXD = '_' + PXD
    PYX = NAME + '.pyx'
    _PYX = '_' + PYX

    SELF = 'stl_file *stl'
    PYSELF = '&self._c_stl_file'

    TEMPLATE = '''    def {pyfunction}({pyargs}):
        """{docstring}"""
        if not self._opened:
            raise AdmeshError('STL not opened'){extra}
        {cfunction}({cargs})
        if stl_get_error(&self._c_stl_file):
            stl_clear_error(&self._c_stl_file)
            raise AdmeshError('{cfunction}')

'''

    def run(self, *args, **kwargs):
        self.autogen()
        super(Autogen, self).run(*args, **kwargs)

    def autogen(self):
        print('generating {PXD} and {PYX}'.format(PXD=Autogen.PXD, PYX=Autogen.PYX))

        with open(Autogen.PXD, 'w') as self.pxd:
            with open(Autogen.PYX, 'w') as self.pyx:
                self.pxd.write('# Autogenerated, do not change!\n')
                self.pyx.write('# Autogenerated, do not change!\n')

                with open(Autogen._PXD, 'r') as _pxd:
                    self.pxd.write(_pxd.read())

                with open(Autogen._PYX, 'r') as _pyx:
                    self.pyx.write(_pyx.read())

                for header in Autogen.HEADERS:
                    self.process_header(header)

    def process_header(self, header):
        _header = self.get_header(header)
        if not _header:
            sys.stderr.write('Error: {h} not found, install ADMesh first.\n\nSee README.rst '
                             'for more information\n'.format(h=header))
            exit(1)
        with open(_header) as h:
            lines = h.read().split('\n')
        for line in lines:
            self.process_line(line)

    def process_line(self, line):
        if not line:
            return
        line = line.split(';')[0]  # cut off ;
        line = line.split()
        if not line or line[0] != 'extern':
            return

        pxd_line = '    {0}\n'.format(' '.join(line[1:]))  # void stl_foo(...)

        line = ' '.join(line[2:])  # stl_foo(...)
        line = line.split('(')
        function = line[0][Autogen.PRECUT:]  # foo

        if function in Autogen.PXD_IGNORE:
            return

        self.pxd.write(pxd_line)

        if function in Autogen.PYX_IGNORE:
            return

        argdeflist = [argdef.strip() for argdef in line[1][:-1].split(',')]
        if argdeflist[0] == Autogen.SELF:
            static = False
            argdeflist = argdeflist[1:]
        else:
            static = True
            return  # this is a hack, we don' want any static method NOW

        args, chars = Autogen.process_argdefs(argdeflist)
        self.pyx.write(Autogen.format_function(function, static, args, chars))

    @classmethod
    def process_argdefs(cls, argdeflist):
        args = []
        chars = set()
        for arg in argdeflist:
            ctype = arg.split()[-2]
            arg = arg.split()[-1]
            if arg.startswith('*'):
                arg = arg[1:]
            if arg.endswith('[]'):
                arg = arg[:-2]
            if arg.endswith('[3]'):
                arg = arg[:-3]
            args.append(arg)
            if ctype == 'char':
                chars.add(arg)
        return args, chars

    @classmethod
    def format_function(cls, function, static, args, chars):
        pyfunction = function
        cfunction = Autogen.PREFIX + function
        docstring = cfunction  # for now we don't have anything better

        if static:
            pyargs = 'cls'
        else:
            pyargs = 'self'

        if args:
            pyargs += ', '
            label = -1
            for i, arg in enumerate(args):
                if arg == 'label':
                    label = i
            if label >= 0:
                largs = args[:label] + args[label+1:] + ["label='admesh'"]
            else:
                largs = args
            pyargs += ', '.join(largs)

        extra = ''
        for idx, arg in enumerate(args):
            if arg not in chars:
                continue
            extra += '\n        '
            extra += 'by_{arg} = {arg}.encode(\'UTF-8\')'.format(arg=arg)
            args[idx] = 'by_' + arg

        cargs = ''
        if not static:
            cargs += Autogen.PYSELF
            if args:
                cargs += ', '
        if args:
            cargs += ', '.join(args)

        return Autogen.TEMPLATE.format(pyfunction=pyfunction,
                                       cfunction=cfunction,
                                       docstring=docstring,
                                       pyargs=pyargs,
                                       cargs=cargs,
                                       extra=extra)

    def get_header(self, header):
        cflags = os.environ.get('CFLAGS', '')
        p = subprocess.Popen('gcc -v -E -'.split() + cflags.split(), stdin=subprocess.PIPE,
                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        out, log = p.communicate('')
        log = log.decode('utf8').split('\n')
        read = False
        for line in log:
            if 'search starts here:' in line:
                read = True
                continue
            if 'End of search list' in line:
                break
            if read:
                candidate = os.path.join(line.strip(), header)
                if os.path.isfile(candidate):
                    print("found %s" % candidate)
                    return candidate
        return None
