import os
import shutil
import six
import yaml
import pathlib
from mkgendocs.parse import GoogleDocString, Extract
import argparse
import ast
from mako.template import Template

DOCSTRING_TEMPLATE = """
## if we are processing a method function
%if header['function']:
    %if header['class']:
${h3} .${header['function']}
    %else:
${h3} ${header['function']}
    %endif
```python
.${signature}
```
%elif header['class']:
${h2} ${header['class']}

```python 
${signature}
```

%endif 

%for section in sections:
    %if section['header']:

**${section['header']}**

    %else:
---
    %endif
    %if section['args']:
        %for arg in section['args']:
        %if arg['field']:
* **${arg['field']}** ${arg['signature']} : ${arg['description']}
        %else:
* ${arg['description']}
        %endif
        %endfor
    %endif
${section['text']}
%endfor
"""


def get_module_docstring(filepath):
    """Extract the module docstring.

    Also finds the line at which the docstring ends.
    """
    extract = Extract(filepath)
    docstring = extract.get_docstring()

    return docstring


def copy_examples(examples_dir, destination_dir):
    """Copy the examples directory in the documentation.

    Prettify files by extracting the docstrings written in Markdown.
    """
    pathlib.Path(destination_dir).mkdir(exist_ok=True)
    for file in os.listdir(examples_dir):
        if not file.endswith('.py'):
            continue
        module_path = os.path.join(examples_dir, file)
        docstring = get_module_docstring(module_path)
        destination_file = os.path.join(destination_dir, file[:-2] + 'md')
        with open(destination_file, 'w+', encoding='utf-8') as f_out, \
                open(os.path.join(examples_dir, file),
                     'r+', encoding='utf-8') as f_in:

            f_out.write(docstring + '\n\n')

            # skip docstring
            for _ in range(starting_line):
                next(f_in)

            f_out.write('```python\n')
            # next line might be empty.
            line = next(f_in)
            if line != '\n':
                f_out.write(line)

            # copy the rest of the file.
            for line in f_in:
                f_out.write(line)
            f_out.write('```')


def to_markdown(target_info, template):
    docstring = target_info['docstring']
    parser = GoogleDocString(docstring)
    try:
        parser.parse()
    except SyntaxError as e:
        e2 = f"Error while processing docstrings for {target_info['class']}.{target_info['function']}"
        raise Exception(e2 + ":\n\t" + str(e)).with_traceback(e.__traceback__)

    headers, data = parser.markdown()

    # if docstring contains a signature, override the source
    if "signature" in data[0]:
        signature = data[0]["signature"]
    else:
        signature = target_info['signature']

    # print(class_info)

    # in mako ## is a comment
    markdown_str = template.render(header=target_info,
                                   signature=signature,
                                   sections=data,
                                   headers=headers,
                                   h2='##', h3='###')

    return markdown_str


def generate(config_path):
    """Generates the markdown files for the documentation.

    # Arguments
        sources_dir: Where to put the markdown files.
    """

    root = pathlib.Path().absolute()  # pathlib.Path(__file__).resolve().parents[1]
    print("Loading configuration file")
    config = yaml.full_load(open(config_path))

    sources_dir = config.get('sources_dir', 'docs/sources')
    template_dir = config.get('templates_dir', None)

    print('Cleaning up existing sources directory.')
    if os.path.exists(sources_dir):
        shutil.rmtree(sources_dir)

    print('Populating sources directory with templates.')
    if template_dir:
        if not os.path.exists(template_dir):
            raise FileNotFoundError("No such directory: %s" % template_dir)
        shutil.copytree(template_dir, sources_dir)

    if os.path.exists('README.md'):
        readme = open('README.md').read()

    if template_dir and os.path.exists(os.path.join(template_dir, 'index.md')):
        index = open(os.path.join(template_dir, 'index.md')).read()
        index = index.replace('{{autogenerated}}', readme[readme.find('##'):])
    else:
        index = readme

    # TODO this and README are still hardcoded filenames
    if os.path.exists('CONTRIBUTING.md'):
        shutil.copyfile('CONTRIBUTING.md', os.path.join(sources_dir, 'contributing.md'))

    if os.path.exists('examples'):
        copy_examples(os.path.join('examples'),
                      os.path.join(sources_dir, 'examples'))

    # if there are no templates, sources are not created from the files copied
    if not os.path.exists(sources_dir):
        os.makedirs(sources_dir)

    with open(os.path.join(sources_dir, 'index.md'), 'w', encoding='utf-8') as f:
        f.write(index)

    print("Generating docs ...")
    docstring_template = DOCSTRING_TEMPLATE
    if "docstring_template" in config:
        try:
            docstring_template = open(config["docstring_template"]).read()
        except FileNotFoundError as e:
            raise e
    markdown_template = Template(text=docstring_template)

    pages = config.get("pages", dict())
    for page_data in pages:
        source = os.path.join(root, page_data['source'])
        extract = Extract(source)

        markdown_docstrings = []
        classes = page_data.get('classes', [])
        methods = page_data.get('methods', [])
        functions = page_data.get('functions', [])

        for class_name in classes:
            target_info = extract.get_class(class_name)
            markdown_str = to_markdown(target_info)
            markdown_docstrings.append(markdown_str)

            methods = extract.get_methods(class_name)
            if methods:
                markdown_docstrings[-1] += "\n\n**Methods:**\n\n"
                for method in methods:
                    try:
                        method_info = extract.get_method(class_name=class_name,
                                                         method_name=method)
                        markdown_str = to_markdown(method_info, markdown_template)
                        markdown_docstrings[-1] += markdown_str
                    except NameError:
                        pass

        # print(page_data.get('methods', []))
        for class_dict in methods:
            for c, ms in class_dict.items():
                for m in ms:
                    print(f"Generating docs for {c}.{m}")
                    method_info = extract.get_method(class_name=c,
                                                     method_name=m)
                    markdown_str = to_markdown(method_info, markdown_template)
                    markdown_docstrings.append(markdown_str)

        for fn in functions:
            print(f"Generating docs for {fn}")
            fn_info = extract.get_function(fn)
            markdown_str = to_markdown(fn_info, markdown_template)
            markdown_docstrings.append(markdown_str)
        #    for method name in class_name:

        markdown = '\n----\n\n'.join(markdown_docstrings)

        # Either insert content into existing template or create new page
        page_name = page_data['page']
        path = os.path.join(sources_dir, page_name)
        if os.path.exists(path):
            page_template = open(path).read()

            if '{{autogenerated}}' not in page_template:
                raise RuntimeError('Template found for ' + path +
                                   ' but missing {{autogenerated}}'
                                   ' tag.')
            markdown = page_template.replace('{{autogenerated}}', markdown)
            print('...inserting autogenerated content into template:', path)
        else:
            print('...creating new page with autogenerated content:', path)
        subdir = os.path.dirname(path)
        if not os.path.exists(subdir):
            os.makedirs(subdir)
        with open(path, 'w', encoding='utf-8') as f:
            f.write(markdown)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Generate docs')
    parser.add_argument('-c', '--config', dest='config', help='path to config file', default="mkgendocs.yml")
    args = parser.parse_args()
    generate(args.config)
