#!/usr/bin/env python
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import argparse
import configparser
import io
import os
import os.path
import sys
from urllib import parse as urllib_parse

import jinja2


def escape(buff):
    """Because otherwise Firefox is a sad panda."""
    return buff.replace(',', '%2c').replace('-', '%2D')


def generate_dashboard_url(dashboard):
    """Generate a dashboard URL from a given definition."""
    try:
        title = dashboard.get('dashboard', 'title')
    except configparser.NoOptionError:
        raise ValueError("option 'title' in section 'dashboard' not set")

    try:
        foreach = dashboard.get('dashboard', 'foreach')
    except configparser.NoOptionError:
        raise ValueError("option 'foreach' in section 'dashboard' not set")

    try:
        baseurl = dashboard.get('dashboard', 'baseurl')
    except configparser.NoOptionError:
        baseurl = 'https://review.opendev.org/#/dashboard/?'

    url = baseurl
    url += escape(urllib_parse.urlencode({'title': title,
                                          'foreach': foreach}))
    for section in dashboard.sections():
        if not section.startswith('section'):
            continue

        try:
            query = dashboard.get(section, 'query')
        except configparser.NoOptionError:
            raise ValueError("option 'query' in '%s' not set" % section)

        title = section[9:-1]
        encoded = escape(urllib_parse.urlencode({title: query}))
        url += "&%s" % encoded
    return url


def get_options():
    """Parse command line arguments and options."""
    parser = argparse.ArgumentParser(
        description='Create a Gerrit dashboard URL from specified dashboard '
                    'definition files')
    parser.add_argument('dashboard_paths', nargs='+',
                        metavar='dashboard_path',
                        help='Path to a dashboard definition file or a '
                             'directory containing a set of dashboard '
                             'definition files with the file suffix .dash.')
    parser.add_argument('--check-only', default=False, action="store_true",
                        help='Only check the syntax of the specified '
                             'dasbhoard files')
    parser.add_argument('--template', default='single.txt',
                        help='Name of template')

    # Find path to template_dir
    # We need to support running with and without installation
    if os.path.exists('templates'):
        template_dir = 'templates'
    elif os.path.exists('/usr/local/share/gerrit-dash-creator/templates'):
        template_dir = '/usr/local/share/gerrit-dash-creator/templates'
    else:
        template_dir = os.path.join(sys.prefix, 'share',
                                    'gerrit-dash-creator', 'templates')
    parser.add_argument('--template-directory',
                        default=template_dir,
                        help='Directory to scan for template files')
    parser.add_argument('--template-file', default=None,
                        help='Location of a specific template file')
    return parser.parse_args()


def read_dashboard_file(dashboard_file):
    """Read and parse a dashboard definition from a specified file."""
    if (not os.path.isfile(dashboard_file) or
            not os.access(dashboard_file, os.R_OK)):
        raise ValueError("dashboard file '%s' is missing or "
                         "is not readable" % dashboard_file)
    dashboard = configparser.ConfigParser()
    dashboard.read_file(open(dashboard_file))
    return dashboard


def load_template(template_file=None, template_directory=None,
                  template_name=None):
    """Load the specified template."""
    if template_file:
        template_name = os.path.basename(template_file)
        template_directory = os.path.dirname(os.path.abspath(template_file))

    try:
        loader = jinja2.FileSystemLoader(template_directory)
        environment = jinja2.Environment(loader=loader)
        template = environment.get_template(template_name)
    except (jinja2.exceptions.TemplateError, IOError) as e:
        print("error: opening template '%s' failed: %s" %
              (template_name, e.__class__.__name__))
        return

    return template


def get_configuration(dashboard):
    """Returns the configuration of a dashboard as string."""
    configuration = io.StringIO()
    dashboard.write(configuration)
    result = configuration.getvalue()
    configuration.close()
    return result


def generate_dashboard_urls(dashboards, template):
    """Prints the dashboard URLs of a set of dashboards."""
    result = 0

    for dashboard_file in dashboards:
        dashboard = dashboards[dashboard_file]
        try:
            url = generate_dashboard_url(dashboard)
        except ValueError as e:
            raise ValueError("generating dashboard '%s' failed: %s" %
                             (dashboard_file, e))
            result = 1
            continue

        variables = {
            'url': url,
            'title': dashboard.get('dashboard', 'title') or None,
            'description': dashboard.get('dashboard', 'description') or None,
            'configuration': get_configuration(dashboard)
        }
        print(template.render(variables))

    return result


def load_dashboards(paths):
    """Load specified dashboards from files or directories."""
    dashboards = {}
    for dashboard_path in paths:
        dashboard_files = []
        if os.path.isdir(dashboard_path):
            for root, dirs, files in os.walk(dashboard_path):
                for file in files:
                    if file.endswith('.dash'):
                        dashboard_files.append(os.path.join(root, file))
        else:
            dashboard_files.append(dashboard_path)

        for dashboard_file in dashboard_files:
            try:
                dashboards[dashboard_file] = read_dashboard_file(
                    dashboard_file
                )
            except configparser.Error as e:
                raise ValueError("dashboard file '%s' cannot be "
                                 "parsed: %s" % (dashboard_file, e))

    return dashboards


def main():
    """Entrypoint."""
    opts = get_options()

    template = None
    if not opts.check_only:
        template = load_template(
            template_file=opts.template_file,
            template_directory=opts.template_directory,
            template_name=opts.template
        )

    try:
        dashboards = load_dashboards(opts.dashboard_paths)
        if not opts.check_only and template:
            generate_dashboard_urls(dashboards, template)
        elif not opts.check_only and not template:
            return 1
    except ValueError as e:
        print("error: %s" % e)
        return 1

    return 0


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