# Copyright 2012-2015 Eucalyptus Systems, Inc.
#
# Redistribution and use of this software 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
# OWNER 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 socket

from requestbuilder import Arg, MutuallyExclusiveArgList
from requestbuilder.exceptions import ArgumentError

from euca2ools.commands.ec2 import EC2Request, parse_ports


class _ModifySecurityGroupRule(EC2Request):
    """
    The basis for security group-editing commands
    """

    ARGS = [Arg('group', metavar='GROUP', route_to=None,
                help='name or ID of the security group to modify (required)'),
            Arg('--egress', action='store_true', route_to=None,
                help='''[VPC only] manage an egress rule, which controls
                traffic leaving the group'''),
            Arg('-P', '--protocol', dest='IpPermissions.1.IpProtocol',
                metavar='PROTOCOL', default='tcp',
                help='''the protocol to affect (Non-VPC: tcp, udp, icmp)
                (VPC only: tcp, udp, icmp, -1/all, other protocol numbers)
                (default: tcp)'''),
            Arg('-p', '--port-range', dest='port_range', metavar='RANGE',
                route_to=None, help='''range of ports (specified as "from-to")
                or a single port number (required for tcp and udp)'''),
            Arg('-t', '--icmp-type-code', dest='icmp_type_code',
                metavar='TYPE:CODE', route_to=None, help='''ICMP type and
                code (specified as "type:code") (required for icmp)'''),
            MutuallyExclusiveArgList(
                Arg('-s', '--cidr', metavar='CIDR',
                    dest='IpPermissions.1.IpRanges.1.CidrIp',
                    help='''IP range (default: 0.0.0.0/0)'''),
                # ^ default is added by main()
                Arg('-o', dest='target_group', metavar='GROUP', route_to=None,
                    help='''[Non-VPC only] name of a security group with which
                    to affect network communication''')),
            Arg('-u', metavar='ACCOUNT',
                dest='IpPermissions.1.Groups.1.UserId',
                help='''ID of the account that owns the security group
                specified with -o''')]

    def process_cli_args(self):
        self.process_port_cli_args()

    # noinspection PyExceptionInherit
    def configure(self):
        EC2Request.configure(self)

        if self.args['group'].startswith('sg-'):
            # The check could probably be a little better, but meh.  Fix if
            # needed.
            self.params['GroupId'] = self.args['group']
        else:
            if self.args['egress']:
                raise ArgumentError('egress rules must use group IDs, not '
                                    'names')
            self.params['GroupName'] = self.args['group']

        target_group = self.args.get('target_group')
        if target_group is not None:
            if target_group.startswith('sg-'):
                # Same note as above
                self.params['IpPermissions.1.Groups.1.GroupId'] = target_group
            else:
                if self.args['egress']:
                    raise ArgumentError('argument -o: egress rules must use '
                                        'group IDs, not names')
                self.params['IpPermissions.1.Groups.1.GroupName'] = target_group

        protocol = self.args.get('IpPermissions.1.IpProtocol')
        if str(protocol).lower() in ('icmp', 'tcp', 'udp', '1', '6', '17'):
            from_port, to_port = parse_ports(
                protocol, self.args.get('port_range'),
                self.args.get('icmp_type_code'))
            self.params['IpPermissions.1.FromPort'] = from_port
            self.params['IpPermissions.1.ToPort'] = to_port
        elif str(protocol).lower() in ('all', '-1'):
            self.params['IpPermissions.1.IpProtocol'] = -1
        elif not str(protocol).isdigit():
            try:
                self.params['IpPermissions.1.IpProtocol'] = \
                    socket.getprotobyname(protocol)
            except socket.error:
                raise ArgumentError('argument -P: no such protocol: {0}'
                                    .format(protocol))

        if (not self.args.get('IpPermissions.1.IpRanges.1.GroupName') and
                not self.args.get('IpPermissions.1.IpRanges.1.CidrIp')):
            # Default rule target is the entire Internet
            self.params['IpPermissions.1.IpRanges.1.CidrIp'] = '0.0.0.0/0'
        if (self.params.get('IpPermissions.1.Groups.1.GroupName') and
                not self.args.get('IpPermissions.1.Groups.1.UserId')):
            raise ArgumentError('argument -u is required when -o names a '
                                'security group by name')

    def print_result(self, _):
        print self.tabify(['GROUP', self.args.get('group')])
        perm_str = ['PERMISSION', self.args.get('group'), 'ALLOWS',
                    self.params.get('IpPermissions.1.IpProtocol'),
                    self.params.get('IpPermissions.1.FromPort'),
                    self.params.get('IpPermissions.1.ToPort')]
        if self.params.get('IpPermissions.1.Groups.1.UserId'):
            perm_str.append('USER')
            perm_str.append(self.params.get('IpPermissions.1.Groups.1.UserId'))
        if self.params.get('IpPermissions.1.Groups.1.GroupName'):
            perm_str.append('NAME')
            perm_str.append(self.params.get(
                'IpPermissions.1.Groups.1.GroupName'))
        if self.params.get('IpPermissions.1.Groups.1.GroupId'):
            perm_str.append('ID')
            perm_str.append(self.params.get(
                'IpPermissions.1.Groups.1.GroupId'))
        if self.params.get('IpPermissions.1.IpRanges.1.CidrIp'):
            perm_str.extend(['FROM', 'CIDR'])
            perm_str.append(self.params.get(
                'IpPermissions.1.IpRanges.1.CidrIp'))
        print self.tabify(perm_str)


class AuthorizeSecurityGroupRule(_ModifySecurityGroupRule):
    DESCRIPTION = 'Add a rule to a security group that allows traffic to pass'

    @property
    def action(self):
        if self.args['egress']:
            return 'AuthorizeSecurityGroupEgress'
        else:
            return 'AuthorizeSecurityGroupIngress'


class RevokeSecurityGroupRule(_ModifySecurityGroupRule):
    DESCRIPTION = 'Remove a rule from a security group'

    @property
    def action(self):
        if self.args['egress']:
            return 'RevokeSecurityGroupEgress'
        else:
            return 'RevokeSecurityGroupIngress'
