#!/usr/bin/env python3

"""
Convert juptyer notebooks to html, markdown, or pdf
during git commits

INSTALL:
1. chmod +x nbgitconvert
2. to setup up pre-commit hook run "./nbgitconvert --setup"

The setup step will create a git hook that, for any .ipynb files staged for commit, will:
1. convert them to the format(s) specified in nbgitconvert.ini placing them in a
   rendered/{output_format}/ directory
2. commit those newly converted files
"""
import subprocess
import argparse
import os
import configparser


convert_dict = {
    'markdown': {'ext': 'md', 'default': 'no'},
    'html': {'ext': 'html', 'default': 'yes'},
    'pdf': {'ext': 'pdf', 'default': 'no'}
}
CONFIG_LOCATION = 'nbgitconvert.ini'


def create_config():
    """create config file with default output type setting written to {CONFIG_LOCATION}"""
    if os.path.exists(CONFIG_LOCATION):
        return  # if the config already exists, don't overwrite it

    lines = ["[output_types]"]
    for output_type, d in convert_dict.items():
        lines.append('%s = %s' % (output_type, d['default']))
    lines.append("")
    lines.append("[output_dir]")
    lines.append("path = rendered/{output_type}")

    with open(CONFIG_LOCATION, "w") as f:
        f.write('\n'.join(lines))


def setup():
    """
        1. create a config file (written to {CONFIG_LOCATION}) if one doesn't exist
        2. create a git pre-commit hook to run this script before every commit
    """
    create_config()
    src = '../../nbgitconvert'
    dst = '.git/hooks/pre-commit'
    try:
        os.symlink(src, dst)
        print("creating sym link from %s to %s" % (src, dst))
    except OSError as e:
        if 'File exists' in e.strerror:
            print(
                'A pre-commit file already exists at %s, you must remove that file '
                'and rerun setup to create the nbgitconvert hook.'
                % dst
            )
        else:
            raise e
    return


def system(*args, **kwargs):
    """Run system command."""
    kwargs.setdefault('stdout', subprocess.PIPE)
    proc = subprocess.Popen(args, **kwargs, encoding="utf8")
    out, err = proc.communicate()
    return out


def system_with_exitcode(*args, **kwargs):
    """Run system command and return an exit code."""
    kwargs.setdefault('stdout', subprocess.PIPE)
    proc = subprocess.Popen(args, **kwargs, encoding="utf8")
    out, err = proc.communicate()
    return proc.returncode


def get_changed_files():
    """Get python files from 'files to commit' git cache list."""
    files = []
    filelist = system('git', 'diff', '--cached', '--name-status').strip()
    for line in filelist.split('\n'):
        if line == '':
            continue
        try:
            action, filename = line.strip().split('\t')
            print(filename)
            if filename.endswith('.ipynb') and action != 'D':
                files.append(filename)
        except Exception:
            raise
    return files


def convert(files, output_type, output_dir):
    """run nbconvert to {output_type} on list of {files}"""
    root_dir = os.getcwd()
    output_dir_ext = output_dir.format(output_type=output_type)
    convert_count = 0
    for file in files:
        file_path = os.path.realpath(file)
        file_dir = os.path.dirname(file_path)
        os.chdir(file_dir)
        output_dir = '%s/%s' % (file_dir, output_dir_ext)
        args = ['jupyter', 'nbconvert', '--to']
        args.append(output_type)
        args.append(file_path)
        args.append('--output-dir=%s' % output_dir)
        print(' '.join(args))
        exitcode = system_with_exitcode(*args)
        os.chdir(root_dir)
        if exitcode == 0:
            convert_count += 1
    return convert_count


def gitadd(files, output_type, output_dir):
    """git add all newly (re)rendered notebook files"""
    output_dir_ext = output_dir.format(output_type=output_type)
    for file in files:
        file_dir = file_dir = os.path.dirname(os.path.realpath(file))
        output_dir = '%s/%s*' % (file_dir, output_dir_ext)
        exitcode = system_with_exitcode('git', 'add', output_dir)
        if exitcode != 0:
            raise Exception('git add failed for %s' % output_dir)


def process(files, output_type, output_dir):
    """convert notebooks files to {output_type} and git add the rendered notebook files"""
    convert_count = convert(files, output_type, output_dir)
    if convert_count > 0:
        gitadd(files, output_type, output_dir)


def main(args):
    if args['setup']:
        setup()
        return

    if args['all']:
        files = filter(
            lambda x: x.endswith('.ipynb'),
            [f for f in os.listdir('.') if os.path.isfile(f)]
        )
    else:
        files = get_changed_files()

    if len(files) == 0:
        return

    if args['pdf']:
        process(files, 'pdf', args['output_dir'])
    if args['markdown']:
        process(files, 'markdown', args['output_dir'])
    if args['html']:
        process(files, 'html', args['output_dir'])


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Process Config Location")
    parser.add_argument('--all', action='store_true')
    parser.add_argument('--setup', action='store_true')
    try:
        config = configparser.ConfigParser()
        config.read(CONFIG_LOCATION)
        args = vars(parser.parse_args())
        for option in config.options('output_types'):
            args[option] = config.getboolean('output_types', option)
        args['output_dir'] = config.get('output_dir', 'path')
    except configparser.NoSectionError:
        pass
    except Exception as e:
        print(e)
        raise
        parser.add_argument('--html', action='store_true')
        parser.add_argument('--markdown', action='store_true')
        parser.add_argument('--pdf', action='store_true')
        args = vars(parser.parse_args())
    main(args)
