#!/usr/bin/env python3
# coding: utf-8

# Copyright ⓒ 2016 Daniel Keep.
#
# Licensed under the MIT license (see LICENSE or <http://opensource.org
# /licenses/MIT>) or the Apache License, Version 2.0 (see LICENSE of
# <http://www.apache.org/licenses/LICENSE-2.0>), at your option. All
# files in the project carrying such notice may not be copied, modified,
# or distributed except according to those terms.

import distutils.dir_util
import os
import shutil
import sys
import tempfile
import time

from common import *

DOC_ARGS = '--no-deps'
DOC_FEATURES = ""
DOC_TARGET_BRANCH = 'gh-pages'
DOC_PKG_DIR = None
DOC_TOOLCHAIN = None
TEMP_CHECKOUT_PREFIX = 'gh-pages-checkout-'
TEMP_OUTPUT_PREFIX = 'gh-pages-generated-'

TRACE_UPDATE_DOCS = os.environ.get('TRACE_UPDATE_DOCS', '') != ''

def copytree(src, dst):
    msg_trace('copytree(%r, %r)' % (src, dst))
    distutils.dir_util.copy_tree(src=src, dst=dst)

def really_rmtree(path):
    msg_trace('really_rmtree(%r)' % path)

    WAIT_TIME_SECS = 1.0
    MAX_TRIES = 10

    def on_error(func, path, exc_info):
        """
        Error handler for ``shutil.rmtree``.

        If the error is due to an access error (read only file)
        it attempts to add write permission and then retries.

        If the error is for another reason it re-raises the error.

        Usage: ``shutil.rmtree(path, onerror=on_error)``

        From <http://stackoverflow.com/a/2656405>_.
        """
        import stat
        if not os.access(path, os.W_OK):
            # Is the error an access error ?
            os.chmod(path, stat.S_IWUSR)
            func(path)
        else:
            raise

    for _ in range(MAX_TRIES):
        failed = True
        try:
            msg_trace('shutil.rmtree(%r)' % path)
            shutil.rmtree(path, onerror=on_error)
            failed = False
        except WindowsError:
            time.sleep(WAIT_TIME_SECS)
        if not failed: return

    msg('Warning: failed to remove directory %r' % path)

def init_doc_branch():
    msg("Initialising %s branch" % DOC_TARGET_BRANCH)

    dir = os.getcwd()
    msg_trace('dir = %r' % dir)

    tmp = tempfile.mkdtemp(prefix=TEMP_CHECKOUT_PREFIX)
    msg_trace('tmp = %r' % tmp)

    try:
        msg("Cloning into a temporary directory...")
        sh('git init -q "%s"' % tmp)
        msg_trace('os.chdir(%r)' % tmp)
        os.chdir(tmp)
        sh('git checkout -q --orphan "%s"' % DOC_TARGET_BRANCH)
        sh('git commit -qm "Initial commit." --allow-empty')
        sh('git remote add origin "%s"' % dir)
        sh('git push -q origin gh-pages')

    finally:
        msg('Cleaning up...')
        msg_trace('os.chdir(%r)' % dir)
        os.chdir(dir)
        msg_trace('shutil.rmtree(%r)' % tmp)
        really_rmtree(tmp)

    msg('%s is ready.  Continuing.' % DOC_TARGET_BRANCH)

def gen_doc_bare(tmp1, tmp2):
    msg("Generating documentation...")
    args = '%s --features="%s"' % (DOC_ARGS, DOC_FEATURES)
    if DOC_TOOLCHAIN is not None:
        toolchain = "%s run %s " % (RUSTUP, DOC_TOOLCHAIN)
    else:
        toolchain = ""
    sh('%scargo doc %s' % (toolchain, args))
    tmp1_target_doc = '%s/target/doc' % tmp1
    msg_trace('shutil.move(%r, %r)' % (tmp1_target_doc, tmp2))
    shutil.move(tmp1_target_doc, tmp2)

def gen_doc_pkg(tmp1, tmp2, doc_pkg):
    if DOC_FEATURES != "":
        msg("Error: doc packages don't support features.")
        sys.exit(1)

    import json
    old_dir = os.getcwd()
    msg("Generating documentation from doc package...")
    msg_trace('doc_pkg = %r' % doc_pkg)
    msg_trace('os.chdir(%r)' % doc_pkg)
    try:
        os.chdir(doc_pkg)
        manifest_str = sh_eval('cargo read-manifest --manifest-path "%s"'
            % os.path.join(doc_pkg, 'Cargo.toml'))
        manifest = json.loads(manifest_str)
        if DOC_TOOLCHAIN is not None:
            toolchain = "%s run %s " % (RUSTUP, DOC_TOOLCHAIN)
        else:
            toolchain = ""
        packages = " ".join("--package %s" % d['name'] for d in manifest['dependencies'])
        sh('%scargo doc %s' % (toolchain, packages))
        target_doc = os.path.join(doc_pkg, 'target/doc')
        msg_trace('shutil.move(%r, %r)' % (target_doc, tmp2))
        shutil.move(target_doc, tmp2)
    finally:
        msg_trace('os.chdir(%r)' % old_dir)
        os.chdir(old_dir)

def main():
    load_globals_from_metadata('update-docs', globals(),
        {
            'DOC_ARGS',
            'DOC_FEATURES',
            'DOC_TARGET_BRANCH',
            'DOC_PKG_DIR',
            'DOC_TOOLCHAIN',
            'TEMP_CHECKOUT_PREFIX',
            'TEMP_OUTPUT_PREFIX',
        })

    if sh_eval('git symbolic-ref --short HEAD') != u'master':
        msg('Not on master; doing nothing.')
        return 0

    # Sanity check: does the doc branch exist at all?
    branches = {b[2:].strip() for b in sh_eval('git branch', dont_strip=True).splitlines()}
    msg_trace('branches = %r' % branches)
    if DOC_TARGET_BRANCH not in branches:
        init_doc_branch()

    last_rev = sh_eval('git rev-parse HEAD')
    last_msg = sh_eval('git log -1 --pretty=%B')
    msg_trace('last_rev = %r' % last_rev)
    msg_trace('last_msg = %r' % last_msg)

    dir = os.getcwd()
    msg_trace('dir = %r' % dir)

    tmp1 = tempfile.mkdtemp(prefix=TEMP_CHECKOUT_PREFIX)
    tmp2 = tempfile.mkdtemp(prefix=TEMP_OUTPUT_PREFIX)
    msg_trace('tmp1 = %r' % tmp1)
    msg_trace('tmp2 = %r' % tmp2)

    try:
        msg("Cloning into a temporary directory...")
        sh('git clone -qb "%s" "%s" "%s"' % (DOC_TARGET_BRANCH, dir, tmp1))
        msg_trace('os.chdir(%r)' % tmp1)
        os.chdir(tmp1)
        sh('git checkout -q master')

        if DOC_PKG_DIR is not None:
            gen_doc_pkg(tmp1, tmp2, os.path.abspath(DOC_PKG_DIR))
        else:
            gen_doc_bare(tmp1, tmp2)

        msg('Updating %s...' % DOC_TARGET_BRANCH)
        sh('git checkout -q "%s"' % DOC_TARGET_BRANCH)
        sh('git clean -dfq')
        tmp2_doc = '%s/doc' % tmp2

        msg_trace('copytree(%r, %r)' % (tmp2_doc, './doc'))
        copytree(tmp2_doc, './doc')

        msg('Committing changes...')
        sh('git add .')
        sh('git commit --amend -m "Update docs for %s" -m "%s"' % (last_rev[:7], last_msg))

        sh('git push -fqu origin "%s"' % DOC_TARGET_BRANCH)

    finally:
        msg('Cleaning up...')
        msg_trace('os.chdir(%r)' % dir)
        os.chdir(dir)
        msg_trace('shutil.rmtree(%r)' % tmp2)
        really_rmtree(tmp2)
        msg_trace('shutil.rmtree(%r)' % tmp1)
        really_rmtree(tmp1)

    msg('Publishing...')
    sh('git push -f origin "%s"' % DOC_TARGET_BRANCH)

    msg('Done.')


if __name__ == '__main__':
    sys.exit(main())
