# Copyright (c) 2016 RIPE NCC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import argparse
import os
import re
import sys

from dateutil import parser

from ..settings import aliases


class ArgumentType(object):

    @staticmethod
    def path(string):
        if not os.path.exists(string) and not string == "-":
            raise argparse.ArgumentTypeError(
                'The file name specified, "{}" does not appear to exist'.format(
                    string
                )
            )
        return string

    @staticmethod
    def country_code(string):
        if not re.match(r"^[a-zA-Z][a-zA-Z]$", string):
            raise argparse.ArgumentTypeError(
                "Countries must be defined with a two-letter ISO code")
        return string.upper()

    @staticmethod
    def datetime(string):
        try:
            return parser.parse(string)
        except:
            raise argparse.ArgumentTypeError(
                "Times must be specified in ISO 8601 format.  For example: "
                "2010-10-01T00:00:00 or a portion thereof.  All times are in "
                "UTC."
            )

    @staticmethod
    def ip_or_domain(string):
        message = '"{}" does not appear to be an IP address or host ' \
                  'name'.format(string)

        if " " in string:
            raise argparse.ArgumentTypeError(message)
        if "." not in string and ":" not in string:
            if not re.match(r"^\w+$", string):
                raise argparse.ArgumentTypeError(message)

        return string

    @classmethod
    def comma_separated_integers_or_file(cls, string):
        """
        Allow a list of comma-separated integers, or a file containing a
        newline-separated list of integers, OR "-" which implies standard out.
        """

        if re.match(r"^((\d+,?)+)$", string):
            return cls.comma_separated_integers()(string)

        f = sys.stdin
        if not string == "-":
            if not os.path.exists(string):
                raise argparse.ArgumentTypeError("Cannot find file: {}".format(
                    string
                ))
            f = open(string)

        try:
            return [int(_) for _ in f.readlines()]
        except ValueError:
            raise argparse.ArgumentTypeError(
                "The contents of the file presented does not conform to input "
                "standards.  Please ensure that every line in the file "
                "consists of a single integer."
            )

    @staticmethod
    def tag(string):
        pattern = re.compile("^[a-z_\-0-9]+$")

        if not pattern.match(string):
            raise argparse.ArgumentTypeError(
                '"{}" does not appear to be a valid tag.'.format(string))

        return string

    class integer_range(object):

        def __init__(self, minimum=float("-inf"), maximum=float("inf")):
            self.minimum = minimum
            self.maximum = maximum

        def __call__(self, string):

            message = "The integer must be between {} and {}.".format(
                self.minimum, self.maximum)
            if self.maximum == float("inf"):
                message = "The integer must be greater than {}.".format(
                    self.minimum)

            try:
                integer = int(string)
                if integer < self.minimum or integer > self.maximum:
                    raise argparse.ArgumentTypeError(message)
            except ValueError:
                raise argparse.ArgumentTypeError(
                    "An integer must be specified."
                )

            return integer

    class comma_separated_integers(object):

        def __init__(self, minimum=float("-inf"), maximum=float("inf")):
            self.minimum = minimum
            self.maximum = maximum

        def __call__(self, string):

            r = []

            for i in string.split(","):

                try:
                    i = int(i)
                except ValueError:
                    raise argparse.ArgumentTypeError(
                        "The ids supplied were not in the correct format. Note "
                        "that you must specify them as a list of "
                        "comma-separated integers without spaces.  Example: "
                        "1,2,34,157,10006"
                    )

                if i < self.minimum:
                    raise argparse.ArgumentTypeError(
                        "{} is lower than the minimum permitted value of "
                        "{}.".format(i, self.minimum)
                    )
                if i > self.maximum:
                    raise argparse.ArgumentTypeError(
                        "{} exceeds the maximum permitted value of {}.".format(
                            i, self.maximum)
                    )

                r.append(i)

            return r

    class regex(object):

        def __init__(self, regex):
            self.regex = re.compile(regex)

        def __call__(self, string):

            if not self.regex.match(string):
                raise argparse.ArgumentTypeError(
                    '"{}" does not appear to be valid.'.format(string))

            return string

    @staticmethod
    def alias_is_valid(string):
        ret = None

        if string and not string.isdigit():
            pattern = re.compile("^[a-zA-Z\._\-0-9]+$")

            if pattern.match(string):
                ret = string

        if not ret:
            raise argparse.ArgumentTypeError(
                '"{}" does not appear to be a valid '
                'alias.'.format(string))

        return ret

    class id_or_alias(object):
        TYPE = None

        def __call__(self, string):
            if string.isdigit():
                return int(string)

            if string in aliases[self.TYPE]:
                return int(aliases[self.TYPE][string])
            else:
                raise argparse.ArgumentTypeError(
                    '"{}" does not appear to be an existent '
                    '{} alias.'.format(string, self.TYPE)
                )

    class msm_id_or_name(id_or_alias):
        TYPE = "measurement"

    class probe_id_or_name(id_or_alias):
        TYPE = "probe"