"""
IPTables configuration
======================

Module for processing output of the ``iptables-save`` and ``ip6tables-save``
commands.  Parsers included are:

IPTables - command ``iptables-save``
------------------------------------

IP6Tables - command ``ip6tables-save``
--------------------------------------

IPTabPermanent - file ``/etc/sysconfig/iptables``
-------------------------------------------------

IP6TabPermanent - file ``/etc/sysconfig/ip6tables``
---------------------------------------------------

Sample input data looks like::

    # Generated by iptables-save v1.4.7 on Tue Aug 16 10:18:43 2016
    *filter
    :INPUT ACCEPT [0:0]
    :FORWARD ACCEPT [0:0]
    :OUTPUT ACCEPT [769:196899]
    :REJECT-LOG - [0:0]
    -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
    -A INPUT -s 192.168.0.0/24 -j ACCEPT
    -A INPUT -p icmp -j ACCEPT
    -A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
    -A INPUT -j REJECT --reject-with icmp-host-prohibited
    -A REJECT-LOG -p tcp -j REJECT --reject-with tcp-reset
    COMMIT
    # Completed on Tue Aug 16 10:18:43 2016
    # Generated by iptables-save v1.4.7 on Tue Aug 16 10:18:43 2016
    *mangle
    :PREROUTING ACCEPT [451:22060]
    :INPUT ACCEPT [451:22060]
    :FORWARD ACCEPT [0:0]
    :OUTPUT ACCEPT [594:47151]
    :POSTROUTING ACCEPT [594:47151]
    COMMIT
    # Completed on Tue Aug 16 10:18:43 2016
    # Generated by iptables-save v1.4.7 on Tue Aug 16 10:18:43 2016
    *nat
    :PREROUTING ACCEPT [0:0]
    :POSTROUTING ACCEPT [3:450]
    :OUTPUT ACCEPT [3:450]
    COMMIT
    # Completed on Tue Aug 16 10:18:43 2016

* Each table of iptables starts with a ``# Generated by ...`` line.
* Each table starts with ``*<table-name>``, for example ``*filter``.
* Each chain specifications starts with a ``:`` sign.
* A chain specification looks like ``:<chain-name> <chain-policy> [<packet-counter>:<byte-counter>]``
* The chain-name may be for example ``INPUT``.
* Each ``iptables`` rule starts with a `-` sign.

Examples:

    >>> ipt.rules[0] == {'target': 'ACCEPT', 'chain': 'INPUT', 'rule': '-m state --state RELATED,ESTABLISHED -j ACCEPT', 'table': 'filter', 'target_options': None, 'target_action': 'jump', 'constraints': '-m state --state RELATED,ESTABLISHED'}
    True
    >>> ipt.get_chain('INPUT')[1] == {'target': 'ACCEPT', 'chain': 'INPUT', 'rule': '-s 192.168.0.0/24 -j ACCEPT', 'table': 'filter', 'target_options': None, 'target_action': 'jump', 'constraints': '-s 192.168.0.0/24'}
    True
    >>> ipt.table_chains('mangle') == {'FORWARD': [], 'INPUT': [], 'POSTROUTING': [], 'PREROUTING': [], 'OUTPUT': []}
    True
    >>> ipt.get_table('nat')[-1] == {'policy': 'ACCEPT', 'table': 'nat', 'byte_counter': 450, 'name': 'OUTPUT', 'packet_counter': 3}
    True
"""

from .. import Parser, parser, get_active_lines, CommandParser
from insights.specs import Specs


class IPTablesConfiguration(Parser):
    """
    A general class for parsing iptables configuration in the
    ``iptables-save``-like format.
    """
    def parse_content(self, content):
        self.chains = []
        self.rules = []
        current_table = None
        for line in get_active_lines(content):
            if line.startswith("*"):
                current_table = line[1:].strip()
            elif line.startswith(":"):
                name, policy, counter = line[1:].split()
                packet_counter, byte_counter = counter.strip("[]").split(":")
                self.chains.append({
                    "policy": policy if policy != "-" else None,
                    "table": current_table,
                    "name": name,
                    "packet_counter": int(packet_counter),
                    "byte_counter": int(byte_counter),
                })
            elif line.startswith("-"):
                line_spl = line[3:].split(None, 1)
                if not line_spl:
                    continue
                chain_name = line_spl[0]
                rule = line_spl[1] if len(line_spl) == 2 else ''
                target_option = [i for i in (' -j', '-j ', ' -g', '-g ') if i in rule]
                if target_option:
                    constraints, target = [i.strip() for i in rule.split(target_option[-1])]
                    if " " in target:
                        target, target_options = target.split(None, 1)
                    else:
                        target_options = None
                    self.rules.append({
                        "table": current_table,
                        "chain": chain_name,
                        "rule": rule,
                        "target_action": "jump" if target_option[-1].strip() == "-j" else "goto",
                        "constraints": constraints,
                        "target": target,
                        "target_options": target_options
                    })
                else:
                    self.rules.append({
                        "table": current_table,
                        "chain": chain_name,
                        "rule": rule
                    })

    def get_chain(self, name, table="filter"):
        """
        Get the list of rules for a particular chain. Chain order is kept intact.

        Args:
            name (str): chain name, e.g. ``
            table (str): table name, defaults to ``filter``

        Returns:
            list: rules
        """
        return [r for r in self.rules if r["table"] == table and r["chain"] == name]

    def get_table(self, name="filter"):
        """
        Get the list of chains for a particular table.

        Args:
            name (str): table name, defaults to ``filter``

        Returns:
            list: chains
        """
        return [c for c in self.chains if c["table"] == name]

    def table_chains(self, table="filter"):
        """
        Get a dict where the keys are all the chains for the given table
        and each value is the set of rules defined for the given chain.

        Args:
            table (str): table name, defaults to ``filter``

        Returns:
            dict: chains with set of defined rules
        """
        return dict((c["name"], self.get_chain(c["name"], table)) for c in self.get_table(table))

    def get_rule(self, s):
        """
        Get the list of rules that contain the given string.

        Args:
            s (str): string to look for in iptables rules

        Returns:
            list: rules containing given string
        """
        return [r for r in self.rules if s in r["rule"]]

    def __contains__(self, s):
        return any(s in r["rule"] for r in self.rules)


@parser(Specs.iptables)
class IPTables(CommandParser, IPTablesConfiguration):
    """
    Process output of the ``iptables-save`` command.

    See the :py:class:`insights.parsers.iptables.IPTablesConfiguration` base
    class for additional information.
    """
    pass


@parser(Specs.ip6tables)
class IP6Tables(CommandParser, IPTablesConfiguration):
    """
    Process output of the ``ip6tables-save`` command.

    See the :py:class:`insights.parsers.iptables.IPTablesConfiguration` base
    class for additional information.
    """
    pass


@parser(Specs.iptables_permanent)
class IPTabPermanent(IPTablesConfiguration):
    """
    Process ``iptables`` configuration saved in file ``/etc/sysconfig/iptables``.

    The configuration in this file is loaded by the ``iptables`` service when the system boots.
    New configuration is saved by using the ``service iptables save`` command. This configuration
    file is not available on a system with ``firewalld`` service.

    See the :py:class:`insights.parsers.iptables.IPTablesConfiguration` base
    class for additional information.
    """
    pass


@parser(Specs.ip6tables_permanent)
class IP6TabPermanent(IPTablesConfiguration):
    """
    Process ``ip6tables`` configuration saved in file ``/etc/sysconfig/ip6tables``.

    The configuration in this file is loaded by the ``ip6tables`` service when the system boots.
    New configuration is saved by using the ``service ip6tables save`` command. This configuration
    file is not available on a system with ``firewalld`` service.

    See the :py:class:`insights.parsers.iptables.IPTablesConfiguration` base
    class for additional information.
    """
    pass
