r"""
Write flint header files.
"""
#*****************************************************************************
#       Copyright (C) 2023 Vincent Delecroix <20100.delecroix@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#                  http://www.gnu.org/licenses/
#*****************************************************************************

import os
import shutil
from .env import AUTOGEN_DIR, FLINT_GIT_DIR, FLINT_DOC_DIR, FLINT_INCLUDE_DIR
from .reader import extract_functions


def write_flint_cython_headers(output_dir, documentation=False):
    r"""
    Write cython header files.

    Arguments
    output_dir -- (string) path where to write the .pxd files

    Requires gitpython to be installed.
    """
    if not os.path.isdir(os.path.join(FLINT_GIT_DIR, ".git")):
        raise ValueError(f"FLINT_GIT_DIR(={FLINT_GIT_DIR}) is not a Git repository")

    import git
    repo = git.Repo(FLINT_GIT_DIR)
    commit = repo.head.commit
    print(f"Generating cython headers from {commit}")

    header_list = []
    pxd_list = []
    for filename in os.listdir(FLINT_DOC_DIR):
        if not filename.endswith('.rst'):
            continue
        prefix = filename[:-4]

        absolute_filename = os.path.join(FLINT_DOC_DIR, filename)
        content = extract_functions(absolute_filename)
        if not content:
            # NOTE: skip files with no function declaration
            continue

        # try to match header
        header = prefix + '.h'
        if prefix == 'flint':
            header = header + '.in'
        absolute_header = os.path.join(FLINT_INCLUDE_DIR, header)

        if not os.path.isfile(absolute_header):
            print('Warning: skipping {} because no associated .h found'.format(filename))
            continue

        # TODO: below are some exceptions for which we do not create .pxd file
        if prefix == 'machine_vectors' or prefix == 'fft_small':
            print('Warning: ignoring machine_vectors and fft_small because architecture dependent')
            continue
        if prefix == 'acb_theta':
            print('Warning: ignoring acb_theta because not in stable release')
            continue

        header_list.append(prefix + '.h')
        pxd_list.append(prefix + '.pxd')

        output = open(os.path.join(output_dir, prefix + '.pxd'), 'w')

        print('# distutils: libraries = flint', file=output)
        print('# distutils: depends = flint/{}'.format(prefix + '.h'), file=output)
        print(file=output)
        print('#' * 80, file=output)
        print('# This file is auto-generated by the script', file=output)
        print('#   SAGE_ROOT/src/sage_setup/autogen/flint_autogen.py.', file=output)
        print('# From the commit {}'.format(commit), file=output)
        print('# Do not modify by hand! Fix and rerun the script instead.', file=output)
        print('#' * 80, file=output)
        print(file=output)

        print('from libc.stdio cimport FILE', file=output)
        print('from sage.libs.gmp.types cimport *', file=output)
        print('from sage.libs.mpfr.types cimport *', file=output)
        print('from sage.libs.flint.types cimport *', file=output)
        print(file=output)

        print('cdef extern from "flint_wrap.h":', file=output)

        for section in content:
            if section is not None:
                print('    ## {}'.format(section), file=output)
            for func_signatures, doc in content[section]:
                if documentation:
                    print('', file=output)
                    for line in doc:
                        print('    # {}'.format(line), file=output)
                for line in func_signatures:
                    print('    {} noexcept'.format(line), file=output)

        if os.path.isfile(os.path.join(AUTOGEN_DIR, 'macros', prefix + '_macros.pxd')):
            print('\nfrom .{} cimport *'.format(prefix + '_macros'), file=output)

        output.close()

    for extra_header in ['nmod_types.h']:
        if extra_header in header_list:
            print('Warning: {} already in HEADER_LIST'.format(extra_header))
        header_list.append(extra_header)

    header_list.sort()
    pxd_list.sort()

    with open(os.path.join(AUTOGEN_DIR, 'templates', 'flint_wrap.h.template')) as f:
        text = f.read()
    with open(os.path.join(output_dir, 'flint_wrap.h'), 'w') as output:
        output.write(text.format(HEADER_LIST='\n'.join('#include <flint/{}>'.format(header) for header in header_list)))

    with open(os.path.join(AUTOGEN_DIR, 'templates', 'types.pxd.template')) as f:
        text = f.read()
    with open(os.path.join(output_dir, 'types.pxd'), 'w') as output:
        output.write(text.format(HEADER_LIST=' '.join('flint/{}'.format(header) for header in header_list)))

    for filename in os.listdir(os.path.join(AUTOGEN_DIR, 'macros')):
        prefix = filename[:-4]
        shutil.copy(os.path.join(AUTOGEN_DIR, 'macros', filename), os.path.join(output_dir, filename))

    with open(os.path.join(AUTOGEN_DIR, 'templates', 'flint_sage.pyx.template')) as f:
        text = f.read()
    with open(os.path.join(output_dir, 'flint_sage.pyx'), 'w') as output:
        output.write(text.format(CYTHON_IMPORTS='\n'.join('from .{} cimport *'.format(header[:-4]) for header in pxd_list)))
