import json

from coalib.bearlib import deprecate_settings
from coalib.bearlib.abstractions.Linter import linter
from coalib.bears.requirements.GemRequirement import GemRequirement
from coalib.results.Result import Result
from coalib.results.SourceRange import SourceRange
from coala_utils.param_convertion import negate


@linter(executable='reek', use_stdin=True)
class RubySmellBear:
    """
    Detect code smells in Ruby source code.

    For more information about the detected smells, see
    <https://github.com/troessner/reek/blob/master/docs/Code-Smells.md>.
    """

    LANGUAGES = {"Ruby"}
    REQUIREMENTS = {GemRequirement('reek')}
    AUTHORS = {'The coala developers'}
    AUTHORS_EMAILS = {'coala-devel@googlegroups.com'}
    LICENSE = 'AGPL-3.0'
    CAN_DETECT = {'Smell'}

    @staticmethod
    def create_arguments(filename, file, config_file):
        return '--format', 'json', '-c', config_file

    def process_output(self, output, filename, file):
        output = json.loads(output) if output else ()
        for issue in output:
            sourceranges = []
            for line in issue['lines']:
                sourceranges.append(SourceRange.from_values(
                    file=filename, start_line=line))

            if 'name' in issue:
                message = "'{}' (in '{}') {}.".format(
                    issue['name'], issue['context'], issue['message'])
            else:
                message = "'{}' {}".format(issue['context'], issue['message'])

            yield Result(
                origin="{} ({})".format(self.__class__.__name__,
                                        issue['smell_type']),
                message=message,
                affected_code=sourceranges,
                additional_info="More information is available at {}"
                                ".".format(issue["wiki_link"]))

    @deprecate_settings(allow_duplicate_method=(
                            'duplicate_method_call', negate),
                        allow_data_clump=('data_clump', negate),
                        allow_control_parameters=('control_parameter', negate),
                        allow_class_variables=('class_variable', negate),
                        allow_boolean_parameter_in_functions=(
                            'boolean_parameter', negate),
                        allow_setter_in_classes=('attribute', negate),
                        allow_unused_private_methods=(
                            'unused_private_method', negate),
                        allow_unused_variables=('unused_params', negate))
    def generate_config(self,
                        allow_setter_in_classes: bool=False,
                        allow_boolean_parameter_in_functions: bool=False,
                        allow_class_variables: bool=False,
                        allow_control_parameters: bool=False,
                        allow_data_clump: bool=False,
                        allow_duplicate_method: bool=False,
                        feature_envy: bool=True,
                        missing_module_description: bool=True,
                        long_param_list: bool=True,
                        long_yield_list: bool=True,
                        module_initialize: bool=True,
                        nested_iterators: bool=True,
                        nil_check: bool=True,
                        prima_donna_method: bool=True,
                        repeated_conditional: bool=True,
                        too_many_instance_variables: bool=True,
                        too_many_methods: bool=True,
                        too_long_method: bool=True,
                        bad_method_name: bool=True,
                        bad_module_name: bool=True,
                        bad_param_name: bool=True,
                        bad_var_name: bool=True,
                        allow_unused_variables: bool=False,
                        allow_unused_private_methods: bool=True,
                        utility_function: bool=True):
        """
        :param allow_setter_in_classes:
            Allows setter in classes.
        :param allow_boolean_parameter_in_functions:
            Allows boolean parameter in functions (control coupling).
        :param allow_class_variables:
            Allows class variables.
        :param allow_control_parameters:
            Allows parameters that control function behaviour (control
            coupling).
        :param allow_data_clump:
            Does not warn when the same two or three items frequently appear
            together in function/class parameter list.
        :param allow_duplicate_method:
            Allows having two fragments of code that look nearly identical, or
            two fragments of code that have nearly identical effects at some
            conceptual level.
        :param feature_envy:
            Occurs when a code fragment references another object more often
            than it references itself, or when several clients do the same
            series of manipulations on a particular type of object.
        :param missing_module_description:
            Warns if a module description is missing.
        :param long_param_list:
            Warns about too many parameters of functions.
        :param long_yield_list:
            Warns when a method yields a lot of arguments to the block it gets
            passed.
        :param module_initialize:
            Warns about ``#initialize`` methods in modules.
        :param nested_iterators:
            Warns when a block contains another block.
        :param nil_check:
            Warns about nil checks.
        :param prima_donna_method:
            Warns about methods whose names end with an exclamation mark.
        :param repeated_conditional:
            Warns about repeated conditionals.
        :param too_many_instance_variables:
            Warns for too many instance variables.
        :param too_many_methods:
            Warns if a class has too many methods.
        :param too_long_method:
            Warns about huge methods.
        :param bad_method_name:
            Warns about method names which are not communicating the purpose
            of the method well.
        :param bad_module_name:
            Warns about module names which are not communicating the purpose
            of the module well.
        :param bad_param_name:
            Warns about parameter names which are not communicating the purpose
            of the parameter well.
        :param bad_var_name:
            Warns about variable names which are not communicating the purpose
            of the variable well.
        :param allow_unused_variables:
            Allows unused parameters though they are dead code.
        :param check_unused_private_methods:
            Warns about unused private methods, as they are dead code.
        :param utility_function:
            Allows any instance method that has no dependency on the state of
            the instance.
        """
        config = {
            "Attribute": not allow_setter_in_classes,
            "BooleanParameter": not allow_boolean_parameter_in_functions,
            "ClassVariable": not allow_class_variables,
            "ControlParameter": not allow_control_parameters,
            "DataClump": not allow_data_clump,
            "DuplicateMethodCall": not allow_duplicate_method,
            "FeatureEnvy": feature_envy,
            "IrresponsibleModule": missing_module_description,
            "LongParameterList": long_param_list,
            "LongYieldList": long_yield_list,
            "ModuleInitialize": module_initialize,
            "NestedIterators": nested_iterators,
            "NilCheck": nil_check,
            "PrimaDonnaMethod": prima_donna_method,
            "RepeatedConditional": repeated_conditional,
            "TooManyInstanceVariables": too_many_instance_variables,
            "TooManyMethods": too_many_methods,
            "TooManyStatements": too_long_method,
            "UncommunicativeMethodName": bad_method_name,
            "UncommunicativeModuleName": bad_module_name,
            "UncommunicativeParameterName": bad_param_name,
            "UncommunicativeVariableName": bad_var_name,
            "UnusedParameters": not allow_unused_variables,
            "UnusedPrivateMethod": not allow_unused_private_methods,
            "UtilityFunction": utility_function}

        return ('---\n' +
                '\n'.join('{}:\n  enabled: {}'.format(key, str(value).lower())
                          for key, value in config.items()))
