import platform
import shlex

from coalib.bearlib import deprecate_settings
from coalib.bearlib.abstractions.Linter import linter
from coalib.bearlib.spacing.SpacingHelper import SpacingHelper
from coalib.bears.requirements.DistributionRequirement import (
    DistributionRequirement)


@linter(executable="indent" if platform.system() != "Darwin" else "gindent",
        use_stdin=True,
        output_format='corrected',
        result_message="Indentation can be improved.")
class GNUIndentBear:
    """
    This bear checks and corrects spacing and indentation via the well known
    Indent utility.

    C++ support is considered experimental.
    """

    LANGUAGES = {"C", "C++"}
    REQUIREMENTS = {DistributionRequirement(apt='indent')}
    AUTHORS = {'The coala developers'}
    AUTHORS_EMAILS = {'coala-devel@googlegroups.com'}
    LICENSE = 'AGPL-3.0'
    CAN_FIX = {'Formatting'}

    @staticmethod
    @deprecate_settings(indent_size='tab_width')
    def create_arguments(filename, file, config_file,
                         max_line_length: int=79,
                         use_spaces: bool=True,
                         blank_lines_after_declarations: bool=False,
                         blank_lines_after_procedures: bool=False,
                         blank_lines_after_commas: bool=False,
                         braces_on_if_line: bool=False,
                         braces_on_func_def_line: bool=False,
                         cuddle_else: bool=False,
                         while_and_brace_on_same_line: bool=False,
                         case_indentation: int=0,
                         space_before_semicolon_after_empty_loop: bool=True,
                         delete_optional_blank_lines: bool=True,
                         declaration_indent: int=0,
                         brace_indent: int=2,
                         gnu_style: bool=False,
                         k_and_r_style: bool=False,
                         linux_style: bool=False,
                         indent_size: int=SpacingHelper.DEFAULT_TAB_WIDTH,
                         indent_cli_options: str=''):
        """
        :param max_line_length:
            Maximum number of characters for a line.
        :param use_spaces:
            True if spaces are to be used, else tabs.
        :param blank_lines_after_declarations:
            Forces blank lines after the declarations.

            Example:
                If ``blank_lines_after_declarations = True`` then
                ```
                int a;
                return ...;
                ```
                changes to
                ```
                int a;

                return ...;
                ```

        :param blank_lines_after_procedures:
            Force blank lines after procedure bodies.
        :param blank_lines_after_commas:
            Forces newline after comma in declaration.

            Example:
                If ``blank_lines_after_commas = True`` then
                ```
                int a, b;
                ```
                changes to
                ```
                int a,
                  b;
                ```
        :param braces_on_if_line:
            Puts the brace ``{`` on same line with if.

            Example:
                If ``braces_on_if_line = True``  then
                ```
                if (x > 0)
                {
                ```
                changes to
                ```
                if (x > 0) {
                ```
        :param braces_on_func_def_line:
            Puts the brace `{` on same line with the function declaration.
        :param cuddle_else:
            Cuddle else and preceding ``}``.

            Example:
                If ``cuddle_else = True`` then
                ```
                if (...) {
                    ....
                }
                else {
                ```
                changes to
                ```
                if (...) {
                    ....
                } else {
                ```

        :param while_and_brace_on_same_line:
            Cuddles while of ``do {} while``; and preceding ``}``.
        :param case_indentation:
            Specifies the number of spaces by which ``case`` in the ``switch``
            are indented.
        :param space_before_semicolon_after_empty_loop:
            Forces a blank before the semicolon ``;`` on one-line ``for`` and
            ``while`` statements.
        :param delete_optional_blank_lines:
             Deletes blank lines that are not needed. An example of needed
             blank line, is the blank line following a declaration when
             ``blank_line_after_declaration=True``.
        :param declaration_indent:
            Forces variables names to be aligned in column ``n`` with
            ``n = declaration_indent``  in declaration.

            Example:
                If ``declaration_indent = 8`` then,
                ```
                int a;
                float b;
                ```
                changes to
                ```
                int     a;
                float   b;
                ```
        :param brace_indent:
            Specifies the number of spaces by which braces are indented. Its
            default value is 2.
        :param gnu_style:
            Uses GNU coding style.
        :param k_and_r_style:
            Uses Kernighan & Ritchie coding style.
        :param linux_style:
            Uses Linux coding style.
        :param indent_size:
            Number of spaces per indentation level.
        :param indent_cli_options:
            Any command line options the indent binary understands. They
            will be simply passed through.
        """
        indent_options = ("--no-tabs" if use_spaces else "--use-tabs",
                          "--line-length", str(max_line_length),
                          "--indent-level", str(indent_size),
                          "--tab-size", str(indent_size), )
        indent_options += (("--cuddle-do-while",)
                           if while_and_brace_on_same_line
                           else ("--dont-cuddle-do-while",))
        indent_options += (("--swallow-optional-blank-lines",)
                           if delete_optional_blank_lines else ("-nsob",))
        indent_options += (("--blank-lines-after-declarations",)
                           if blank_lines_after_declarations else ("-nbad",))
        indent_options += (("--blank-lines-after-commas",)
                           if blank_lines_after_commas else ("-nbc",))
        indent_options += (("--blank-lines-after-procedures",)
                           if blank_lines_after_procedures else ("-nbap",))
        indent_options += (("-di" + str(declaration_indent),)
                           if declaration_indent != 0 else ())
        indent_options += (("--case-indentation"+str(case_indentation),)
                           if case_indentation != 0 else ())
        indent_options += (("--space-special-semicolon",)
                           if space_before_semicolon_after_empty_loop
                           else ("-nss",))
        indent_options += ("--brace-indent"+str(brace_indent),)
        indent_options += (("--braces-on-func-def-line",)
                           if braces_on_func_def_line else ("-blf",))
        indent_options += ((("-ce",) if cuddle_else else ("-nce",)) +
                           ("-br",)) if braces_on_if_line else ("-bl",)
        indent_style_option = ()
        indent_style_option += ("--gnu-style",) if gnu_style else ()
        indent_style_option += (("--k-and-r-style",)
                                if k_and_r_style and indent_style_option is ()
                                else ())
        indent_style_option += (("--linux-style",)
                                if linux_style and indent_style_option is ()
                                else ())
        # If a style is chosen the other configs aren't passed to `indent`
        return (indent_style_option if indent_style_option is not ()
                else indent_options) + tuple(shlex.split(indent_cli_options))
