# -*- coding: utf-8 -*-
# Copyright (c) 2022, Compiler Explorer Authors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#     * Redistributions of source code must retain the above copyright notice,
#       this list of conditions and the following disclaimer.
#     * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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.
import sys
from os import listdir
from os.path import isfile, join
import re

COMPILERS_LIST_RE = re.compile(r'compilers=(.*)')
ALIAS_LIST_RE = re.compile(r'alias=(.*)')
GROUP_NAME_RE = re.compile(r'group\.(.*?)\.')
COMPILER_EXE_RE = re.compile(r'compiler\.(.*?)\.exe')
DEFAULT_COMPILER_RE = re.compile(r'defaultCompiler=(.*)')
FORMATTERS_LIST_RE = re.compile(r'formatters=(.*)')
FORMATTER_EXE_RE = re.compile(r'formatter\.(.*?)\.exe')
LIBS_LIST_RE = re.compile(r'libs=(.+)')
LIB_VERSIONS_LIST_RE = re.compile(r'libs\.(.*?)\.versions=(.*)')
LIB_VERSION_RE = re.compile(r'libs\.(.*?)\.versions\.(.*?)\.version')
TOOLS_LIST_RE = re.compile(r'tools=(.+)')
TOOL_EXE_RE = re.compile(r'tools\.(.*?)\.exe')
EMPTY_LIST_RE = re.compile(r'.*(compilers|formatters|versions|tools|alias|exclude)=.*::.*')
DISABLED_RE = re.compile(r'^# Disabled:\s*(.*)')


def match_and_add(line, expr, s):
    match = expr.match(line)
    if match:
        s.add(match.group(1))
    return match


def match_and_update(line, expr, s, split=':'):
    match = expr.match(line)
    if match:
        s.update(match.group(1).split(split))
    return match


def process_file(file: str):
    default_compiler = set()
    listed_groups = set()
    seen_groups = set()
    listed_compilers = set()
    seen_compilers = set()
    listed_formatters = set()
    seen_formatters = set()
    listed_tools = set()
    seen_tools = set()
    listed_libs_ids = set()
    seen_libs_ids = set()
    listed_libs_versions = set()
    seen_libs_versions = set()
    empty_separators = set()
    disabled = set()
    with open(file) as f:
        for line in f:
            match_empty = EMPTY_LIST_RE.match(line)
            if match_empty:
                empty_separators.add(f"{line}")

            match_compilers = COMPILERS_LIST_RE.search(line)
            if match_compilers:
                ids = match_compilers.group(1).split(':')
                for elem_id in ids:
                    if elem_id.startswith('&'):
                        listed_groups.add(elem_id[1:])
                    elif '@' not in elem_id:
                        listed_compilers.add(elem_id)

            match_libs_versions = LIB_VERSIONS_LIST_RE.match(line)
            if match_libs_versions:
                lib_id = match_libs_versions.group(1)
                versions = match_libs_versions.group(2).split(':')
                seen_libs_ids.add(lib_id)
                listed_libs_versions.update([f"{lib_id} {v}" for v in versions])

            match_libs_version = LIB_VERSION_RE.match(line)
            if match_libs_version:
                lib_id = match_libs_version.group(1)
                version = match_libs_version.group(2)
                seen_libs_versions.add(f"{lib_id} {version}")

            if(
                    match_and_add(line, DEFAULT_COMPILER_RE, default_compiler) or
                    match_and_add(line, GROUP_NAME_RE, seen_groups) or
                    match_and_add(line, COMPILER_EXE_RE, seen_compilers) or
                    match_and_add(line, FORMATTER_EXE_RE, seen_formatters) or
                    match_and_add(line, TOOL_EXE_RE, seen_tools) or
                    match_and_update(line, ALIAS_LIST_RE, seen_compilers) or
                    match_and_update(line, FORMATTERS_LIST_RE, listed_formatters) or
                    match_and_update(line, TOOLS_LIST_RE, listed_tools) or
                    match_and_update(line, LIBS_LIST_RE, listed_libs_ids) or
                    match_and_update(line, DISABLED_RE, disabled, ' ')
            ):
                continue
    bad_compilers = listed_compilers.symmetric_difference(seen_compilers)
    bad_groups = listed_groups.symmetric_difference(seen_groups)
    bad_formatters = listed_formatters.symmetric_difference(seen_formatters)
    bad_libs_ids = listed_libs_ids.symmetric_difference(seen_libs_ids)
    bad_libs_versions = listed_libs_versions.symmetric_difference(seen_libs_versions)
    bad_tools = listed_tools.symmetric_difference(seen_tools)
    bad_default = default_compiler - listed_compilers
    return (file,
            bad_compilers - disabled,
            bad_groups - disabled,
            bad_formatters - disabled,
            bad_libs_ids - disabled,
            bad_libs_versions - disabled,
            bad_tools - disabled,
            bad_default,
            empty_separators)


def process_folder(folder: str):
    return [process_file(join(folder, f))
            for f in listdir(folder)
            if isfile(join(folder, f))
            and not (f.endswith('.defaults.properties') or f.endswith('.local.properties'))
            and f.endswith('.properties')]


def problems_found(file_result):
    return any([len(r) for r in file_result[1:]])


def print_issue(name, result):
    if len(result) > 0:
        sep = "\n\t"
        print(f"{name}:\n\t{sep.join(sorted(result))}")


def find_orphans(folder: str):
    result = sorted([r for r in process_folder(folder) if problems_found(r)], key=lambda x: x[0])
    if result:
        print(f"Found {len(result)} property file(s) with mismatching ids:")
        for r in result:
            print(r[0])
            print_issue("COMPILERS", r[1])
            print_issue("GROUPS", r[2])
            print_issue("FORMATTERS", r[3])
            print_issue("LIB IDS", r[4])
            print_issue("LIB VERSIONS", r[5])
            print_issue("TOOLS", r[6])
            print_issue("UNKNOWN DEFAULT COMPILER", r[7])
            print_issue("EMPTY LISTINGS", r[8])
            print("")
        print("To suppress this warning on IDs that are temporally disabled, "
              "add one or more comments to each listed file:")
        print("# Disabled: id1 id2 ...")
    else:
        print("No configuration mismatches found")
    return result


if __name__ == '__main__':
    if find_orphans('./etc/config/'):
        sys.exit(1)
