#!/usr/bin/python
#
# Copyright 2009-2013 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the
# OpenSSL library under certain conditions as described in each
# individual source file, and distribute linked combinations
# including the two.
# You must obey the GNU General Public License in all respects
# for all of the code used other than OpenSSL.  If you modify
# file(s) with this exception, you may extend this exception to your
# version of the file(s), but you are not obligated to do so.  If you
# do not wish to do so, delete this exception statement from your
# version.  If you delete this exception statement from all source
# files in the program, then also delete it here.
"""Wrapper script for pyflakes command."""

from __future__ import print_function

import os
import subprocess
import sys


SRCDIR = os.environ.get('SRCDIR', os.getcwd())

# Define a dummy WindowsError class to keep pyflakes happy on !Windows
# In the future we should remove this, when pyflakes handles platform-specific
# code more correctly by ignoring some warnings while on other platforms.
if sys.platform != 'win32':
    class WindowsError(OSError):
        """Dummy WindowsError wrapper to make pyflakes happy."""


class InvalidSetupException(Exception):
    """Raised when the env is not correctly setup."""


def find_python_installation_path():
    """Return the path where python was installed."""
    assert(sys.platform == 'win32')
    # To get the correct path of the script we need the installation path
    # of python. To get the installation path we first check on the path,
    # then read the registry.

    for path in os.getenv("Path", "").split(";"):
        if os.path.exists(os.path.join(path, "python.exe")):
            return path

    try:
        import winreg
    except ImportError:
        import _winreg as winreg
    software_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, 'Software')
    python_key = None
    try:
        python_key = winreg.OpenKey(software_key, 'Python')
    except WindowsError:
        try:
            # look in the WoW6432node, we are running python
            # 32 on a 64 machine
            wow6432node_key = winreg.OpenKey(software_key, 'WoW6432Node')
            python_key = winreg.OpenKey(wow6432node_key, 'Python')
        except WindowsError:
            raise InvalidSetupException(
                'Could not located python installation path.')
    try:
        core_key = winreg.OpenKey(python_key, 'PythonCore')
        version_key = winreg.OpenKey(core_key, sys.winver)
        return winreg.QueryValue(version_key, 'InstallPath')
    except WindowsError:
        raise InvalidSetupException(
            'Could not located python installation path.')


def find_script_path(script, python_path=None):
    """Return the path of the given script to be executed by subprocess."""
    if sys.platform == 'win32':
        if python_path is None:
            python_path = find_python_installation_path()
        # In a buildout the scripts go next to python.exe, no Scripts folder.
        if os.path.exists(os.path.join(python_path, script)):
            return os.path.join(python_path, script)
        else:
            return os.path.join(python_path, 'Scripts', script)
    else:
        # the default is to return the name of the script beacuse we expect it
        # to be executable and in the path.
        return script


def get_subprocess_start_info(script):
    """Return the basic info used by subprocess to start a script."""
    if sys.platform == 'win32':
        # the basic setup in windows is not to have python in the path and not
        # to have .py assigned to be ran with python, therefore we assume this
        # scenario
        python_path = find_python_installation_path()
        return [os.path.join(python_path, 'python.exe'),
                find_script_path(script, python_path)]
    else:
        # the default is to assume that the script is executable and that it
        # can be found in the path
        return [script, ]


def _group_lines_by_file(data):
    """Format file:line:message output as lines grouped by file."""
    did_fail = False
    outputs = []
    filename = ""
    for line in data.splitlines():
        current = line.split(":", 3)
        if line.startswith("    "):
            outputs.append("    " + current[0] + "")
        elif line.startswith("build/") or len(current) < 3:
            pass
        elif filename == current[0]:
            if not "[W0511]" in current[2]:
                did_fail = True
            outputs.append("    " + current[1] + ": " + current[2])
        elif filename != current[0]:
            filename = current[0]
            outputs.append("")
            outputs.append(filename + ":")
            if not "[W0511]" in current[2]:
                did_fail = True
            outputs.append("    " + current[1] + ": " + current[2])

    return (did_fail, "\n".join(outputs))


def _find_files():
    """Find all Python files under the current tree."""
    pyfiles = []
    for root, dirs, files in os.walk(SRCDIR, topdown=False):
        for filename in files:
            filepath = root + os.path.sep

            # Skip files in build/
            builddir = os.path.join(SRCDIR, 'build') + os.path.sep
            if filepath.startswith(builddir):
                continue

            # Skip protobuf-generated and backup files
            if filename.endswith("_pb2.py") \
                    or filename.endswith("~") \
                    or filename.endswith(".bat"):
                continue

            if filename.endswith(".py") \
                    or filepath.endswith("bin" + os.path.sep):
                pyfiles.append(os.path.join(root, filename))

    pyfiles.sort()
    return pyfiles


def main(options=None, args=None):
    """Do the deed."""
    from optparse import OptionParser
    usage = '%prog [options]'
    parser = OptionParser(usage=usage)
    parser.add_option('-i', '--ignore', dest='ignored', default=None,
                      help='comma-separated paths or files, to ignore')
    (options, args) = parser.parse_args()

    failed = False
    ignored = []
    if options.ignored:
        ignored.extend([os.path.join(SRCDIR, item) for item in
                        map(str.strip, options.ignored.split(','))])

    pylint_args = get_subprocess_start_info('pyflakes')

    for path in _find_files():
        is_build = path.startswith(os.path.join(SRCDIR, "_build"))
        is_ignored = False
        if path in ignored:
            continue
        for ignored_path in ignored:
            if path.startswith(ignored_path):
                is_ignored = True
                break
        if is_build or is_ignored:
            continue
        pylint_args.append(path)

    sp = subprocess.Popen(pylint_args,
                          bufsize=4096, stdout=subprocess.PIPE)
    notices = sp.stdout

    output = "".join(notices.readlines())
    if output != "":
        print("== Python Lint Notices ==")
        (failed, grouped) = _group_lines_by_file(output)
        print(grouped, end="\n\n")

    returncode = sp.wait()

    if failed:
        if returncode != 0:
            exit(returncode)
        else:
            exit(1)


if __name__ == '__main__':
    main()
