#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import argparse
import os
import textwrap

import cspell
import check_xml
import check_xml_dependencies
import clang_tidy
import codingstyle
import common
import cppcheck
import filesize
import forbidtoken
import glslang_validator

hooks = {}
hooks.update(forbidtoken.hooks)
hooks.update(filesize.hooks)
hooks.update(check_xml.hooks)
hooks.update(check_xml_dependencies.hooks)
hooks.update(cppcheck.hooks)
hooks.update(glslang_validator.hooks)
hooks.update(cspell.hooks)
hooks.update(clang_tidy.hooks)

DEFAULT_HOOKS = 'crlf tab filesize codingstyle doxygen check_xml check_xml_dependencies check_glsl_version glslang_validator cspell'

parser = argparse.ArgumentParser(
    formatter_class=argparse.RawDescriptionHelpFormatter,
    description='Check and/or reformat code to comply to Sight coding guidelines.',
    epilog=textwrap.dedent('The script works on git staged/modified files or directly on file/directory:\n'
                           '\n'
                           '  For git mode, in three different ways depending on the number of paths:\n'
                           '    - If no path is specified, the current staged files are processed.\n'
                           '    - If 1 path is specified, the files modified in the specified path is processed.\n'
                           '    - If 2 paths are specified, the files modified between the two paths are processed.\n'
                           '\n'
                           '  For file/directory mode, using the --input argument:\n'
                           '    - If the argument is a file, only this file will be checked.\n'
                           '    - If the argument is a directory, Sheldon will recursively check all files within this directory.\n'
                           )
)

parser.add_argument('-f', '--format',
                    action="store_true",
                    help='Enable code reformatting.')

parser.add_argument('-v', '--verbose',
                    action="store_true",
                    help='Increase the verbosity level.')

parser.add_argument('--with-uncrustify',
                    action='store',
                    dest='uncrustify_path',
                    help='Use uncrustify from path.')

parser.add_argument('--with-cppcheck',
                    action='store',
                    dest='cppcheck_path',
                    help='Use cppcheck from path.')

parser.add_argument('--with-glslang-validator',
                    action='store',
                    dest='glslang_validator_path',
                    help='Use glslang from path.')

parser.add_argument('-i', '--input',
                    action='store',
                    dest='input_path',
                    help='Check the specific file/directory, staged or not. Recursive when the argument is a directory')

parser.add_argument('-c', '--commit',
                    action='store',
                    dest='commit_check',
                    help='Check all unpushed commit')

parser.add_argument('-o', '--config',
                    action='append',
                    dest='options',
                    help='Override configuration options in the KEY=VALUE format. May be specified multiple times.')

parser.add_argument('--hooks',
                    action='store',
                    dest='hooks',
                    help='The hooks to be run. Overrides the hooks specified in the config file. Available hooks: '
                         + ', '.join([hook_name for hook_name, _ in hooks.items()]))

parser.add_argument('path',
                    nargs='*',
                    help='Git path, can be a commit or two commits.')

args = parser.parse_args()

# Set global option from command line arguments
common.g_enable_reformat = args.format
common.g_trace = args.verbose
common.g_cppcheck_path_arg = args.cppcheck_path
common.g_uncrustify_path_arg = args.uncrustify_path
common.g_glslang_validator_path = args.glslang_validator_path

if args.options is not None:
    for option in args.options:
        splitted_option = option.split('=')
        if len(splitted_option) < 2:
            common.error('Invalid command line configuration option: "{}". It must be in the KEY=VALUE format.'.format(option))
            exit(1)
        common.g_options[splitted_option[0]] = '='.join(splitted_option[1:])

# Whether we will check file dates from commits date or from the local time
check_commits_date = True

if args.input_path is not None and len(args.input_path) > 0:
    # Double check path argument
    if args.path is not None and len(args.path) > 0:
        common.warn('--input is used, path argument will be ignored.')

    check_commits_date = False

    # Cleanup the path
    file_path = os.path.realpath(args.input_path)

    common.set_repo_root(file_path)

    if os.path.isdir(file_path):
        # Change the current working dir so we can correctly detect a 'Sight' repository structure
        os.chdir(file_path)

        # Execute hooks on files
        files = [f for f in common.directory_on_disk(file_path)]
    elif os.path.isfile(file_path):
        # Change the current working dir so we can correctly detect a 'Sight' repository structure
        dir_path = os.path.dirname(file_path)
        os.chdir(dir_path)

        # Execute hooks on files
        files = [f for f in common.file_on_disk(file_path)]
    else:
        common.error('Cannot find the input file/directory, exiting...')
        exit(0)
else:
    if len(args.path) > 2:
        print("Invalid git path")
        exit(1)
    else:
        common.set_repo_root(os.getcwd())

        if len(args.path) > 1:
            files = [f for f in common.files_in_rev(args.path[0], args.path[1])]
        elif len(args.path) > 0:
            files = [f for f in common.files_in_rev(args.path[0])]
        else:
            # "Pre-commit" mode, get the list of staged files
            files = [f for f in common.files_staged_for_commit(common.current_commit())]
            check_commits_date = False

print('\n' + '*' * 120)

if not files:
    common.note('No file(s) found, exiting...')
    exit(0)

# Filter files in case of .sheldonignore file
files = common.apply_sheldon_ignore(files)

common.note("Files to process :")
for f in files:
    common.note('- ' + f.path)
common.note('')

if args.hooks is not None:
    active_hooks = args.hooks.split(',')
else:
    active_hooks = common.get_option('fw4spl-hooks.hooks', default=None)
    if active_hooks is not None:
        common.warn('"fw4spl-hooks" section is deprecated, please use default hooks OR rename it "sight-hooks"')
    else:
        active_hooks = common.get_option('sight-hooks.hooks', default=DEFAULT_HOOKS)
    active_hooks = active_hooks.split()

common.note('Enabled hooks: ' + ', '.join(active_hooks))

print('\n' + '*' * 120)

results = [False]

reformatted = False

# check coding style
if 'codingstyle' in active_hooks:
    common.note("Beautifier phase :")
    codingstyle_result, reformatted_files = codingstyle.coding_style(files, common.g_enable_reformat, check_commits_date)
    results.append(codingstyle_result)

    print('\n' + '*' * 120)
else:
    # reformatted_files is used elsewhere..
    reformatted_files = []

common.note("Check phase :")
results += [f(files) for name, f in hooks.items() if name in active_hooks]

# Summarize results
result = any(results)

#  User report
print('\n' + '*' * 120)

if common.g_enable_reformat:
    if result:
        common.error("Check failed !!! Check error messages above.")
        if reformatted_files:
            print('\n')
            common.note("Meanwhile, some files have been reformatted :")
            for f in reformatted_files:
                common.note('- ' + f)
            common.note('Please review changes.')
        print('\n')
        common.error("Please fix the issues, stage modifications with 'git add' and run 'sheldon' again.")
    else:
        if reformatted_files:
            common.note("Check succeeded, but some files have been reformatted :")
            for f in reformatted_files:
                common.note('- ' + f)
            common.note('Please review and commit changes.')
        else:
            common.note(r"Check succeeded ! \o/ ")
else:
    if result or reformatted_files:
        common.error("Check failed !!! ")
        if reformatted_files:
            common.error('The following file(s) are not correctly formatted:')
            for f in reformatted_files:
                common.note('- ' + f)
        common.error("Please fix the issues, stage modifications with 'git add' and run 'sheldon' again.")

    else:
        common.note(r"Check succeeded ! \o/ ")

print('*' * 120)

exit(result)
