#!/usr/bin/env python3
# Copyright (C) 2011 Igalia S.L.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

from __future__ import print_function

import argparse
import codecs
import glob
import gtkdoc
import logging
import os.path
import sys

if sys.version_info < (3, 2):
    from ConfigParser import SafeConfigParser as ConfigParser
else:
    from configparser import ConfigParser

top_level_directory = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..'))
sys.path.insert(0, os.path.join(top_level_directory, 'Tools', 'glib'))
import common

if sys.version_info.major == 2:
    sys.stdout = codecs.getwriter("utf-8")(sys.stdout)
    sys.stderr = codecs.getwriter("utf-8")(sys.stderr)

def configure_logging(verbose):
    level = logging.DEBUG if verbose else logging.INFO
    logger = logging.getLogger('gtkdoc')
    logger.setLevel(level)
    handler = logging.StreamHandler()
    handler.setLevel(level)
    logger.addHandler(handler)
    if level == logging.DEBUG:
        handler.setFormatter(logging.Formatter('[%(asctime)s]  %(message)s'))
    else:
        handler.setFormatter(logging.Formatter('%(message)s'))

def get_gtkdoc_module_paths(cross_reference_deps):
    dependent_packages = {
        'glib-2.0' : ['glib', 'gobject', 'gio'],
        'libsoup-2.4' : ['libsoup-2.4']
    }

    if arguments.gtk:
        dependent_packages['gdk-pixbuf-2.0'] = ['gdk-pixbuf']
        dependent_packages['gtk+-3.0'] = ['gtk3', 'gdk3']

    paths = []
    html_dir = os.path.join('share', 'gtk-doc', 'html')
    for package, modules in dependent_packages.items():
        prefix = common.prefix_of_pkg_config_file(package)
        if prefix is None:
            continue
        for module in modules:
            paths.append(os.path.join(prefix, html_dir, module))

    for local_dep in cross_reference_deps:
        paths.append(common.build_path('Documentation', local_dep, 'html'))
    return paths

def print_missing_api(generator):
    missing_api = generator.api_missing_documentation()
    if not missing_api:
        return
    print("\nThe following API are missing documentation:")
    for api in missing_api:
        print("\t{0}".format(api))

def files_to_ignore(source_dirs, headers_with_gtkdoc):
    """
    Find files to ignore during documentation generation. We assume that if an
    implementation file exists for a header with gtkdoc (say webkitfoo.cpp for
    webkitfoo.h) we shouldn't ignore that file. Currently this holds true for all
    of the WebKit project.
    """
    implementation_files = list(headers_with_gtkdoc)
    for header in headers_with_gtkdoc:
        def add_file_if_exists(filename):
            for dir in source_dirs:
                file = os.path.join(dir, filename)
                if os.path.isfile(file):
                    implementation_files.append(os.path.abspath(file))
        header_basename_without_extension = os.path.splitext(os.path.basename(header))[0]
        add_file_if_exists(header_basename_without_extension + ".cpp")
        add_file_if_exists(header_basename_without_extension + "Gtk.cpp")
        add_file_if_exists(header_basename_without_extension + ".c")

    def file_should_be_ignored(file):
        if os.path.splitext(file)[1] not in ['.h', '.c', '.cpp', '.cc']:
            return False # These files are ignored anyway.
        if not os.path.isfile(file):
            return True
        return os.path.abspath(file) not in implementation_files

    all_files = sum([[os.path.join(dir, file) for file in os.listdir(dir)] for dir in source_dirs], [])
    return filter(file_should_be_ignored, all_files)

def get_generator_for_config(config_file, virtual_root, cross_reference_deps = []):
    if not os.path.isfile(config_file):
        return None

    config = ConfigParser()
    config.read(config_file)
    module_name = config.sections()[0]
    pkgconfig_file = config.get(module_name, 'pkgconfig_file')

    if not os.path.isfile(pkgconfig_file):
        return None

    source_dirs = config.get(module_name, 'source_dirs').replace(';', ' ').split()
    headers = [os.path.abspath(f) for f in config.get(module_name, 'headers').replace(';', ' ').split()]
    return gtkdoc.PkgConfigGTKDoc(pkgconfig_file, {
        'decorator': config.get(module_name, 'decorator'),
        'deprecation_guard': config.get(module_name, 'deprecation_guard'),
        'library_path': common.library_build_path(),
        'virtual_root': virtual_root,
        'module_name': module_name,
        'namespace': config.get(module_name, 'namespace'),
        'doc_dir': config.get(module_name, 'doc_dir'),
        'output_dir': common.build_path('Documentation', module_name),
        'main_sgml_file': config.get(module_name, 'main_sgml_file'),
        'source_dirs': source_dirs,
        'headers': headers,
        'cflags': " ".join(config.get(module_name, 'cflags').split()),
        'cross_reference_deps': get_gtkdoc_module_paths(cross_reference_deps),
        'ignored_files': files_to_ignore(source_dirs, headers),
    })

def generate_doc(generator, skip_html):
    generator.generate(not skip_html)
    if generator.saw_warnings:
        print_missing_api(generator)
    return generator.saw_warnings

def rebase_doc(generator):
    try:
        generator.rebase_installed_docs()
    except Exception:
        print("Rebase did not happen, likely no documentation is present.")

def generate_documentation(generator):
    if not arguments.rebase:
        return generate_doc(generator, arguments.skip_html)

    rebase_doc(generator)
    return False

def prepare_environment_for_gtkdoc_generation():
    # We need to add the JavaScriptCore build directory to the PKG_CONFIG_PATH
    # so that pkgconfig can properly resolve the libjavascriptcore dependency.
    pkg_config_path = os.environ.get("PKG_CONFIG_PATH")
    os.environ['PKG_CONFIG_PATH'] = common.build_path('Source', 'JavaScriptCore')
    if pkg_config_path:
        os.environ['PKG_CONFIG_PATH'] += ':' + pkg_config_path

    # Newer versions of glib have deprecated g_type_init, so we need to disable
    # that warning when running gtkdoc-scanobj by overriding the CFLAGS we use
    # to compile it.
    cflags = os.environ.get('CFLAGS', '')
    cflags += ' -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_32'

    # In non-x86 architectures, when a pointer is cast to (void*) and
    # back, the compiler thinks that the alignment is random. Since
    # gtkdoc build is broken at any warning message, it is better to
    # silence these false positives.
    cflags += ' -Wno-cast-align'
    os.environ['CFLAGS'] = cflags

    # Paths from the GNUmakefile generated configuration files are relative to the build directory.
    os.chdir(common.build_path())

def build_gtkdoc_for_wpe(arguments):
    webextensions_generator = get_generator_for_config(common.build_path('gtkdoc-webextensions.cfg'), arguments.virtual_root)
    if not webextensions_generator:
        print("gtkdoc-webextensions.cfg does not exist! Skipping that documentation")
        sys.exit(1)
    saw_warnings = generate_documentation(webextensions_generator)
    if saw_warnings:
        sys.exit(saw_warnings)

    wpe_generator = get_generator_for_config(common.build_path('gtkdoc-wpe.cfg'), arguments.virtual_root)
    if not wpe_generator:
        print("gtkdoc-wpe.cfg does not exist! Skipping that documentation")
        sys.exit(1)
    saw_warnings = generate_documentation(wpe_generator)
    sys.exit(saw_warnings)

def build_gtkdoc_for_wkgtk(arguments):
    jsc_generator = get_generator_for_config(common.build_path('gtkdoc-jsc-glib.cfg'), arguments.virtual_root)
    if not jsc_generator:
        print("gtkdoc-jsc-glib.cfg does not exist! Skipping that documentation")
        sys.exit(1)
    saw_warnings = generate_documentation(jsc_generator)
    if saw_warnings:
        sys.exit(saw_warnings)

    webkitdom_generator = get_generator_for_config(common.build_path('gtkdoc-webkitdom.cfg'), arguments.virtual_root, [jsc_generator.module_name])
    if not webkitdom_generator:
        print("gtkdoc-webkitdom.cfg does not exist! Skipping that documentation")
        sys.exit(1)
    saw_warnings = generate_documentation(webkitdom_generator)
    if saw_warnings:
        sys.exit(saw_warnings)

    webkit2_generator = get_generator_for_config(common.build_path('gtkdoc-webkit2gtk.cfg'), arguments.virtual_root, [webkitdom_generator.module_name, jsc_generator.module_name])
    if not webkit2_generator:
        print("gtkdoc-webkit2gtk.cfg does not exist! Skipping that documentation")
        sys.exit(1)
    saw_warnings = generate_documentation(webkit2_generator)
    sys.exit(saw_warnings)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Generate gtkdoc for WebKit.')
    parser.add_argument('-v', '--verbose', action='store_true',
                        help='Whether or not to run in verbose mode.')
    parser.add_argument('--rebase', action='store_true',
                        help='When specified, run the tool in rebase mode.')
    parser.add_argument('--skip-html', action='store_true',
                        help='Whether or not to skip HTML generation, which can be slow.')
    parser.add_argument('--virtual-root', type=str, default='',
                        help='A temporary installation directory which is used as the root ' + \
                             'where the actual installation prefix lives; this is mostly ' + \
                             'useful for packagers, and should be set to what is given to ' + \
                             'make install as DESTDIR.')

    parser.add_argument('--gtk', action='store_true',
                        help='Build documentation for WebKitGTK')
    parser.add_argument('--wpe', action='store_true',
                        help='Build documentation for WPE')

    arguments = parser.parse_args()
    configure_logging(arguments.verbose)

    prepare_environment_for_gtkdoc_generation()

    if arguments.wpe:
        build_gtkdoc_for_wpe(arguments)
    elif arguments.gtk:
        build_gtkdoc_for_wkgtk(arguments)
    else:
        print('Please choose a WebKit port with --gtk or --wpe')
        sys.exit(1)
