# Copyright (c) 2013-2019 Philip Hane
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
# 2. 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.

# CLI python script interface for ipwhois.utils lookups.

import argparse
from collections import OrderedDict
import json
from ipwhois.utils import (ipv4_lstrip_zeros, calculate_cidr, get_countries,
                           ipv4_is_defined, ipv6_is_defined, unique_everseen,
                           unique_addresses)

# CLI ANSI rendering
ANSI = {
    'end': '\033[0m',
    'b': '\033[1m',
    'ul': '\033[4m',
    'red': '\033[31m',
    'green': '\033[32m',
    'yellow': '\033[33m',
    'cyan': '\033[36m'
}

# Setup the arg parser.
parser = argparse.ArgumentParser(
    description='ipwhois utilities CLI interface'
)
parser.add_argument(
    '--ipv4_lstrip_zeros',
    type=str,
    nargs=1,
    metavar='"IP ADDRESS"',
    help='Strip leading zeros in each octet of an IPv4 address.'
)
parser.add_argument(
    '--calculate_cidr',
    type=str,
    nargs=2,
    metavar='"IP ADDRESS"',
    help='Calculate a CIDR range(s) from a start and end IP address.'
)
parser.add_argument(
    '--get_countries',
    action='store_true',
    help='Output a dictionary containing ISO_3166-1 country codes to names.'
)
parser.add_argument(
    '--get_country',
    type=str,
    nargs=1,
    metavar='"COUNTRY CODE"',
    help='Output the ISO_3166-1 name for a country code.'
)
parser.add_argument(
    '--ipv4_is_defined',
    type=str,
    nargs=1,
    metavar='"IP ADDRESS"',
    help='Check if an IPv4 address is defined (in a reserved address range).'
)
parser.add_argument(
    '--ipv6_is_defined',
    type=str,
    nargs=1,
    metavar='"IP ADDRESS"',
    help='Check if an IPv6 address is defined (in a reserved address range).'
)
parser.add_argument(
    '--unique_everseen',
    type=json.loads,
    nargs=1,
    metavar='"ITERABLE"',
    help='List unique elements from input iterable, preserving the order.'
)
parser.add_argument(
    '--unique_addresses',
    type=str,
    nargs=1,
    metavar='"FILE PATH"',
    help='Search an input file, extracting, counting, and summarizing '
         'IPv4/IPv6 addresses/networks.'
)

# Output options
group = parser.add_argument_group('Output options')
group.add_argument(
    '--colorize',
    action='store_true',
    help='If set, colorizes the output using ANSI. Should work in most '
         'platform consoles.'
)

# Get the args
script_args = parser.parse_args()

if script_args.ipv4_lstrip_zeros:

    print(ipv4_lstrip_zeros(address=script_args.ipv4_lstrip_zeros[0]))

elif script_args.calculate_cidr:

    try:

        result = calculate_cidr(
            start_address=script_args.calculate_cidr[0],
            end_address=script_args.calculate_cidr[1]
        )

        print('{0}Found {1} CIDR blocks for ({2}, {3}){4}:\n{5}'.format(
            ANSI['green'] if script_args.colorize else '',
            len(result),
            script_args.calculate_cidr[0],
            script_args.calculate_cidr[1],
            ANSI['end'] if script_args.colorize else '',
            '\n'.join(result)
        ))

    except Exception as e:

        print('{0}Error{1}: {2}'.format(ANSI['red'], ANSI['end'], str(e)))

elif script_args.get_countries:

    try:

        result = get_countries()

        print('{0}Found {1} countries{2}:\n{3}'.format(
            ANSI['green'] if script_args.colorize else '',
            len(result),
            ANSI['end'] if script_args.colorize else '',
            '\n'.join(['{0}: {1}'.format(k, v) for k, v in (
                OrderedDict(sorted(result.items())).iteritems())])
        ))

    except Exception as e:

        print('{0}Error{1}: {2}'.format(ANSI['red'], ANSI['end'], str(e)))

elif script_args.get_country:

    try:

        countries = get_countries()
        result = countries[script_args.get_country[0].upper()]

        print('{0}Match found for country code ({1}){2}:\n{3}'.format(
            ANSI['green'] if script_args.colorize else '',
            script_args.get_country[0],
            ANSI['end'] if script_args.colorize else '',
            result
        ))

    except Exception as e:

        print('{0}Error{1}: {2}'.format(ANSI['red'], ANSI['end'], str(e)))

elif script_args.ipv4_is_defined:

    try:

        result = ipv4_is_defined(address=script_args.ipv4_is_defined[0])

        if result[0]:
            print('{0}{1} is defined{2}:\n{3}'.format(
                ANSI['green'] if script_args.colorize else '',
                script_args.ipv4_is_defined[0],
                ANSI['end'] if script_args.colorize else '',
                'Name: {0}\nRFC: {1}'.format(result[1], result[2])
            ))
        else:
            print('{0}{1} is not defined{2}'.format(
                ANSI['yellow'] if script_args.colorize else '',
                script_args.ipv4_is_defined[0],
                ANSI['end'] if script_args.colorize else ''
            ))

    except Exception as e:

        print('{0}Error{1}: {2}'.format(ANSI['red'], ANSI['end'], str(e)))

elif script_args.ipv6_is_defined:

    try:

        result = ipv6_is_defined(address=script_args.ipv6_is_defined[0])

        if result[0]:
            print('{0}{1} is defined{2}:\n{3}'.format(
                ANSI['green'] if script_args.colorize else '',
                script_args.ipv6_is_defined[0],
                ANSI['end'] if script_args.colorize else '',
                'Name: {0}\nRFC: {1}'.format(result[1], result[2])
            ))
        else:
            print('{0}{1} is not defined{2}'.format(
                ANSI['yellow'] if script_args.colorize else '',
                script_args.ipv6_is_defined[0],
                ANSI['end'] if script_args.colorize else ''
            ))

    except Exception as e:

        print('{0}Error{1}: {2}'.format(ANSI['red'], ANSI['end'], str(e)))

elif script_args.unique_everseen:

    try:

        result = list(unique_everseen(iterable=script_args.unique_everseen[0]))

        print('{0}Unique everseen{1}:\n{2}'.format(
            ANSI['green'] if script_args.colorize else '',
            ANSI['end'] if script_args.colorize else '',
            result
        ))

    except Exception as e:

        print('{0}Error{1}: {2}'.format(ANSI['red'], ANSI['end'], str(e)))

elif script_args.unique_addresses:

    try:

        result = unique_addresses(file_path=script_args.unique_addresses[0])

        tmp = []
        for k, v in sorted(result.items(), key=lambda kv: int(kv[1]['count']),
                           reverse=True):
            tmp.append('{0}{1}{2}: Count: {3}, Ports: {4}'.format(
                ANSI['b'] if script_args.colorize else '',
                k,
                ANSI['end'] if script_args.colorize else '',
                v['count'],
                json.dumps(v['ports'])
            ))

        print('{0}Found {1} unique addresses{2}:\n{3}'.format(
            ANSI['green'] if script_args.colorize else '',
            len(result),
            ANSI['end'] if script_args.colorize else '',
            '\n'.join(tmp)
        ))

    except Exception as e:

        print('{0}Error{1}: {2}'.format(ANSI['red'], ANSI['end'], str(e)))
