#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2014  Alex Merry <alex.merry@kdemail.net>
# Copyright 2014  Aurélien Gâteau <agateau@kde.org>
# Copyright 2014  Alex Turbov <i.zaufi@gmail.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# Python 2/3 compatibility (NB: we require at least 2.7)
from __future__ import division, absolute_import, print_function, unicode_literals

import argparse
import logging
import os
import shutil
import subprocess
import sys
import tempfile

import jinja2

from kapidox import utils
from kapidox.generator import *
try:
    from kapidox import depdiagram
    DEPDIAGRAM_AVAILABLE = True
except ImportError:
    DEPDIAGRAM_AVAILABLE = False

def get_tier(yaml_file):
    """Parse the tier out of a yaml file"""
    with open(yaml_file) as f:
        import yaml
        data = yaml.load(f)
        return data['tier']

def generate_group_menu(tiers):
    """Generate a menu for the frameworks"""
    sections = []
    for t in range(1,5):
        frameworks = []
        for fw in tiers[t]:
            rellink = '../../' + fw['outputdir'] + '/html/index.html'
            frameworks.append({
                'href': rellink,
                'name': fw['fancyname']
                })
        sections.append({
            'title': 'Tier ' + str(t),
            'members': frameworks
            })
    return {'group_title': 'Frameworks', 'sections': sections}

def process_toplevel_html_file(inputfile, outputfile, tiers, title,
        api_searchbox=False):
    """Pass a HTML file through the templating filters"""

    frameworks = {}
    gm_sections = []
    for t in range(1,5):
        gm_frameworks = []
        for fw in tiers[t]:
            rellink = fw['outputdir'] + '/html/index.html'
            gm_frameworks.append({
                'href': rellink,
                'name': fw['fancyname']
                })
            frameworks[fw['modulename']] = {'link': rellink, 'name': fw['fancyname']}
        gm_sections.append({
            'title': 'Tier ' + str(t),
            'members': gm_frameworks
            })
    group_menu = {'group_title': 'Frameworks', 'sections': gm_sections}

    mapping = {
            'resources': '.',
            'api_searchbox': api_searchbox,
            # steal the doxygen css from one of the frameworks
            # this means that all the doxygen-provided images etc. will be found
            'doxygencss': tiers[1][0]['outputdir'] + '/html/doxygen.css',
            'title': title,
            'breadcrumbs': {
                'entries': [
                    {
                        'href': 'http://api.kde.org/',
                        'text': 'KDE API Reference'
                    }
                    ]
                },
            'group_menu': group_menu,
            'frameworks': frameworks
        }
    tmpl = load_template(inputfile)
    with open(outputfile, 'w') as outf:
        outf.write(tmpl.render(mapping))


def find_dot_files(dot_dir):
    """Returns a list of path to files ending with .dot in subdirs of `dot_dir`."""
    lst = []
    for (root, dirs, files) in os.walk(dot_dir):
        lst.extend([os.path.join(root, x) for x in files if x.endswith('.dot')])
    return lst


def generate_diagram(png_path, fancyname, tier, dot_files, tmp_dir):
    """Generate a dependency diagram for a framework.
    """
    def run_cmd(cmd, **kwargs):
        try:
            subprocess.check_call(cmd, **kwargs)
        except subprocess.CalledProcessError as exc:
            logging.error(
                    'Command {exc.cmd} failed with error code {exc.returncode}.'.format(exc=exc))
            return False
        return True

    logging.info('Generating dependency diagram')
    dot_path = os.path.join(tmp_dir, fancyname + '.dot')

    with open(dot_path, 'w') as f:
        with_qt = tier <= 2
        ok = depdiagram.generate(f, dot_files, framework=fancyname, with_qt=with_qt)
        if not ok:
            logging.error('Generating diagram failed')
            return False

    logging.info('- Simplifying diagram')
    simplified_dot_path = os.path.join(tmp_dir, fancyname + '-simplified.dot')
    with open(simplified_dot_path, 'w') as f:
        if not run_cmd(['tred', dot_path], stdout=f):
            return False

    logging.info('- Generating diagram png')
    if not run_cmd(['dot', '-Tpng', '-o' + png_path, simplified_dot_path]):
        return False

    # These os.unlink() calls are not in a 'finally' block on purpose.
    # Keeping the dot files around makes it possible to inspect their content
    # when running with the --keep-temp-dirs option. If generation fails and
    # --keep-temp-dirs is not set, the files will be removed when the program
    # ends because they were created in `tmp_dir`.
    os.unlink(dot_path)
    os.unlink(simplified_dot_path)
    return True


def main():
    utils.setup_logging()

    parser = argparse.ArgumentParser(description='Generate API documentation ' +
            'for the KDE Frameworks')
    parser.add_argument('frameworksdir',
            help='Location of the frameworks modules.')
    parser.add_argument('--title', default='KDE API Documentation',
            help='String to use for page titles.')
    parser.add_argument('--man-pages', action='store_true',
            help='Generate man page documentation.')
    parser.add_argument('--qhp', action='store_true',
            help='Generate Qt Compressed Help documentation.')
    parser.add_argument('--depdiagram-dot-dir',
            help='Generate dependency diagrams, using the .dot files from DIR.',
            metavar="DIR")
    parser.add_argument('--searchengine', action='store_true',
            help="Enable Doxygen's search engine feature.")
    parser.add_argument('--api-searchbox', action='store_true',
            help="Enable the API searchbox (only useful for api.kde.org).")
    parser.add_argument('--doxdatadir',
            help='Location of the HTML header files and support graphics.')
    parser.add_argument('--qtdoc-dir',
            help='Location of (local) Qt documentation; this is searched ' +
                 'for tag files to create links to Qt classes.')
    parser.add_argument('--qtdoc-link',
            help='Override Qt documentation location for the links in the ' +
                 'html files.  May be a path or URL.')
    parser.add_argument('--qtdoc-flatten-links', action='store_true',
            help='Whether to assume all Qt documentation html files ' +
                 'are immediately under QTDOC_LINK (useful if you set ' +
                 'QTDOC_LINK to the online Qt documentation).  Ignored ' +
                 'if QTDOC_LINK is not set.')
    parser.add_argument('--doxygen', default='doxygen',
            help='(Path to) the doxygen executable.')
    parser.add_argument('--qhelpgenerator', default='qhelpgenerator',
            help='(Path to) the qhelpgenerator executable.')
    parser.add_argument('--keep-temp-dirs', action='store_true',
            help='Do not delete temporary dirs, useful for debugging.')
    args = parser.parse_args()

    if args.depdiagram_dot_dir and not DEPDIAGRAM_AVAILABLE:
        logging.error('You need to install the Graphviz Python bindings to generate dependency diagrams.\nSee <http://www.graphviz.org/Download.php>.')
        exit(1)

    doxdatadir = find_doxdatadir_or_exit(args.doxdatadir)
    tagfiles = search_for_tagfiles(
            suggestion = args.qtdoc_dir,
            doclink = args.qtdoc_link,
            flattenlinks = args.qtdoc_flatten_links,
            searchpaths = ['/usr/share/doc/qt5', '/usr/share/doc/qt'])

    if not os.path.isdir(args.frameworksdir):
        logging.error(args.frameworksdir + " is not a directory")
        exit(2)

    framework_list = os.listdir(args.frameworksdir)
    tiers = {1:[],2:[],3:[],4:[]}
    for modulename in framework_list:
        fwdir = os.path.join(args.frameworksdir, modulename)
        if not os.path.isdir(fwdir):
            continue
        yaml_file = os.path.join(fwdir, 'metainfo.yaml')

        if not os.path.isfile(yaml_file):
            logging.warning('{} does not contain a framework (metainfo.yaml missing)'.format(fwdir))
            continue

        fancyname = utils.parse_fancyname(fwdir)
        if not fancyname:
            logging.warning('Could not find fancy name for {}, skipping it'.format(fwdir))
            continue

        outputdir = modulename

        # FIXME: option in yaml file to disable docs
        tier = get_tier(yaml_file)
        if tier is None:
            logging.warning('Could not find tier for {}'.format(framework))
        elif tier < 1 or tier > 4:
            logging.warning('Invalid tier {} for {}'.format(tier, framework))
        else:
            tiers[tier].append({
                'modulename': modulename,
                'fancyname': fancyname,
                'srcdir': fwdir,
                'outputdir': outputdir,
                'dependency_diagram': None,
                })

    for t in range(1,5):
        tiers[t] = sorted(tiers[t], key=lambda f: f['fancyname'].lower())

    copy_dir_contents(os.path.join(doxdatadir,'htmlresource'),'.')

    group_menu = generate_group_menu(tiers)

    def gen_fw_apidocs(fwinfo, rebuild=False):
        logging.info('# Generating doc for {}'.format(fwinfo['fancyname']))
        generate_apidocs(
                modulename = fwinfo['modulename'],
                fancyname = fwinfo['fancyname'],
                srcdir = fwinfo['srcdir'],
                outputdir = fwinfo['outputdir'],
                doxdatadir = doxdatadir,
                tagfiles = tagfiles,
                dependency_diagram = fwinfo['dependency_diagram'],
                man_pages = args.man_pages,
                qhp = args.qhp,
                searchengine = args.searchengine,
                api_searchbox = args.api_searchbox,
                doxygen = args.doxygen,
                qhelpgenerator = args.qhelpgenerator,
                title = args.title,
                resourcedir = '../..',
                template_mapping = {
                    'breadcrumbs': {
                        'entries': [
                            {
                                'href': 'http://api.kde.org/',
                                'text': 'KDE API Reference'
                            },
                            {
                                'href': '../../index.html',
                                'text': 'Frameworks'
                            }
                            ]
                        },
                    'group_menu': group_menu
                    },
                doxyfile_entries = {
                    'WARN_IF_UNDOCUMENTED': True
                    }
                )
        if not rebuild:
            tagfile = os.path.abspath(
                        os.path.join(
                            fwinfo['outputdir'],
                            'html',
                            fwinfo['modulename']+'.tags'))
            tagfiles.append((tagfile,'../../' + fwinfo['outputdir'] + '/html/'))

    process_toplevel_html_file(os.path.join(doxdatadir, 'frameworks.html'), 'index.html',
            title=args.title, tiers=tiers, api_searchbox=args.api_searchbox)

    try:
        if args.depdiagram_dot_dir:
            tmp_dir = tempfile.mkdtemp(prefix='kapidox-deps-')
            dot_files = find_dot_files(args.depdiagram_dot_dir)
            assert(dot_files)
        else:
            tmp_dir = None

        for t in range(1,5):
            for fwinfo in tiers[t]:
                if args.depdiagram_dot_dir:
                    png_path = os.path.join(tmp_dir, fwinfo['modulename']) + '.png'
                    ok = generate_diagram(png_path, fwinfo['fancyname'], t, dot_files, tmp_dir)
                    if ok:
                        fwinfo['dependency_diagram'] = png_path
                gen_fw_apidocs(fwinfo)

            if t >= 3:
                logging.info('# Rebuilding for interdependencies')
                # Rebuild for interdependencies
                # FIXME: can we be cleverer about deps?
                for fwinfo in tiers[t]:
                    shutil.rmtree(fwinfo['outputdir'])
                    gen_fw_apidocs(fwinfo, rebuild=True)
    finally:
        if tmp_dir:
            if args.keep_temp_dirs:
                logging.info('Kept temp dir at {}'.format(tmp_dir))
            else:
                shutil.rmtree(tmp_dir)


if __name__ == "__main__":
    main()

