#
# Copyright 2015 BMC Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import os
import json
import requests
import urllib
import logging
import boundary.util as util

class ApiCall(object):
    def __init__(self, api_host="premium-api.boundary.com", email=None, api_token=None):
        """
        :param api_host: api end point host
        :param email: TrueSight Pulse account e-mail
        :param api_token: TrueSight Pulse api token
        :return: returns nothing

        :Example:

        from boundary import API

        api = API(email="foo@bary.com", api_token="api.xxxxxxxxxx-yyyy"
        """
        self._kwargs = None
        self._methods = {"DELETE": self._do_delete,
                         "GET": self._do_get,
                         "POST": self._do_post,
                         "PUT": self._do_put}

        self._api_host = "premium-api.boundary.com"
        self._email = None
        self._api_token = None
        self._curl = False

        # All member variables related to REST CALL
        self._scheme = "https"
        self._method = "GET"
        self._headers = None
        self._data = None
        self._url = None
        self._path = None
        self._url_parameters = None

        self._api_result = None
        self.logLevel = None

        # Set the api_host, email, api token set by environment
        # variables then override with those passed in
        self._get_environment()
        if api_host is not None:
            self._api_host = api_host
        if email is not None:
            self._email = email
        if api_token is not None:
            self._api_token = api_token

    #
    # data
    #

    @property
    def data(self):
        """
        Value of the HTTP payload
        :return:
        """
        return self._data

    @data.setter
    def data(self, data):
        self._data = data

    #
    # headers
    #

    @property
    def headers(self):
        return self._headers

    @headers.setter
    def headers(self, headers):
        self._headers = headers

    #
    # method
    #

    @property
    def method(self):
        """
        """
        return self._method

    @method.setter
    def method(self, value):
        """
        Before assigning the value validate that is in one of the
        HTTP methods we implement
        """
        keys = self._methods.keys()
        if value not in keys:
            raise AttributeError("Method value not in " + str(keys))
        else:
            self._method = value

    #
    # path
    #

    @property
    def path(self):
        return self._path

    @path.setter
    def path(self, value):
        self._path = value

    #
    # url_parameters
    #

    @property
    def url_parameters(self):
        return self._url_parameters

    @url_parameters.setter
    def url_parameters(self, url_parameters):
        self._url_parameters = url_parameters

    def _get_environment(self):
        """
        Gets the configuration stored in environment variables
        """
        if 'TSP_EMAIL' in os.environ:
            self._email = os.environ['TSP_EMAIL']
        if 'TSP_API_TOKEN' in os.environ:
            self._api_token = os.environ['TSP_API_TOKEN']
        if 'TSP_API_HOST' in os.environ:
            self._api_host = os.environ['TSP_API_HOST']
        else:
            self._api_host = 'api.truesight.bmc.com'

    def _get_url_parameters(self):
        """
        Encode URL parameters
        """
        url_parameters = ''
        if self._url_parameters is not None:
            url_parameters = '?' + urllib.urlencode(self._url_parameters)
        return url_parameters

    def metric_get(self, enabled=False, custom=False):
        """
        Returns a metric definition identified by name
        :param enabled: Return only enabled metrics
        :param custom: Return only custom metrics
        :return Metrics:
        """
        self.path = 'v1/metrics?enabled={0}&{1}'.format(enabled, custom)
        self._call_api()
        self._handle_results()
        return self.metrics

    def get_api_parameters(self):
        pass

    def handle_api_results(self):
        pass

    def _do_get(self):
        """
        HTTP Get Request
        """
        return requests.get(self._url, data=self._data, headers=self._headers, auth=(self._email, self._api_token))

    def _do_delete(self):
        """
        HTTP Delete Request
        """
        return requests.delete(self._url, data=self._data, headers=self._headers, auth=(self._email, self._api_token))

    def _do_post(self):
        """
        HTTP Post Request
        """
        return requests.post(self._url, data=self._data, headers=self._headers, auth=(self._email, self._api_token))

    def _do_put(self):
        """
        HTTP Put Request
        """
        return requests.put(self._url, data=self._data, headers=self._headers, auth=(self._email, self._api_token))

    def good_response(self, status_code):
        """
        Determines what status codes represent a good response from an API call.
        """
        return status_code == requests.codes.ok

    def form_url(self):
        return "{0}://{1}/{2}{3}".format(self._scheme, self._api_host, self._path, self._get_url_parameters())

    def _curl_output(self):

        headers = ""
        if self._headers is not None:
            for key in self._headers:
                headers = headers + ' -H "{0}: {1}"'.format(key, self._headers[key])

        data = None
        if self._data is not None:
            data = " -d '{0}'".format(self._data)
        else:
            data = ''

        url = ' "{0}"'.format(self.form_url())

        print('curl -X {0} -u "{1}:{2}"{3}{4}{5}'.format(self._method,
                                                       self._email,
                                                       self._api_token,
                                                       headers,
                                                       data,
                                                       url))

    def _call_api(self):
        """
        Make an API call to get the metric definition
        """

        self._url = self.form_url()
        if self._headers is not None:
            logging.debug(self._headers)
        if self._data is not None:
            logging.debug(self._data)
        if len(self._get_url_parameters()) > 0:
            logging.debug(self._get_url_parameters())

        result = self._methods[self._method]()

        if not self.good_response(result.status_code):
            logging.error(self._url)
            logging.error(self._method)
            if self._data is not None:
                logging.error(self._data)
            logging.error(result)
        self._api_result = result

    def handle_key_word_args(self):
        pass

    def api_call(self):
        self._get_environment()
        self.handle_key_word_args()
        self.get_api_parameters()
        self._call_api()
        return self._handle_api_results()

    def _handle_api_results(self):
        result = None
        # Only process if we get HTTP result of 200
        if self._api_result.status_code == requests.codes.ok:
            result = json.loads(self._api_result.text)
        return result


#
# Copyright 2015 BMC Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

from boundary import ApiCli
import csv
from datetime import datetime
import sys
from matplotlib import pyplot as plt

class MeasurementPlot(ApiCli):
    def __init__(self):
        ApiCli.__init__(self)
        self.file_name = None

        linestyles = [

            # solid line style
            '-',
            # dashed line style
            '--',
            # dash-dot line style
            '-.',
            # dotted line style
            ':'
        ]

        markers = [
            # point marker
            '.',
            # pixel marker
            ',',
            # circle marker
            'o',
            # triangle_down marker
            'v',
            # triangle_up marker
            '^',
            # triangle_left marker
            '<',
            # triangle_right marker
            '>',
            # tri_down marker
            '1',
            # tri_up marker
            '2',
            # tri_left marker
            '3',
            # tri_right marker
            '4',
            # square marker
            's',
            # pentagon marker
            'p',
            # star marker
            '*',
            # hexagon1 marker
            'h',
            # hexagon2 marker
            'H',
            # plus marker
            '+',
            # x marker
            'x',
            # diamond marker
            'D',
            # thin_diamond marker
            'd',
            # vline marker
            '|',
            # hline marker
            '_',
        ]

        colors = [
            # blue
            'b',
            # green
            'g',
            # red
            'r',
            # cyan
            'c',
            # magenta
            'm',
            # yellow
            'y',
            # black
            'k',
            # white
            'w',
        ]

        self._symbols = [(linestyle, marker, color) for linestyle in linestyles for marker in markers for color in
                         colors]

    def add_arguments(self):
        ApiCli.add_arguments(self)
        self.parser.add_argument('-f', '--file-name', dest='file_name', action='store', required=False, metavar='path',
                                 help='File name including path of data to plot')

    def get_arguments(self):
        """
        Extracts the specific arguments of this CLI
        """
        # ApiCli.get_arguments(self)
        if self.args.file_name is not None:
            self.file_name = self.args.file_name

    def _plot_data(self):
        timestamps = {}
        values = {}
        metric_name = None

        if self.file_name is not None:
            f = open(self.file_name, 'r')
        else:
            f = sys.stdin
        reader = csv.reader(f, delimiter=',')
        first = True
        for row in reader:
            if first:
                first = False
            else:
                epoch_ts = int(row[0]) / 1000
                metric_name = row[1]
                source = row[2]
                value = float(row[3])
                ts = datetime.fromtimestamp(epoch_ts)
                if source not in timestamps:
                    timestamps[source] = []
                timestamps[source].append(ts)
                if source not in values:
                    values[source] = []
                values[source].append(value)

        # Close the file handle only if we opened it
        if f != sys.stdin:
            f.close()

        symbol_index = 0
        for source in values:
            linestyle, marker, color = self._symbols[symbol_index]
            plt.plot(timestamps[source], values[source], color=color, marker=marker, linestyle=linestyle, label=source)
            symbol_index += 1

        # add legend
        plt.legend(loc=9)

        # add a title
        plt.title(metric_name)
        #  add a label to the y-axis
        plt.ylabel("Bytes")
        plt.show()

    def execute(self):
        """
        Run the steps to execute the CLI
        """
        # self._get_environment()
        self.add_arguments()
        self._parse_args()
        self.get_arguments()
        if self._validate_arguments():
            self._plot_data()
        else:
            print(self._message)


from unittest import TestCase
from inspect import isclass, ismethod, ismodule

from twisted.python.modules import getModule as get_module
from twisted.python.reflect import namedAny as named_any
import attr

from virtue.loaders import AttributeLoader, ModuleLoader

def prefixed_by(prefix):
    """
    Make a callable returning True for names starting with the given prefix.

    The returned callable takes two arguments, the attribute or name of
    the object, and possibly its corresponding value (which is ignored),
    as suitable for use with :meth:`ObjectLocator.is_test_module` and
    :meth:`ObjectLocator.is_test_method`\ .

    """

    def prefixed_by_(name, value=None):
        return name.startswith(prefix)
    prefixed_by_.__name__ += prefix
    return prefixed_by_

def inherits_from_TestCase(attr, cls):
    return issubclass(cls, TestCase)

@attr.s
class ObjectLocator(object):
    """
    I locate test cases on an object: a package, module or test class.

    Arguments:

        is_test_method (callable):

            decide whether the provided object is a test method
            or not. By default, callable objects whose names
            (``__name__``\ s) start with ``test_`` are considered
            test methods.

        is_test_class (callable):

            decide whether the provided object is a test
            class or not. By default, objects inheriting from
            `unittest.TestCase` are considered test cases.

        is_test_module (callable):

            decide whether the provided object is a test module
            or not. By default, modules whose names start with
            ``test_`` are considered to be test modules.

    """

    is_test_method = attr.ib(default=prefixed_by("test"), repr=False)
    is_test_class = attr.ib(default=inherits_from_TestCase, repr=False)
    is_test_module = attr.ib(default=prefixed_by("test_"), repr=False)

    def locate_by_name(self, name):
        """
        Locate any tests found in the object referred to by the given name.

        The name should be a fully qualified object name. (E.g., the fully
        qualified object name of this function is
        ``virtue.locators.ObjectLocator.locate_by_name``\ ).

        """

        obj = named_any(name)
        if ismethod(obj):
            class_name, dot, method_name = name.rpartition(".")
            cls = named_any(class_name)

            is_aliased = not isclass(cls)
            if is_aliased:
                cls, method_name = obj.im_class, obj.__name__

            return [AttributeLoader(cls=cls, attribute=method_name)]
        return self.locate_in(obj)

    def locate_in(self, obj):
        """
        Attempt to locate the test cases in the given object (of any kind).

        """

        # We don't use inspect.getmembers because its predicate only
        # takes the value, not the attr name.
        if ismodule(obj):
            is_package = getattr(obj, "__path__", None)
            if is_package is not None:
                return self.locate_in_package(obj)
            return self.locate_in_module(obj)
        elif isclass(obj):
            return self.locate_in_class(obj)
        else:
            raise ValueError(
                "Can't determine the appropriate way to load {0!r}".format(
                    obj,
                )
            )

    def locate_in_package(self, package):
        """
        Locate all of the test cases contained in the given package.

        """

        for module in get_module(package.__name__).walkModules():
            _, _, name = module.name.rpartition(".")
            if self.is_test_module(name):
                yield ModuleLoader(locator=self, module=module)

    def locate_in_module(self, module):
        """
        Locate all of the test cases contained in the given module.

        """

        for attribute in dir(module):
            value = getattr(module, attribute, None)
            if isclass(value) and self.is_test_class(attribute, value):
                for test_case in self.locate_in_class(value):
                    yield test_case

    def locate_in_class(self, cls):
        """
        Locate the methods on the given class that are test cases.

        """

        for attribute in dir(cls):
            value = getattr(cls, attribute, None)
            if callable(value) and self.is_test_method(attribute, value):
                yield AttributeLoader(cls=cls, attribute=attribute)


#
# Copyright 2015 BMCSoftware, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""
Implements command to remove a metric definition from a Boundary account.
"""

from boundary import ApiCli

"""
Uses the following Boundary API:

http://premium-documentation.boundary.com/v1/delete/metrics/:metric
"""

class MetricDelete(ApiCli):
    def __init__(self):
        ApiCli.__init__(self)
        self.method = "DELETE"
        self._metric_name = None

    def add_arguments(self):
        ApiCli.add_arguments(self)
        self.parser.add_argument('-n', '--metric-name', dest='metric_name', action='store', required=True,
                                 metavar='metric_name', help='Metric identifier')

    def get_arguments(self):
        """
        Extracts the specific arguments of this CLI
        """
        ApiCli.get_arguments(self)
        if self.args.metric_name is not None:
            self._metric_name = self.args.metric_name

        self.path = "v1/metrics/{0}".format(self._metric_name)

    def get_description(self):
        return 'Deletes a metric definition from a {0} account'.format(self.product_name)


'''
datetime.tzinfo timezone definitions generated from the
Olson timezone database:

    ftp://elsie.nci.nih.gov/pub/tz*.tar.gz

See the datetime section of the Python Library Reference for information
on how to use these modules.
'''

# The Olson database is updated several times a year.
OLSON_VERSION = '2013d'
VERSION = OLSON_VERSION
# Version format for a patch release - only one so far.
#VERSION = OLSON_VERSION + '.2'
__version__ = OLSON_VERSION

OLSEN_VERSION = OLSON_VERSION # Old releases had this misspelling

__all__ = [
    'timezone', 'utc', 'country_timezones', 'country_names',
    'AmbiguousTimeError', 'InvalidTimeError',
    'NonExistentTimeError', 'UnknownTimeZoneError',
    'all_timezones', 'all_timezones_set',
    'common_timezones', 'common_timezones_set',
    ]

import sys, datetime, os.path, gettext

from pytzpure.exceptions import AmbiguousTimeError
from pytzpure.exceptions import InvalidTimeError
from pytzpure.exceptions import NonExistentTimeError
from pytzpure.exceptions import UnknownTimeZoneError
from pytzpure.lazy import LazyDict, LazyList, LazySet
from pytzpure.tzinfo import unpickler
from pytzpure.tzfile import build_tzinfo, _byte_string

from pytzpure.tz_descriptor import TzDescriptor
from pytzpure.zone_tab_data import ZoneTabLazy
from pytzpure.iso3166_data import Iso3166Lazy
from pytzpure.loader import is_loadable

try:
    unicode

except NameError: # Python 3.x

    # Python 3.x doesn't have unicode(), making writing code
    # for Python 2.3 and Python 3.x a pain.
    unicode = str

    def ascii(s):
        r"""
        >>> ascii('Hello')
        'Hello'
        >>> ascii('\N{TRADE MARK SIGN}') #doctest: +IGNORE_EXCEPTION_DETAIL
        Traceback (most recent call last):
            ...
        UnicodeEncodeError: ...
        """
        s.encode('US-ASCII') # Raise an exception if not ASCII
        return s # But return the original string - not a byte string.

else: # Python 2.x

    def ascii(s):
        r"""
        >>> ascii('Hello')
        'Hello'
        >>> ascii(u'Hello')
        'Hello'
        >>> ascii(u'\N{TRADE MARK SIGN}') #doctest: +IGNORE_EXCEPTION_DETAIL
        Traceback (most recent call last):
            ...
        UnicodeEncodeError: ...
        """
        return s.encode('US-ASCII')

#def open_resource(name):
#    """Open a resource from the zoneinfo subdir for reading.
#
#    Uses the pkg_resources module if available and no standard file
#    found at the calculated location.
#    """
#    name_parts = name.lstrip('/').split('/')
#    for part in name_parts:
#        if part == os.path.pardir or os.path.sep in part:
#            raise ValueError('Bad path segment: %r' % part)
#    filename = os.path.join(os.path.dirname(__file__),
#                            'zoneinfo', *name_parts)
#    if not os.path.exists(filename) and resource_stream is not None:
#        # http://bugs.launchpad.net/bugs/383171 - we avoid using this
#        # unless absolutely necessary to help when a broken version of
#        # pkg_resources is installed.
#        return resource_stream(__name__, 'zoneinfo/' + name)
#    return open(filename, 'rb')

#def resource_exists(name):
#    """Return true if the given resource exists"""
#    try:
#        open_resource(name).close()
#        return True
#    except IOError:
#        return False

# Enable this when we get some translations?
# We want an i18n API that is useful to programs using Python's gettext
# module, as well as the Zope3 i18n package. Perhaps we should just provide
# the POT file and translations, and leave it up to callers to make use
# of them.
#
# t = gettext.translation(
#         'pytz', os.path.join(os.path.dirname(__file__), 'locales'),
#         fallback=True
#         )
# def _(timezone_name):
#     """Translate a timezone name using the current locale, returning Unicode"""
#     return t.ugettext(timezone_name)

_tzinfo_cache = {}

def timezone(zone):
    r''' Return a datetime.tzinfo implementation for the given timezone

    >>> from datetime import datetime, timedelta
    >>> utc = timezone('UTC')
    >>> eastern = timezone('US/Eastern')
    >>> eastern.zone
    'US/Eastern'
    >>> timezone(unicode('US/Eastern')) is eastern
    True
    >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc)
    >>> loc_dt = utc_dt.astimezone(eastern)
    >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'
    >>> loc_dt.strftime(fmt)
    '2002-10-27 01:00:00 EST (-0500)'
    >>> (loc_dt - timedelta(minutes=10)).strftime(fmt)
    '2002-10-27 00:50:00 EST (-0500)'
    >>> eastern.normalize(loc_dt - timedelta(minutes=10)).strftime(fmt)
    '2002-10-27 01:50:00 EDT (-0400)'
    >>> (loc_dt + timedelta(minutes=10)).strftime(fmt)
    '2002-10-27 01:10:00 EST (-0500)'

    Raises UnknownTimeZoneError if passed an unknown zone.

    >>> try:
    ...     timezone('Asia/Shangri-La')
    ... except UnknownTimeZoneError:
    ...     print('Unknown')
    Unknown

    >>> try:
    ...     timezone(unicode('\N{TRADE MARK SIGN}'))
    ... except UnknownTimeZoneError:
    ...     print('Unknown')
    Unknown

    '''
    if zone.upper() == 'UTC':
        return utc

    try:
        zone = ascii(zone)
    except UnicodeEncodeError:
        # All valid timezones are ASCII
        raise UnknownTimeZoneError(zone)

    zone = _unmunge_zone(zone)
    if zone not in _tzinfo_cache:
        if zone in all_timezones_set:
#            fp = open_resource(zone)
#            try:
             _tzinfo_cache[zone] = build_tzinfo(zone)#, fp)
#            finally:
#                fp.close()
        else:
            raise UnknownTimeZoneError(zone)

    return _tzinfo_cache[zone]

def _unmunge_zone(zone):
    """Undo the time zone name munging done by older versions of pytz."""
    return zone.replace('_plus_', '+').replace('_minus_', '-')

ZERO = datetime.timedelta(0)
HOUR = datetime.timedelta(hours=1)

class UTC(datetime.tzinfo):
    """UTC

    Optimized UTC implementation. It unpickles using the single module global
    instance defined beneath this class declaration.
    """
    zone = "UTC"

    _utcoffset = ZERO
    _dst = ZERO
    _tzname = zone

    def fromutc(self, dt):
        if dt.tzinfo is None:
            return self.localize(dt)
        return super(utc.__class__, self).fromutc(dt)

    def utcoffset(self, dt):
        return ZERO

    def tzname(self, dt):
        return "UTC"

    def dst(self, dt):
        return ZERO

    def __reduce__(self):
        return _UTC, ()

    def localize(self, dt, is_dst=False):
        '''Convert naive time to local time'''
        if dt.tzinfo is not None:
            raise ValueError('Not naive datetime (tzinfo is already set)')
        return dt.replace(tzinfo=self)

    def normalize(self, dt, is_dst=False):
        '''Correct the timezone information on the given datetime'''
        if dt.tzinfo is self:
            return dt
        if dt.tzinfo is None:
            raise ValueError('Naive time - no tzinfo set')
        return dt.astimezone(self)

    def __repr__(self):
        return "<UTC>"

    def __str__(self):
        return "UTC"

UTC = utc = UTC() # UTC is a singleton

def _UTC():
    """Factory function for utc unpickling.

    Makes sure that unpickling a utc instance always returns the same
    module global.

    These examples belong in the UTC class above, but it is obscured; or in
    the README.txt, but we are not depending on Python 2.4 so integrating
    the README.txt examples with the unit tests is not trivial.

    >>> import datetime, pickle
    >>> dt = datetime.datetime(2005, 3, 1, 14, 13, 21, tzinfo=utc)
    >>> naive = dt.replace(tzinfo=None)
    >>> p = pickle.dumps(dt, 1)
    >>> naive_p = pickle.dumps(naive, 1)
    >>> len(p) - len(naive_p)
    17
    >>> new = pickle.loads(p)
    >>> new == dt
    True
    >>> new is dt
    False
    >>> new.tzinfo is dt.tzinfo
    True
    >>> utc is UTC is timezone('UTC')
    True
    >>> utc is timezone('GMT')
    False
    """
    return utc
_UTC.__safe_for_unpickling__ = True

def _p(*args):
    """Factory function for unpickling pytz tzinfo instances.

    Just a wrapper around tzinfo.unpickler to save a few bytes in each pickle
    by shortening the path.
    """
    return unpickler(*args)
_p.__safe_for_unpickling__ = True

#class _CountryTimezoneDict(LazyDict):
#    """Map ISO 3166 country code to a list of timezone names commonly used
#    in that country.
#
#    iso3166_code is the two letter code used to identify the country.
#
#    >>> def print_list(list_of_strings):
#    ...     'We use a helper so doctests work under Python 2.3 -> 3.x'
#    ...     for s in list_of_strings:
#    ...         print(s)
#
#    >>> print_list(country_timezones['nz'])
#    Pacific/Auckland
#    Pacific/Chatham
#    >>> print_list(country_timezones['ch'])
#    Europe/Zurich
#    >>> print_list(country_timezones['CH'])
#    Europe/Zurich
#    >>> print_list(country_timezones[unicode('ch')])
#    Europe/Zurich
#    >>> print_list(country_timezones['XXX'])
#    Traceback (most recent call last):
#    ...
#    KeyError: 'XXX'
#
#    Previously, this information was exposed as a function rather than a
#    dictionary. This is still supported::
#
#    >>> print_list(country_timezones('nz'))
#    Pacific/Auckland
#    Pacific/Chatham
#    """
#    def __call__(self, iso3166_code):
#        """Backwards compatibility."""
#        return self[iso3166_code]
#
#    def _fill(self):
#        data = {}
#        zone_tab = open_resource('zone.tab')
#        try:
#            for line in zone_tab:
#                line = line.decode('US-ASCII')
#                if line.startswith('#'):
#                    continue
#                code, coordinates, zone = line.split(None, 4)[:3]
#                if zone not in all_timezones_set:
#                    continue
#                try:
#                    data[code].append(zone)
#                except KeyError:
#                    data[code] = [zone]
#            self.data = data
#        finally:
#            zone_tab.close()

country_timezones = ZoneTabLazy()#_CountryTimezoneDict()

#class _CountryNameDict(LazyDict):
#    '''Dictionary proving ISO3166 code -> English name.
#
#    >>> print(country_names['au'])
#    Australia
#    '''
#    def _fill(self):
#        data = {}
#        zone_tab = open_resource('iso3166.tab')
#        try:
#            for line in zone_tab.readlines():
#                line = line.decode('US-ASCII')
#                if line.startswith('#'):
#                    continue
#                code, name = line.split(None, 1)
#                data[code] = name.strip()
#            self.data = data
#        finally:
#            zone_tab.close()

country_names = Iso3166Lazy()#_CountryNameDict()

# Time-zone info based solely on fixed offsets

class _FixedOffset(datetime.tzinfo):

    zone = None # to match the standard pytz API

    def __init__(self, minutes):
        if abs(minutes) >= 1440:
            raise ValueError("absolute offset is too large", minutes)
        self._minutes = minutes
        self._offset = datetime.timedelta(minutes=minutes)

    def utcoffset(self, dt):
        return self._offset

    def __reduce__(self):
        return FixedOffset, (self._minutes, )

    def dst(self, dt):
        return ZERO

    def tzname(self, dt):
        return None

    def __repr__(self):
        return 'pytz.FixedOffset(%d)' % self._minutes

    def localize(self, dt, is_dst=False):
        '''Convert naive time to local time'''
        if dt.tzinfo is not None:
            raise ValueError('Not naive datetime (tzinfo is already set)')
        return dt.replace(tzinfo=self)

    def normalize(self, dt, is_dst=False):
        '''Correct the timezone information on the given datetime'''
        if dt.tzinfo is None:
            raise ValueError('Naive time - no tzinfo set')
        return dt.replace(tzinfo=self)

def FixedOffset(offset, _tzinfos = {}):
    """return a fixed-offset timezone based off a number of minutes.

        >>> one = FixedOffset(-330)
        >>> one
        pytz.FixedOffset(-330)
        >>> one.utcoffset(datetime.datetime.now())
        datetime.timedelta(-1, 66600)
        >>> one.dst(datetime.datetime.now())
        datetime.timedelta(0)

        >>> two = FixedOffset(1380)
        >>> two
        pytz.FixedOffset(1380)
        >>> two.utcoffset(datetime.datetime.now())
        datetime.timedelta(0, 82800)
        >>> two.dst(datetime.datetime.now())
        datetime.timedelta(0)

    The datetime.timedelta must be between the range of -1 and 1 day,
    non-inclusive.

        >>> FixedOffset(1440)
        Traceback (most recent call last):
        ...
        ValueError: ('absolute offset is too large', 1440)

        >>> FixedOffset(-1440)
        Traceback (most recent call last):
        ...
        ValueError: ('absolute offset is too large', -1440)

    An offset of 0 is special-cased to return UTC.

        >>> FixedOffset(0) is UTC
        True

    There should always be only one instance of a FixedOffset per timedelta.
    This should be true for multiple creation calls.

        >>> FixedOffset(-330) is one
        True
        >>> FixedOffset(1380) is two
        True

    It should also be true for pickling.

        >>> import pickle
        >>> pickle.loads(pickle.dumps(one)) is one
        True
        >>> pickle.loads(pickle.dumps(two)) is two
        True
    """
    if offset == 0:
        return UTC

    info = _tzinfos.get(offset)
    if info is None:
        # We haven't seen this one before. we need to save it.

        # Use setdefault to avoid a race condition and make sure we have
        # only one
        info = _tzinfos.setdefault(offset, _FixedOffset(offset))

    return info

FixedOffset.__safe_for_unpickling__ = True

def _test():
    import doctest, os, sys
    sys.path.insert(0, os.pardir)
    import pytz
    return doctest.testmod(pytz)

if __name__ == '__main__':
    _test()

all_timezones = \
['Africa/Abidjan',
 'Africa/Accra',
 'Africa/Addis_Ababa',
 'Africa/Algiers',
 'Africa/Asmara',
 'Africa/Asmera',
 'Africa/Bamako',
 'Africa/Bangui',
 'Africa/Banjul',
 'Africa/Bissau',
 'Africa/Blantyre',
 'Africa/Brazzaville',
 'Africa/Bujumbura',
 'Africa/Cairo',
 'Africa/Casablanca',
 'Africa/Ceuta',
 'Africa/Conakry',
 'Africa/Dakar',
 'Africa/Dar_es_Salaam',
 'Africa/Djibouti',
 'Africa/Douala',
 'Africa/El_Aaiun',
 'Africa/Freetown',
 'Africa/Gaborone',
 'Africa/Harare',
 'Africa/Johannesburg',
 'Africa/Juba',
 'Africa/Kampala',
 'Africa/Khartoum',
 'Africa/Kigali',
 'Africa/Kinshasa',
 'Africa/Lagos',
 'Africa/Libreville',
 'Africa/Lome',
 'Africa/Luanda',
 'Africa/Lubumbashi',
 'Africa/Lusaka',
 'Africa/Malabo',
 'Africa/Maputo',
 'Africa/Maseru',
 'Africa/Mbabane',
 'Africa/Mogadishu',
 'Africa/Monrovia',
 'Africa/Nairobi',
 'Africa/Ndjamena',
 'Africa/Niamey',
 'Africa/Nouakchott',
 'Africa/Ouagadougou',
 'Africa/Porto-Novo',
 'Africa/Sao_Tome',
 'Africa/Timbuktu',
 'Africa/Tripoli',
 'Africa/Tunis',
 'Africa/Windhoek',
 'America/Adak',
 'America/Anchorage',
 'America/Anguilla',
 'America/Antigua',
 'America/Araguaina',
 'America/Argentina/Buenos_Aires',
 'America/Argentina/Catamarca',
 'America/Argentina/ComodRivadavia',
 'America/Argentina/Cordoba',
 'America/Argentina/Jujuy',
 'America/Argentina/La_Rioja',
 'America/Argentina/Mendoza',
 'America/Argentina/Rio_Gallegos',
 'America/Argentina/Salta',
 'America/Argentina/San_Juan',
 'America/Argentina/San_Luis',
 'America/Argentina/Tucuman',
 'America/Argentina/Ushuaia',
 'America/Aruba',
 'America/Asuncion',
 'America/Atikokan',
 'America/Atka',
 'America/Bahia',
 'America/Bahia_Banderas',
 'America/Barbados',
 'America/Belem',
 'America/Belize',
 'America/Blanc-Sablon',
 'America/Boa_Vista',
 'America/Bogota',
 'America/Boise',
 'America/Buenos_Aires',
 'America/Cambridge_Bay',
 'America/Campo_Grande',
 'America/Cancun',
 'America/Caracas',
 'America/Catamarca',
 'America/Cayenne',
 'America/Cayman',
 'America/Chicago',
 'America/Chihuahua',
 'America/Coral_Harbour',
 'America/Cordoba',
 'America/Costa_Rica',
 'America/Creston',
 'America/Cuiaba',
 'America/Curacao',
 'America/Danmarkshavn',
 'America/Dawson',
 'America/Dawson_Creek',
 'America/Denver',
 'America/Detroit',
 'America/Dominica',
 'America/Edmonton',
 'America/Eirunepe',
 'America/El_Salvador',
 'America/Ensenada',
 'America/Fort_Wayne',
 'America/Fortaleza',
 'America/Glace_Bay',
 'America/Godthab',
 'America/Goose_Bay',
 'America/Grand_Turk',
 'America/Grenada',
 'America/Guadeloupe',
 'America/Guatemala',
 'America/Guayaquil',
 'America/Guyana',
 'America/Halifax',
 'America/Havana',
 'America/Hermosillo',
 'America/Indiana/Indianapolis',
 'America/Indiana/Knox',
 'America/Indiana/Marengo',
 'America/Indiana/Petersburg',
 'America/Indiana/Tell_City',
 'America/Indiana/Vevay',
 'America/Indiana/Vincennes',
 'America/Indiana/Winamac',
 'America/Indianapolis',
 'America/Inuvik',
 'America/Iqaluit',
 'America/Jamaica',
 'America/Jujuy',
 'America/Juneau',
 'America/Kentucky/Louisville',
 'America/Kentucky/Monticello',
 'America/Knox_IN',
 'America/Kralendijk',
 'America/La_Paz',
 'America/Lima',
 'America/Los_Angeles',
 'America/Louisville',
 'America/Lower_Princes',
 'America/Maceio',
 'America/Managua',
 'America/Manaus',
 'America/Marigot',
 'America/Martinique',
 'America/Matamoros',
 'America/Mazatlan',
 'America/Mendoza',
 'America/Menominee',
 'America/Merida',
 'America/Metlakatla',
 'America/Mexico_City',
 'America/Miquelon',
 'America/Moncton',
 'America/Monterrey',
 'America/Montevideo',
 'America/Montreal',
 'America/Montserrat',
 'America/Nassau',
 'America/New_York',
 'America/Nipigon',
 'America/Nome',
 'America/Noronha',
 'America/North_Dakota/Beulah',
 'America/North_Dakota/Center',
 'America/North_Dakota/New_Salem',
 'America/Ojinaga',
 'America/Panama',
 'America/Pangnirtung',
 'America/Paramaribo',
 'America/Phoenix',
 'America/Port-au-Prince',
 'America/Port_of_Spain',
 'America/Porto_Acre',
 'America/Porto_Velho',
 'America/Puerto_Rico',
 'America/Rainy_River',
 'America/Rankin_Inlet',
 'America/Recife',
 'America/Regina',
 'America/Resolute',
 'America/Rio_Branco',
 'America/Rosario',
 'America/Santa_Isabel',
 'America/Santarem',
 'America/Santiago',
 'America/Santo_Domingo',
 'America/Sao_Paulo',
 'America/Scoresbysund',
 'America/Shiprock',
 'America/Sitka',
 'America/St_Barthelemy',
 'America/St_Johns',
 'America/St_Kitts',
 'America/St_Lucia',
 'America/St_Thomas',
 'America/St_Vincent',
 'America/Swift_Current',
 'America/Tegucigalpa',
 'America/Thule',
 'America/Thunder_Bay',
 'America/Tijuana',
 'America/Toronto',
 'America/Tortola',
 'America/Vancouver',
 'America/Virgin',
 'America/Whitehorse',
 'America/Winnipeg',
 'America/Yakutat',
 'America/Yellowknife',
 'Antarctica/Casey',
 'Antarctica/Davis',
 'Antarctica/DumontDUrville',
 'Antarctica/Macquarie',
 'Antarctica/Mawson',
 'Antarctica/McMurdo',
 'Antarctica/Palmer',
 'Antarctica/Rothera',
 'Antarctica/South_Pole',
 'Antarctica/Syowa',
 'Antarctica/Vostok',
 'Arctic/Longyearbyen',
 'Asia/Aden',
 'Asia/Almaty',
 'Asia/Amman',
 'Asia/Anadyr',
 'Asia/Aqtau',
 'Asia/Aqtobe',
 'Asia/Ashgabat',
 'Asia/Ashkhabad',
 'Asia/Baghdad',
 'Asia/Bahrain',
 'Asia/Baku',
 'Asia/Bangkok',
 'Asia/Beirut',
 'Asia/Bishkek',
 'Asia/Brunei',
 'Asia/Calcutta',
 'Asia/Choibalsan',
 'Asia/Chongqing',
 'Asia/Chungking',
 'Asia/Colombo',
 'Asia/Dacca',
 'Asia/Damascus',
 'Asia/Dhaka',
 'Asia/Dili',
 'Asia/Dubai',
 'Asia/Dushanbe',
 'Asia/Gaza',
 'Asia/Harbin',
 'Asia/Hebron',
 'Asia/Ho_Chi_Minh',
 'Asia/Hong_Kong',
 'Asia/Hovd',
 'Asia/Irkutsk',
 'Asia/Istanbul',
 'Asia/Jakarta',
 'Asia/Jayapura',
 'Asia/Jerusalem',
 'Asia/Kabul',
 'Asia/Kamchatka',
 'Asia/Karachi',
 'Asia/Kashgar',
 'Asia/Kathmandu',
 'Asia/Katmandu',
 'Asia/Khandyga',
 'Asia/Kolkata',
 'Asia/Krasnoyarsk',
 'Asia/Kuala_Lumpur',
 'Asia/Kuching',
 'Asia/Kuwait',
 'Asia/Macao',
 'Asia/Macau',
 'Asia/Magadan',
 'Asia/Makassar',
 'Asia/Manila',
 'Asia/Muscat',
 'Asia/Nicosia',
 'Asia/Novokuznetsk',
 'Asia/Novosibirsk',
 'Asia/Omsk',
 'Asia/Oral',
 'Asia/Phnom_Penh',
 'Asia/Pontianak',
 'Asia/Pyongyang',
 'Asia/Qatar',
 'Asia/Qyzylorda',
 'Asia/Rangoon',
 'Asia/Riyadh',
 'Asia/Saigon',
 'Asia/Sakhalin',
 'Asia/Samarkand',
 'Asia/Seoul',
 'Asia/Shanghai',
 'Asia/Singapore',
 'Asia/Taipei',
 'Asia/Tashkent',
 'Asia/Tbilisi',
 'Asia/Tehran',
 'Asia/Tel_Aviv',
 'Asia/Thimbu',
 'Asia/Thimphu',
 'Asia/Tokyo',
 'Asia/Ujung_Pandang',
 'Asia/Ulaanbaatar',
 'Asia/Ulan_Bator',
 'Asia/Urumqi',
 'Asia/Ust-Nera',
 'Asia/Vientiane',
 'Asia/Vladivostok',
 'Asia/Yakutsk',
 'Asia/Yekaterinburg',
 'Asia/Yerevan',
 'Atlantic/Azores',
 'Atlantic/Bermuda',
 'Atlantic/Canary',
 'Atlantic/Cape_Verde',
 'Atlantic/Faeroe',
 'Atlantic/Faroe',
 'Atlantic/Jan_Mayen',
 'Atlantic/Madeira',
 'Atlantic/Reykjavik',
 'Atlantic/South_Georgia',
 'Atlantic/St_Helena',
 'Atlantic/Stanley',
 'Australia/ACT',
 'Australia/Adelaide',
 'Australia/Brisbane',
 'Australia/Broken_Hill',
 'Australia/Canberra',
 'Australia/Currie',
 'Australia/Darwin',
 'Australia/Eucla',
 'Australia/Hobart',
 'Australia/LHI',
 'Australia/Lindeman',
 'Australia/Lord_Howe',
 'Australia/Melbourne',
 'Australia/NSW',
 'Australia/North',
 'Australia/Perth',
 'Australia/Queensland',
 'Australia/South',
 'Australia/Sydney',
 'Australia/Tasmania',
 'Australia/Victoria',
 'Australia/West',
 'Australia/Yancowinna',
 'Brazil/Acre',
 'Brazil/DeNoronha',
 'Brazil/East',
 'Brazil/West',
 'CET',
 'CST6CDT',
 'Canada/Atlantic',
 'Canada/Central',
 'Canada/East-Saskatchewan',
 'Canada/Eastern',
 'Canada/Mountain',
 'Canada/Newfoundland',
 'Canada/Pacific',
 'Canada/Saskatchewan',
 'Canada/Yukon',
 'Chile/Continental',
 'Chile/EasterIsland',
 'Cuba',
 'EET',
 'EST',
 'EST5EDT',
 'Egypt',
 'Eire',
 'Etc/GMT',
 'Etc/GMT+0',
 'Etc/GMT+1',
 'Etc/GMT+10',
 'Etc/GMT+11',
 'Etc/GMT+12',
 'Etc/GMT+2',
 'Etc/GMT+3',
 'Etc/GMT+4',
 'Etc/GMT+5',
 'Etc/GMT+6',
 'Etc/GMT+7',
 'Etc/GMT+8',
 'Etc/GMT+9',
 'Etc/GMT-0',
 'Etc/GMT-1',
 'Etc/GMT-10',
 'Etc/GMT-11',
 'Etc/GMT-12',
 'Etc/GMT-13',
 'Etc/GMT-14',
 'Etc/GMT-2',
 'Etc/GMT-3',
 'Etc/GMT-4',
 'Etc/GMT-5',
 'Etc/GMT-6',
 'Etc/GMT-7',
 'Etc/GMT-8',
 'Etc/GMT-9',
 'Etc/GMT0',
 'Etc/Greenwich',
 'Etc/UCT',
 'Etc/UTC',
 'Etc/Universal',
 'Etc/Zulu',
 'Europe/Amsterdam',
 'Europe/Andorra',
 'Europe/Athens',
 'Europe/Belfast',
 'Europe/Belgrade',
 'Europe/Berlin',
 'Europe/Bratislava',
 'Europe/Brussels',
 'Europe/Bucharest',
 'Europe/Budapest',
 'Europe/Busingen',
 'Europe/Chisinau',
 'Europe/Copenhagen',
 'Europe/Dublin',
 'Europe/Gibraltar',
 'Europe/Guernsey',
 'Europe/Helsinki',
 'Europe/Isle_of_Man',
 'Europe/Istanbul',
 'Europe/Jersey',
 'Europe/Kaliningrad',
 'Europe/Kiev',
 'Europe/Lisbon',
 'Europe/Ljubljana',
 'Europe/London',
 'Europe/Luxembourg',
 'Europe/Madrid',
 'Europe/Malta',
 'Europe/Mariehamn',
 'Europe/Minsk',
 'Europe/Monaco',
 'Europe/Moscow',
 'Europe/Nicosia',
 'Europe/Oslo',
 'Europe/Paris',
 'Europe/Podgorica',
 'Europe/Prague',
 'Europe/Riga',
 'Europe/Rome',
 'Europe/Samara',
 'Europe/San_Marino',
 'Europe/Sarajevo',
 'Europe/Simferopol',
 'Europe/Skopje',
 'Europe/Sofia',
 'Europe/Stockholm',
 'Europe/Tallinn',
 'Europe/Tirane',
 'Europe/Tiraspol',
 'Europe/Uzhgorod',
 'Europe/Vaduz',
 'Europe/Vatican',
 'Europe/Vienna',
 'Europe/Vilnius',
 'Europe/Volgograd',
 'Europe/Warsaw',
 'Europe/Zagreb',
 'Europe/Zaporozhye',
 'Europe/Zurich',
 'GB',
 'GB-Eire',
 'GMT',
 'GMT+0',
 'GMT-0',
 'GMT0',
 'Greenwich',
 'HST',
 'Hongkong',
 'Iceland',
 'Indian/Antananarivo',
 'Indian/Chagos',
 'Indian/Christmas',
 'Indian/Cocos',
 'Indian/Comoro',
 'Indian/Kerguelen',
 'Indian/Mahe',
 'Indian/Maldives',
 'Indian/Mauritius',
 'Indian/Mayotte',
 'Indian/Reunion',
 'Iran',
 'Israel',
 'Jamaica',
 'Japan',
 'Kwajalein',
 'Libya',
 'MET',
 'MST',
 'MST7MDT',
 'Mexico/BajaNorte',
 'Mexico/BajaSur',
 'Mexico/General',
 'NZ',
 'NZ-CHAT',
 'Navajo',
 'PRC',
 'PST8PDT',
 'Pacific/Apia',
 'Pacific/Auckland',
 'Pacific/Chatham',
 'Pacific/Chuuk',
 'Pacific/Easter',
 'Pacific/Efate',
 'Pacific/Enderbury',
 'Pacific/Fakaofo',
 'Pacific/Fiji',
 'Pacific/Funafuti',
 'Pacific/Galapagos',
 'Pacific/Gambier',
 'Pacific/Guadalcanal',
 'Pacific/Guam',
 'Pacific/Honolulu',
 'Pacific/Johnston',
 'Pacific/Kiritimati',
 'Pacific/Kosrae',
 'Pacific/Kwajalein',
 'Pacific/Majuro',
 'Pacific/Marquesas',
 'Pacific/Midway',
 'Pacific/Nauru',
 'Pacific/Niue',
 'Pacific/Norfolk',
 'Pacific/Noumea',
 'Pacific/Pago_Pago',
 'Pacific/Palau',
 'Pacific/Pitcairn',
 'Pacific/Pohnpei',
 'Pacific/Ponape',
 'Pacific/Port_Moresby',
 'Pacific/Rarotonga',
 'Pacific/Saipan',
 'Pacific/Samoa',
 'Pacific/Tahiti',
 'Pacific/Tarawa',
 'Pacific/Tongatapu',
 'Pacific/Truk',
 'Pacific/Wake',
 'Pacific/Wallis',
 'Pacific/Yap',
 'Poland',
 'Portugal',
 'ROC',
 'ROK',
 'Singapore',
 'Turkey',
 'UCT',
 'US/Alaska',
 'US/Aleutian',
 'US/Arizona',
 'US/Central',
 'US/East-Indiana',
 'US/Eastern',
 'US/Hawaii',
 'US/Indiana-Starke',
 'US/Michigan',
 'US/Mountain',
 'US/Pacific',
 'US/Pacific-New',
 'US/Samoa',
 'UTC',
 'Universal',
 'W-SU',
 'WET',
 'Zulu']
all_timezones = LazyList(
        tz for tz in all_timezones if is_loadable(tz))

all_timezones_set = LazySet(all_timezones)
common_timezones = \
['Africa/Abidjan',
 'Africa/Accra',
 'Africa/Addis_Ababa',
 'Africa/Algiers',
 'Africa/Asmara',
 'Africa/Bamako',
 'Africa/Bangui',
 'Africa/Banjul',
 'Africa/Bissau',
 'Africa/Blantyre',
 'Africa/Brazzaville',
 'Africa/Bujumbura',
 'Africa/Cairo',
 'Africa/Casablanca',
 'Africa/Ceuta',
 'Africa/Conakry',
 'Africa/Dakar',
 'Africa/Dar_es_Salaam',
 'Africa/Djibouti',
 'Africa/Douala',
 'Africa/El_Aaiun',
 'Africa/Freetown',
 'Africa/Gaborone',
 'Africa/Harare',
 'Africa/Johannesburg',
 'Africa/Juba',
 'Africa/Kampala',
 'Africa/Khartoum',
 'Africa/Kigali',
 'Africa/Kinshasa',
 'Africa/Lagos',
 'Africa/Libreville',
 'Africa/Lome',
 'Africa/Luanda',
 'Africa/Lubumbashi',
 'Africa/Lusaka',
 'Africa/Malabo',
 'Africa/Maputo',
 'Africa/Maseru',
 'Africa/Mbabane',
 'Africa/Mogadishu',
 'Africa/Monrovia',
 'Africa/Nairobi',
 'Africa/Ndjamena',
 'Africa/Niamey',
 'Africa/Nouakchott',
 'Africa/Ouagadougou',
 'Africa/Porto-Novo',
 'Africa/Sao_Tome',
 'Africa/Tripoli',
 'Africa/Tunis',
 'Africa/Windhoek',
 'America/Adak',
 'America/Anchorage',
 'America/Anguilla',
 'America/Antigua',
 'America/Araguaina',
 'America/Argentina/Buenos_Aires',
 'America/Argentina/Catamarca',
 'America/Argentina/Cordoba',
 'America/Argentina/Jujuy',
 'America/Argentina/La_Rioja',
 'America/Argentina/Mendoza',
 'America/Argentina/Rio_Gallegos',
 'America/Argentina/Salta',
 'America/Argentina/San_Juan',
 'America/Argentina/San_Luis',
 'America/Argentina/Tucuman',
 'America/Argentina/Ushuaia',
 'America/Aruba',
 'America/Asuncion',
 'America/Atikokan',
 'America/Bahia',
 'America/Bahia_Banderas',
 'America/Barbados',
 'America/Belem',
 'America/Belize',
 'America/Blanc-Sablon',
 'America/Boa_Vista',
 'America/Bogota',
 'America/Boise',
 'America/Cambridge_Bay',
 'America/Campo_Grande',
 'America/Cancun',
 'America/Caracas',
 'America/Cayenne',
 'America/Cayman',
 'America/Chicago',
 'America/Chihuahua',
 'America/Costa_Rica',
 'America/Creston',
 'America/Cuiaba',
 'America/Curacao',
 'America/Danmarkshavn',
 'America/Dawson',
 'America/Dawson_Creek',
 'America/Denver',
 'America/Detroit',
 'America/Dominica',
 'America/Edmonton',
 'America/Eirunepe',
 'America/El_Salvador',
 'America/Fortaleza',
 'America/Glace_Bay',
 'America/Godthab',
 'America/Goose_Bay',
 'America/Grand_Turk',
 'America/Grenada',
 'America/Guadeloupe',
 'America/Guatemala',
 'America/Guayaquil',
 'America/Guyana',
 'America/Halifax',
 'America/Havana',
 'America/Hermosillo',
 'America/Indiana/Indianapolis',
 'America/Indiana/Knox',
 'America/Indiana/Marengo',
 'America/Indiana/Petersburg',
 'America/Indiana/Tell_City',
 'America/Indiana/Vevay',
 'America/Indiana/Vincennes',
 'America/Indiana/Winamac',
 'America/Inuvik',
 'America/Iqaluit',
 'America/Jamaica',
 'America/Juneau',
 'America/Kentucky/Louisville',
 'America/Kentucky/Monticello',
 'America/Kralendijk',
 'America/La_Paz',
 'America/Lima',
 'America/Los_Angeles',
 'America/Lower_Princes',
 'America/Maceio',
 'America/Managua',
 'America/Manaus',
 'America/Marigot',
 'America/Martinique',
 'America/Matamoros',
 'America/Mazatlan',
 'America/Menominee',
 'America/Merida',
 'America/Metlakatla',
 'America/Mexico_City',
 'America/Miquelon',
 'America/Moncton',
 'America/Monterrey',
 'America/Montevideo',
 'America/Montreal',
 'America/Montserrat',
 'America/Nassau',
 'America/New_York',
 'America/Nipigon',
 'America/Nome',
 'America/Noronha',
 'America/North_Dakota/Beulah',
 'America/North_Dakota/Center',
 'America/North_Dakota/New_Salem',
 'America/Ojinaga',
 'America/Panama',
 'America/Pangnirtung',
 'America/Paramaribo',
 'America/Phoenix',
 'America/Port-au-Prince',
 'America/Port_of_Spain',
 'America/Porto_Velho',
 'America/Puerto_Rico',
 'America/Rainy_River',
 'America/Rankin_Inlet',
 'America/Recife',
 'America/Regina',
 'America/Resolute',
 'America/Rio_Branco',
 'America/Santa_Isabel',
 'America/Santarem',
 'America/Santiago',
 'America/Santo_Domingo',
 'America/Sao_Paulo',
 'America/Scoresbysund',
 'America/Shiprock',
 'America/Sitka',
 'America/St_Barthelemy',
 'America/St_Johns',
 'America/St_Kitts',
 'America/St_Lucia',
 'America/St_Thomas',
 'America/St_Vincent',
 'America/Swift_Current',
 'America/Tegucigalpa',
 'America/Thule',
 'America/Thunder_Bay',
 'America/Tijuana',
 'America/Toronto',
 'America/Tortola',
 'America/Vancouver',
 'America/Whitehorse',
 'America/Winnipeg',
 'America/Yakutat',
 'America/Yellowknife',
 'Antarctica/Casey',
 'Antarctica/Davis',
 'Antarctica/DumontDUrville',
 'Antarctica/Macquarie',
 'Antarctica/Mawson',
 'Antarctica/McMurdo',
 'Antarctica/Palmer',
 'Antarctica/Rothera',
 'Antarctica/South_Pole',
 'Antarctica/Syowa',
 'Antarctica/Vostok',
 'Arctic/Longyearbyen',
 'Asia/Aden',
 'Asia/Almaty',
 'Asia/Amman',
 'Asia/Anadyr',
 'Asia/Aqtau',
 'Asia/Aqtobe',
 'Asia/Ashgabat',
 'Asia/Baghdad',
 'Asia/Bahrain',
 'Asia/Baku',
 'Asia/Bangkok',
 'Asia/Beirut',
 'Asia/Bishkek',
 'Asia/Brunei',
 'Asia/Choibalsan',
 'Asia/Chongqing',
 'Asia/Colombo',
 'Asia/Damascus',
 'Asia/Dhaka',
 'Asia/Dili',
 'Asia/Dubai',
 'Asia/Dushanbe',
 'Asia/Gaza',
 'Asia/Harbin',
 'Asia/Hebron',
 'Asia/Ho_Chi_Minh',
 'Asia/Hong_Kong',
 'Asia/Hovd',
 'Asia/Irkutsk',
 'Asia/Jakarta',
 'Asia/Jayapura',
 'Asia/Jerusalem',
 'Asia/Kabul',
 'Asia/Kamchatka',
 'Asia/Karachi',
 'Asia/Kashgar',
 'Asia/Kathmandu',
 'Asia/Khandyga',
 'Asia/Kolkata',
 'Asia/Krasnoyarsk',
 'Asia/Kuala_Lumpur',
 'Asia/Kuching',
 'Asia/Kuwait',
 'Asia/Macau',
 'Asia/Magadan',
 'Asia/Makassar',
 'Asia/Manila',
 'Asia/Muscat',
 'Asia/Nicosia',
 'Asia/Novokuznetsk',
 'Asia/Novosibirsk',
 'Asia/Omsk',
 'Asia/Oral',
 'Asia/Phnom_Penh',
 'Asia/Pontianak',
 'Asia/Pyongyang',
 'Asia/Qatar',
 'Asia/Qyzylorda',
 'Asia/Rangoon',
 'Asia/Riyadh',
 'Asia/Sakhalin',
 'Asia/Samarkand',
 'Asia/Seoul',
 'Asia/Shanghai',
 'Asia/Singapore',
 'Asia/Taipei',
 'Asia/Tashkent',
 'Asia/Tbilisi',
 'Asia/Tehran',
 'Asia/Thimphu',
 'Asia/Tokyo',
 'Asia/Ulaanbaatar',
 'Asia/Urumqi',
 'Asia/Ust-Nera',
 'Asia/Vientiane',
 'Asia/Vladivostok',
 'Asia/Yakutsk',
 'Asia/Yekaterinburg',
 'Asia/Yerevan',
 'Atlantic/Azores',
 'Atlantic/Bermuda',
 'Atlantic/Canary',
 'Atlantic/Cape_Verde',
 'Atlantic/Faroe',
 'Atlantic/Madeira',
 'Atlantic/Reykjavik',
 'Atlantic/South_Georgia',
 'Atlantic/St_Helena',
 'Atlantic/Stanley',
 'Australia/Adelaide',
 'Australia/Brisbane',
 'Australia/Broken_Hill',
 'Australia/Currie',
 'Australia/Darwin',
 'Australia/Eucla',
 'Australia/Hobart',
 'Australia/Lindeman',
 'Australia/Lord_Howe',
 'Australia/Melbourne',
 'Australia/Perth',
 'Australia/Sydney',
 'Canada/Atlantic',
 'Canada/Central',
 'Canada/Eastern',
 'Canada/Mountain',
 'Canada/Newfoundland',
 'Canada/Pacific',
 'Europe/Amsterdam',
 'Europe/Andorra',
 'Europe/Athens',
 'Europe/Belgrade',
 'Europe/Berlin',
 'Europe/Bratislava',
 'Europe/Brussels',
 'Europe/Bucharest',
 'Europe/Budapest',
 'Europe/Busingen',
 'Europe/Chisinau',
 'Europe/Copenhagen',
 'Europe/Dublin',
 'Europe/Gibraltar',
 'Europe/Guernsey',
 'Europe/Helsinki',
 'Europe/Isle_of_Man',
 'Europe/Istanbul',
 'Europe/Jersey',
 'Europe/Kaliningrad',
 'Europe/Kiev',
 'Europe/Lisbon',
 'Europe/Ljubljana',
 'Europe/London',
 'Europe/Luxembourg',
 'Europe/Madrid',
 'Europe/Malta',
 'Europe/Mariehamn',
 'Europe/Minsk',
 'Europe/Monaco',
 'Europe/Moscow',
 'Europe/Oslo',
 'Europe/Paris',
 'Europe/Podgorica',
 'Europe/Prague',
 'Europe/Riga',
 'Europe/Rome',
 'Europe/Samara',
 'Europe/San_Marino',
 'Europe/Sarajevo',
 'Europe/Simferopol',
 'Europe/Skopje',
 'Europe/Sofia',
 'Europe/Stockholm',
 'Europe/Tallinn',
 'Europe/Tirane',
 'Europe/Uzhgorod',
 'Europe/Vaduz',
 'Europe/Vatican',
 'Europe/Vienna',
 'Europe/Vilnius',
 'Europe/Volgograd',
 'Europe/Warsaw',
 'Europe/Zagreb',
 'Europe/Zaporozhye',
 'Europe/Zurich',
 'GMT',
 'Indian/Antananarivo',
 'Indian/Chagos',
 'Indian/Christmas',
 'Indian/Cocos',
 'Indian/Comoro',
 'Indian/Kerguelen',
 'Indian/Mahe',
 'Indian/Maldives',
 'Indian/Mauritius',
 'Indian/Mayotte',
 'Indian/Reunion',
 'Pacific/Apia',
 'Pacific/Auckland',
 'Pacific/Chatham',
 'Pacific/Chuuk',
 'Pacific/Easter',
 'Pacific/Efate',
 'Pacific/Enderbury',
 'Pacific/Fakaofo',
 'Pacific/Fiji',
 'Pacific/Funafuti',
 'Pacific/Galapagos',
 'Pacific/Gambier',
 'Pacific/Guadalcanal',
 'Pacific/Guam',
 'Pacific/Honolulu',
 'Pacific/Johnston',
 'Pacific/Kiritimati',
 'Pacific/Kosrae',
 'Pacific/Kwajalein',
 'Pacific/Majuro',
 'Pacific/Marquesas',
 'Pacific/Midway',
 'Pacific/Nauru',
 'Pacific/Niue',
 'Pacific/Norfolk',
 'Pacific/Noumea',
 'Pacific/Pago_Pago',
 'Pacific/Palau',
 'Pacific/Pitcairn',
 'Pacific/Pohnpei',
 'Pacific/Port_Moresby',
 'Pacific/Rarotonga',
 'Pacific/Saipan',
 'Pacific/Tahiti',
 'Pacific/Tarawa',
 'Pacific/Tongatapu',
 'Pacific/Wake',
 'Pacific/Wallis',
 'US/Alaska',
 'US/Arizona',
 'US/Central',
 'US/Eastern',
 'US/Hawaii',
 'US/Mountain',
 'US/Pacific',
 'UTC']
common_timezones = LazyList(
            tz for tz in common_timezones if tz in all_timezones)

common_timezones_set = LazySet(common_timezones)


#
# Copyright 2015 BMC Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from boundary import ApiCli

class HostgroupDelete(ApiCli):
    def __init__(self):
        ApiCli.__init__(self)
        self.method = "DELETE"
        self.path = "v1/hostgroups"

        self.hostGroupId = ""
        self.force = False

    def add_arguments(self):
        ApiCli.add_arguments(self)
        self.parser.add_argument('-i', '--host-group-id', dest='hostGroupId', metavar='host_group_id',
                                 action='store', required=True, help='Host group id to delete')
        self.parser.add_argument('-f', '--force', dest='force', action='store_true',
                                 help='Remove the host group, even if in use by a dashboard or alarm')

    def get_arguments(self):
        """
        Extracts the specific arguments of this CLI
        """
        ApiCli.get_arguments(self)
        if self.args.hostGroupId is not None:
            self.hostGroupId = self.args.hostGroupId
        if self.args.force is not None:
            self.force = self.args.force

        if self.force:
            self.url_parameters = {"forceRemove": True}

        self.path = "v1/hostgroup/{0}".format(str(self.hostGroupId))

    def get_description(self):
        return "Deletes a host group definition by id from a {0} account".format(self.product_name)


#
# Copyright 2015 BMC Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import json

import requests
from boundary import ApiCli
from boundary.alarm_common import result_to_alarm

class AlarmModify(ApiCli):
    def __init__(self, update):
        ApiCli.__init__(self)
        self._update = update

        # Mapping of alarm types to corresponding codes
        self._alarm_types = {"threshold": 3, "host": 4, "api": 5}

        self._aggregate = None
        self._actions = None
        self._alarm_name = None
        self._alarm_id = None
        self._host_group_id = None
        self._interval = None
        self._is_disabled = None
        self._metric = None
        self._note = None
        self._operation = None
        self._per_host_notify = None
        self._threshold = None
        self._type_id = None
        self._notify_clear = None
        self._notify_set = None
        self._payload = {}
        self._trigger_interval = None
        self._timeout_interval = None

    def add_arguments(self):
        ApiCli.add_arguments(self)
        self.parser.add_argument('-m', '--metric', dest='metric', action='store',
                                 required=True,
                                 metavar='metric_name', help='Name of the metric to alarm')

        self.parser.add_argument('-g', '--trigger-aggregate', dest='aggregate', action='store',
                                 required=(False if self._update else True),
                                 choices=['sum', 'avg', 'max', 'min'], help='Metric aggregate to alarm upon')

        self.parser.add_argument('-o', '--trigger-operation', dest='operation', action='store',
                                 required=(False if self._update else True),
                                 choices=['eq', 'gt', 'lt'], help='Trigger threshold comparison')

        self.parser.add_argument('-v', '--trigger-threshold', dest='threshold', action='store',
                                 required=(False if self._update else True),
                                 metavar='value', help='Trigger threshold value')

        self.parser.add_argument('-r', '--trigger-interval', dest='trigger_interval', action='store',
                                 metavar='trigger_interval', required=False, type=int,
                                 help='Interval of time in ms that the alarm state should be in fault ' +
                                      'before triggering')

        self.parser.add_argument('-j', '--timeout-interval', dest='timeout_interval', action='store',
                                 metavar = 'timeout_interval', required=False, type=int,
                                 help='The interval after which an individual host state should resolve by timeout. ' +
                                      'Default: 259200000 milli-seconds (3 days)')

        self.parser.add_argument('-u', '--host-group-id', dest='host_group_id', action='store', metavar='host_group_id',
                                 type=int, help='Host group the alarm applies to')

        self.parser.add_argument('-d', '--note', dest='note', action='store', metavar='note',
                                 help='A description or resolution of the alarm')

        self.parser.add_argument('-k', '--action', dest='actions', action='append', metavar='action-id', type=int,
                                 help='An action to be performed when an alarm is triggered')

        self.parser.add_argument('-c', '--notify-clear', dest='notify_clear', action='store', default=None,
                                 choices=['true', 'false'],
                                 help='Perform actions when all hosts')

        self.parser.add_argument('-s', '--notify-set', dest='notify_set', action='store', default=None,
                                 choices=['true', 'false'],
                                 help='Perform actions when an alarm threshold and interval is breached.')

        self.parser.add_argument('-p', '--per-host-notify', dest='per_host_notify', action='store',
                                 default=None, choices=['true', 'false'],
                                 help='An alarm by default will run the associated actions when \
                                 any server in the host group violates the threshold, and then at the end when \
                                 all servers are back within the threshold. If perHostNotify is set to true, \
                                 the actions will run when ANY server in the group violates \
                                 and falls back within the threshold.')

        self.parser.add_argument('-x', '--is-disabled', dest='is_disabled', action='store', default=None,
                                 choices=['true', 'false'], help='Enable or disable the alarm definition')

    def get_arguments(self):
        """
        Extracts the specific arguments of this CLI
        """
        ApiCli.get_arguments(self)

        self._actions = self.args.actions if self.args.actions is not None else None
        self._alarm_name = self.args.alarm_name if self.args.alarm_name is not None else None

        self._metric = self.args.metric if self.args.metric is not None else None
        self._aggregate = self.args.aggregate if self.args.aggregate is not None else None
        self._operation = self.args.operation if self.args.operation is not None else None
        self._threshold = self.args.threshold if self.args.threshold is not None else None
        self._trigger_interval = self.args.trigger_interval if self.args.trigger_interval is not None else None
        self._host_group_id = self.args.host_group_id if self.args.host_group_id is not None else None
        self._note = self.args.note if self.args.note is not None else None
        self._per_host_notify = self.args.per_host_notify if self.args.per_host_notify is not None else None
        self._is_disabled = self.args.is_disabled if self.args.is_disabled is not None else None
        self._notify_clear = self.args.notify_clear if self.args.notify_clear is not None else None
        self._notify_set = self.args.notify_set if self.args.notify_set is not None else None
        self._timeout_interval = self.args.timeout_interval if self.args.timeout_interval is not None else None

    def get_api_parameters(self):

        # Create trigger predicate dictionary
        predicate = {}

        if self._aggregate is not None:
            predicate['agg'] = self._aggregate
        if self._operation is not None:
            predicate['op'] = self._operation
        if self._threshold is not None:
            predicate['val'] = float(self._threshold)

        if 'agg' in predicate or 'op' in predicate or 'val' in predicate:
            self._payload['triggerPredicate'] = predicate

        # Create payload dictionary
        if self._alarm_name:
            self._payload['name'] = self._alarm_name

        if self._host_group_id is not None:
            self._payload['hostgroupId'] = self._host_group_id

        if self._interval is not None:
            self._payload['triggerInterval'] = self._interval

        if self._is_disabled is not None:
            self._payload['isDisabled'] = self._is_disabled

        if self._metric is not None:
            self._payload['metric'] = self._metric

        if self._note is not None:
            self._payload['note'] = self._note

        if self._actions is not None:
            self._payload['actions'] = self._actions

        if self._per_host_notify is not None:
            self._payload['perHostNotify'] = True if self._per_host_notify == 'yes' else False

        if self._is_disabled is not None:
            self._payload['isDisabled'] = True if self._is_disabled == 'yes' else False

        if self._notify_clear is not None:
            self._payload['notifyClear'] = self._notify_clear

        if self._notify_set is not None:
            self._payload['notifySet'] = self._notify_set

        if self._timeout_interval is not None:
            self._payload['timeoutInterval'] = self._timeout_interval

        if self._trigger_interval is not None:
            self._payload['triggerInterval'] = self._trigger_interval

        self.data = json.dumps(self._payload, sort_keys=True)
        self.headers = {'Content-Type': 'application/json'}
        self.path = 'v2/alarms'

    def handle_key_word_args(self):

        self._actions = self._kwargs['actions'] if 'actions' in self._kwargs else None
        self._aggregate = self._kwargs['aggregate'] if 'aggregate' in self._kwargs else None
        self._alarm_name = self._kwargs['name'] if 'name' in self._kwargs else None
        self._alarm_id = self._kwargs['id'] if 'id' in self._kwargs else None
        self._operation = self._kwargs['operation'] if 'operation' in self._kwargs else None
        self._threshold = self._kwargs['threshold'] if 'threshold' in self._kwargs else None
        self._host_group_id = self._kwargs['host_group_id'] if 'host_group_id' in self._kwargs else None
        self._trigger_interval = self._kwargs['trigger_interval'] if 'trigger_interval' in self._kwargs else None
        self._metric = self._kwargs['metric'] if 'metric' in self._kwargs else None
        self._note = self._kwargs['note'] if 'note' in self._kwargs else None
        self._actions = self._kwargs['actions'] if 'actions' in self._kwargs else None
        self._timeout_interval = self._kwargs['timeout_interval'] if 'timeout_interval' in self._kwargs else None
        self._notify_clear = self._kwargs['notifyClear'] if 'notifyClear' in self._kwargs else None
        self._notify_set = self._kwargs['notifySet'] if 'notifySet' in self._kwargs else None

        self.data = json.dumps(self._payload, sort_keys=True)
        self.headers = {'Content-Type': 'application/json'}
        self.path = 'v2/alarms'

    def _handle_api_results(self):
        # Only process if we get HTTP result of 201
        if self._api_result.status_code == requests.codes.created:
            alarm_result = json.loads(self._api_result.text)
            return result_to_alarm(alarm_result)
        else:
            return None


#
# Copyright 2015 BMC Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from boundary import ApiCli
import requests
import json
import time

class RelayGetOutput(ApiCli):
    def __init__(self):
        ApiCli.__init__(self)
        self._meter = None
        self._since = None
        self._raw = None
        self._relay_output = None

    def add_arguments(self):
        ApiCli.add_arguments(self)
        self.parser.add_argument('-n', '--name', metavar='meter_name', dest='meter', action='store', required=True,
                                 help='Name of the meter to get output information')

        self.parser.add_argument('-s', '--since', metavar='since', dest='since', action='store', required=False,
                                 default=1, help='Pull since items from end of log')

        self.parser.add_argument('-r', '--raw', dest='raw', action='store_true', required=False,
                                 help='Send output in raw JSON format')

    def get_arguments(self):
        ApiCli.get_arguments(self)

        self._meter = self.args.meter if self.args.meter is not None else None
        self._since = self.args.since if self.args.since is not None else None
        self._raw = self.args.raw if self.args.raw is not None else None

        self.method = 'GET'
        self.path = 'v1/relays/{0}/output/{1}'.format(self._meter, self._since)

    def get_description(self):
        return 'Queries for relay output messages in a {0} account'.format(self.product_name)

    def _dump_text(self):
        """
        Send output in textual format
        """
        results = self._relay_output['result'];

        for l in results:
            dt = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(int(l[1]['ts'])))
            print("{0} {1} {2} {3}".format(l[0], dt, l[1]['type'], l[1]['msg']))

    def _dump_json(self):
        """
        Send raw JSON output
        """
        out = json.dumps(self._relay_output, sort_keys=True, indent=4, separators=(',', ': '))
        print(self.colorize_json(out))

    def _handle_results(self):
        """
        Call back function to be implemented by the CLI.
        """
        # Only process if we get HTTP result of 200
        if self._api_result.status_code == requests.codes.ok:
            self._relay_output = json.loads(self._api_result.text)
            if self._raw:
                self._dump_json()
            else:
                self._dump_text()


#
# Copyright 2015 BMC Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from boundary import PluginBase

class PluginAdd (PluginBase):

    def __init__(self):
        PluginBase.__init__(self)
        self.method = "PUT"
        self.path = "v1/plugins/private"

        self.pluginName = None
        self.organizationName = None
        self.repositoryName = None

    def add_arguments(self):
        PluginBase.add_arguments(self)
        self.parser.add_argument('-o', '--organization-name', dest='organizationName', action='store',
                                 required=True, metavar="organization_name",
                                 help='Name of the GitHub user or organization')
        self.parser.add_argument('-r', '--repository-name', dest='repositoryName', action='store',
                                 required=True, metavar="repository_name",
                                 help='Name of the GitHub repository')

    def get_arguments(self):
        """
        Extracts the specific arguments of this CLI
        """
        PluginBase.get_arguments(self)
        if self.args.organizationName is not None:
            self.organizationName = self.args.organizationName
        if self.args.repositoryName is not None:
            self.repositoryName = self.args.repositoryName

        self.path = "v1/plugins/private/{0}/{1}/{2}".format(self.pluginName, self.organizationName, self.repositoryName)

    def get_description(self):
        return 'Imports a meter plugin from a GitHub repository into a {0} account'.format(self.product_name)


#
# Copyright 2015 BMC Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from boundary import ApiCli

class Metric:
    def __init__(self, display_name=None):
        """
        :param display_name:
        :return:
        """
        self._display_name = display_name

    @property
    def display_name(self):
        return self._display_name

    @display_name.setter
    def display_name(self, value):
        self._display_name = value

    def __repr__(self):
        return 'Metric(display_name="{0}")'.format(self._display_name)

class Metrics:
    def __init__(self, metrics):
        self._metrics = metrics

    def __len__(self):
        return len(self._metrics)

    def __getitem__(self, position):
        metric_values = self._metrics["result"][position]
        metric = None
        if metric_values is not None:
            metric = Metric(display_name=metric_values["displayName"])

        return metric

    def __str__(self):
        return str(self._metrics)

class MetricCommon (ApiCli):

    def __init__(self):
        ApiCli.__init__(self)

    def extract_fields(self, metric):
        """
        Extract only the required fields for the create/update API call
        """
        m = {}
        if 'name' in metric:
            m['name'] = metric['name']
        if 'description' in metric:
            m['description'] = metric['description']
        if 'displayName' in metric:
            m['displayName'] = metric['displayName']
        if 'displayNameShort' in metric:
            m['displayNameShort'] = metric['displayNameShort']
        if 'unit' in metric:
            m['unit'] = metric['unit']
        if 'defaultAggregate' in metric:
            m['defaultAggregate'] = metric['defaultAggregate']
        if 'defaultResolutionMS' in metric:
            m['defaultResolutionMS'] = metric['defaultResolutionMS']
        if 'isDisabled' in metric:
            m['isDisabled'] = metric['isDisabled']
        if 'isBuiltin' in metric:
            m['isBuiltin'] = metric['isBuiltin']
        if 'type' in metric:
            m['type'] = metric['type']
        return m

    def metricDefinitionV2(self, metrics):
        return 'result' not in metrics


#
# Copyright 2015 BMC Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import json
import requests

from boundary import AlarmModify

class AlarmUpdate(AlarmModify):
    def __init__(self):
        AlarmModify.__init__(self, True)
        self._alarm_id = None

    def add_arguments(self):

        self.parser.add_argument('-i', '--alarm-id', dest='alarm_id', action='store', required=True, type=int,
                                 metavar='alarm_id', help='Id of the alarm to update')
        self.parser.add_argument('-n', '--alarm-name', dest='alarm_name', action='store',
                                 metavar='alarm_name', help='Name of the alarm')

        AlarmModify.add_arguments(self)

    def get_arguments(self):
        """
        Extracts the specific arguments of this CLI
        """

        AlarmModify.get_arguments(self)
        self._alarm_id = self.args.alarm_id if self.args.alarm_id is not None else None
        self.get_api_parameters()

    def handle_key_word_args(self):
        AlarmModify.handle_key_word_args(self)
        self._alarm_id = self._kwargs['id'] if 'id' in self._kwargs else None

    def get_api_parameters(self):
        AlarmModify.get_api_parameters(self)
        self.method = "PUT"
        if self._alarm_id is not None:
            self._payload['id'] = int(self._alarm_id)
        self.data = json.dumps(self._payload, sort_keys=True)
        self.path = "v2/alarms/{0}".format(self._alarm_id)

    def get_description(self):
        return 'Updates an alarm definition in an {0} account'.format(self.product_name)


#
# Copyright 2015 BMC Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import json

import requests
from boundary import ApiCli

class RelayList(ApiCli):
    def __init__(self):
        ApiCli.__init__(self)
        self.path = "v1/relays"
        self._control = None
        self._metrics = None
        self._plugins = None
        self._plugin_summary = None
        self._relays = None

    def add_arguments(self):
        ApiCli.add_arguments(self)

        self.parser.add_argument('--no-control', dest="control", action='store_true', required=False,
                                 help='Remove control field from the output')
        self.parser.add_argument('--no-metrics', dest="metrics", action='store_true', required=False,
                                 help='Remove metric field from the output')
        self.parser.add_argument('--no-plugins', dest="plugins", action='store_true', required=False,
                                 help='Remove plugins field from the output')
        # self.parser.add_argument('--plugin-summary', dest="plugin_summary", action='store_true', required=False,
        #                          help='Displays only a summary of the plugin output')

    def get_arguments(self):
        ApiCli.get_arguments(self)

        if self.args.control is not None:
            self._control = self.args.control
        if self.args.metrics is not None:
            self._metrics = self.args.metrics
        if self.args.plugins is not None:
            self._plugins = self.args.plugins
        # if self.args.plugin_summary is not None:
        #    self._plugin_summary = self.args.plugin_summary

    def get_description(self):
        return 'Lists the relays in a {0} account'.format(self.product_name)

    def _filter(self):
        """
        Apply the criteria to filter out on the output required
        """
        if self._metrics or self._control or self._plugins:
            relays = self._relays['result']['relays']
            for relay in relays:
                if self._metrics:
                    del relays[relay]['metrics']
                if self._control:
                    del relays[relay]['control']
                if self._plugins:
                    if 'plugins' in relays[relay]:
                        del relays[relay]['plugins']

    def _dump_json(self):
        out = json.dumps(self._relays, sort_keys=True, indent=4, separators=(',', ': '))
        print(self.colorize_json(out))

    def _handle_results(self):
        """
        Call back function to be implemented by the CLI.
        """

        # Only process if we get HTTP result of 200
        if self._api_result.status_code == requests.codes.ok:
            self._relays = json.loads(self._api_result.text)
            self._filter()
            self._dump_json()


from __future__ import division
from pyfortune.FortuneFile import FortuneFile
from pyfortune.CompiledFortuneFile import CompiledFortuneFile
from pyfortune.path import list_fortune, fortunepath
from operator import attrgetter
from bisect import bisect_left
from io import open

import logging
import os
import sys
import random

logger = logging.getLogger('pyfortune')

if int(sys.version[0]) < 3:
    from itertools import imap as map

def _load_fortune(file):
    compiled = file + '.ftc' # ForTune Compiled
    if os.path.isfile(compiled):
        try:
            logger.debug("Trying compiled file: %s", compiled)
            return CompiledFortuneFile(compiled)
        except ValueError as e:
            logger.warning("Can't use compiled file: %s: %s", compiled, e)
    fortune = FortuneFile(open(file, 'rb'))
    logging.info('Compiling: %s', file)
    try:
        fortune.compile(open(compiled, 'wb'))
    except IOError:
        logger.info('Unable to compile file: %s', file)
    return fortune

def load_fortune(file, path=None, offensive=False, lang=None):
    if os.path.isabs(file):
        return _load_fortune(file)
    if path is None:
        path = fortunepath
    else:
        path = path[:]
        path.extend(fortunepath)
    if lang is not None:
        oldpath = path
        path = path[:]
        path.extend(os.path.join(dir, lang) for dir in oldpath)
        del oldpath
    for dir in path:
        if offensive:
            test = os.path.join(dir, 'off', file)
        else:
            test = os.path.join(dir, file)
        if os.path.isfile(test):
            return _load_fortune(test)
        elif offensive is None:
            test = os.path.join(dir, 'off', file)
            if os.path.isfile(test):
                return _load_fortune(test)

def load_all(offensive=False, path=None, lang=None, include=False):
    if path is None:
        path = fortunepath
    else:
        path = path[:]
        path.extend(fortunepath)
    for file in list_fortune(offensive, path, lang, include):
        yield _load_fortune(file)

class Chooser(object):
    def __init__(self, offensive=None, path=None, equal=False,
                 lang=None, include=False):
        """Initialize based on all available fortune files"""
        # Let me explain:
        # files is a list of tuples (fortune, upper bound)
        # fortune is the file object, upper bound is the maximum number that will
        # result in this file being chosen, that has not been chosen by previous
        # files. It's important that upper bound increases when index in files
        # increases
        self.files = files = []
        count = 0
        for fortune in load_all(offensive, path, lang, include):
            count += 1 if equal else fortune.size
            files.append((fortune, count))
        self.count = count
        self.keys = [i[1] for i in self.files]

    @classmethod
    def fromlist(cls, files, equal=False, offensive=False, lang=None):
        """Initialize based on a list of fortune files"""
        self = cls.__new__(cls)
        self.files = fortunes = []
        count = 0
        for file in files:
            fortune = load_fortune(file, offensive=offensive, lang=lang)
            if fortune is None:
                logger.warn("Can't load: %s", file)
                continue
            count += 1 if equal else fortune.size
            fortunes.append((fortune, count))
        if not fortunes:
            raise ValueError('All fortune files specified are invalid')
        self.count = count
        self.keys = [i[1] for i in self.files]
        return self

    @classmethod
    def set_chance(cls, files, equal=False, offensive=False, lang=None): # where files are (name, chance)
        """Initialize based on a list of fortune files with set chances"""
        self = cls.__new__(cls)
        total = 0.
        file = []
        leftover = []
        for name, chance in files:
            if total >= 1:
                break
            fortune = load_fortune(name, offensive=offensive, lang=lang)
            if fortune is None or not fortune.size:
                continue
            if chance:
                file.append((fortune, chance))
                total += chance
            else:
                leftover.append(fortune)
        if leftover and total < 1:
            left = 1 - total
            if equal:
                perfile = left / len(leftover)
                for fortune in leftover:
                    file.append((fortune, perfile))
            else:
                entries = sum(map(attrgetter('size'), leftover))
                logger.debug('%d entries left', entries)
                for fortune in leftover:
                    chance = left * fortune.size / entries
                    file.append((fortune, chance))

        # Arbitrary limit to calculate upper bound with, nice round number
        self.count = count = 65536
        bound = 0
        self.files = fortunes = []
        for file, chance in file:
            bound += int(chance * count)
            fortunes.append((file, bound))
        self.keys = [i[1] for i in self.files]
        return self

    def choose_file(self):
        if not self.count:
            raise ValueError('No fortune files found')
        try:
            return self.files[bisect_left(self.keys, random.randrange(self.count))][0]
        except IndexError:
            raise RuntimeError('No fortune???')

    def choose(self, long=None, size=160, recurse=0):
        if recurse > 20:
            # What a bad luck, or may be you just can't find it
            return None
        file = self.choose_file()
        choice = file.choose(long, size)
        if choice is None:
            file, choice = self.choose(long, size, recurse+1)
        return file, choice


from twisted.python.reflect import namedAny as named_any
import click
# Sigh. http://twistedmatrix.com/trac/ticket/8267
import twisted.trial.unittest
import twisted.trial.reporter

from virtue import __version__
from virtue.reporters import ComponentizedReporter
from virtue.runner import run

class _Reporter(click.ParamType):

    name = "reporter"

    _BUILT_IN = {
        "bwverbose": twisted.trial.reporter.VerboseTextReporter,
        "default": ComponentizedReporter,
        "subunit": twisted.trial.reporter.SubunitReporter,
        "summary": twisted.trial.reporter.MinimalReporter,
        "text": twisted.trial.reporter.TextReporter,
        "timing": twisted.trial.reporter.TimingTextReporter,
        "tree": twisted.trial.reporter.TreeReporter,
        "verbose": twisted.trial.reporter.VerboseTextReporter,
    }

    def convert(self, value, param, ctx):
        if not isinstance(value, str):
            return value

        Reporter = self._BUILT_IN.get(value)
        if Reporter is not None:
            return Reporter()
        try:
            return named_any(value)
        except Exception:
            raise click.BadParameter(
                "{0!r} is not a known reporter".format(value),
            )

@click.command(context_settings=dict(help_option_names=["-h", "--help"]))
@click.version_option(version=__version__, prog_name="virtue")
@click.option(
    "--reporter",
    default=ComponentizedReporter(),
    help="the name of a reporter to use for outputting test results",
    type=_Reporter(),
)
@click.option(
    "-x", "--stop-after",
    default=None,
    count=True,
    help=(
        "stop the test run after unsuccessful results. May be "
        "repeated to stop after that many non-successes."
    )
)
@click.argument("tests", nargs=-1)
@click.pass_context
def main(context, **kwargs):
    """
    virtue discovers and runs tests found in the given objects.

    Provide it with one or more tests (packages, modules or objects) to run.

    """

    result = run(**kwargs)
    context.exit(not result.wasSuccessful())


#
# Copyright 2015 BMC Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import socket
import time
import json

from boundary import ApiCli
import requests

"""
Implements command line utility to add a measurement value to a Boundary account
Uses the following Boundary API:

http://premium-documentation.boundary.com/v1/post/measurements

"""

class MeasurementCreate(ApiCli):
    def __init__(self):
        ApiCli.__init__(self)
        self.method = "POST"
        self.path = "v1/measurements"
        self.metricName = None
        self.measurement = None
        self.source = None
        self.timestamp = None
        self._properties = None

    def add_arguments(self):
        ApiCli.add_arguments(self)
        self.parser.add_argument('-n', '--metric-name', dest='metricName', action='store', required=True,
                                 metavar='metric_name', help='Metric identifier')
        self.parser.add_argument('-m', '--measurement', dest='measurement', action='store', required=True,
                                 metavar="measurement", help='Measurement value')
        self.parser.add_argument('-s', '--source', dest='source', action='store', required=False, metavar="source",
                                 help='Source of measurement. Defaults to the host where the command is run')
        self.parser.add_argument('-d', '--timestamp', dest='timestamp', action='store', metavar="timestamp",
                                 help='Time of occurrence of the measurement in either epoch seconds or \
                                 epoch milliseconds. Defaults to the receipt time at api endpoint')
        self.parser.add_argument('-p', '--property', dest='properties', action='append', required=False,
                                 metavar='property=value',
                                 help='Property to add to the measurement')

    def _process_properties(self):
        """
        Transforms the command line properties into python dictionary
        :return:
        """
        if self.args.properties is not None:
            self._properties = {}
            for p in self.args.properties:
                d = p.split('=')
                self._properties[d[0]] = d[1]

    def get_arguments(self):
        """
        Extracts the specific arguments of this CLI
        """
        ApiCli.get_arguments(self)
        if self.args.metricName is not None:
            self.metricName = self.args.metricName

        if self.args.measurement is not None:
            self.measurement = self.args.measurement

        if self.args.source is not None:
            self.source = self.args.source
        else:
            self.source = socket.gethostname()

        if self.args.timestamp is not None:
            self.timestamp = int(self.args.timestamp)

        m = {'metric': self.metricName,
             'measure': self.measurement}

        if self.source is not None:
            m['source'] = self.source
        if self.timestamp is not None:
            m['timestamp'] = int(self.timestamp)

        self._process_properties()

        if self._properties is not None:
            m['metadata'] = self._properties

        self.data = json.dumps(m, sort_keys=True)
        self.headers = {'Content-Type': 'application/json', "Accept": "application/json"}

    def _call_api(self):
        """
        Override so we can handle the incomplete read error generated
        by Boundary API that creates a measurement.
        Handle here instead of ApiCli since we do not want to mask errors
        to other API calls.
        """
        try:
            ApiCli._call_api(self)
        except requests.exceptions.ChunkedEncodingError:
            pass

    def get_description(self):
        return 'Adds a measurement value to a {0} account'.format(self.product_name)

    def _handle_results(self):
        """
        Call back function to be implemented by the CLI.
        """

        # Only process if we get HTTP result of 200
        if self._api_result.status_code == requests.codes.ok:
            payload = json.loads(self._api_result.text)
            out = json.dumps(payload, sort_keys=True, indent=4, separators=(',', ': '))
            print(self.colorize_json(out))


#
# Copyright 2015 BMC Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from boundary import ApiCli

class HostgroupSearch(ApiCli):
    def __init__(self):
        ApiCli.__init__(self)
        self.method = "GET"
        self.path = "v1/hostgroups/search"
        self.hostGroupName = ""

    def add_arguments(self):
        ApiCli.add_arguments(self)
        self.parser.add_argument('-n', '--host-group-name', dest='hostGroupName', metavar="host_group_name",
                                 action='store', required=True,help='Host group name')

    def get_arguments(self):
        """
        Extracts the specific arguments of this CLI
        """
        ApiCli.get_arguments(self)
        if self.args.hostGroupName is not None:
            self.url_parameters = {"name": self.args.hostGroupName}

    def get_description(self):
        return 'Searches for Host Group by name in an {0} account'.format(self.product_name)


#
# Copyright 2015 BMC Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from boundary import ApiCli

class PluginGet(ApiCli):
    def __init__(self):
        ApiCli.__init__(self)
        self.method = "GET"
        self.path = "v1/plugins"
        self.plugin_name = None

    def add_arguments(self):
        ApiCli.add_arguments(self)

        self.parser.add_argument('-n', '--plugin-Name', dest='plugin_name', action='store', metavar='plugin_name',
                                 required=True, help='Plugin name')

    def get_arguments(self):
        """
        Extracts the specific arguments of this CLI
        """
        ApiCli.get_arguments(self)
        if self.args.plugin_name is not None:
            self.plugin_name = self.args.plugin_name

        self.path = "v1/plugins/{0}".format(self.plugin_name)

    def get_description(self):
        return 'Get the details of a plugin in a {0} account'.format(self.product_name)


#
# Copyright 2015 BMC Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""
Implements command to remove an alarm definition from a Boundary account.
"""
from boundary import ApiCli
import requests

"""
Uses the following Boundary API:

https://premium-api.boundary.com/v1/alarm/:alarmId
"""

class AlarmDelete(ApiCli):
    def __init__(self,  **kwargs):
        """
        """
        ApiCli.__init__(self)
        self._kwargs = kwargs
        self._alarm_id = None

    def add_arguments(self):
        """
        """
        ApiCli.add_arguments(self)

        self.parser.add_argument('-i', '--alarm-id', dest='alarm_id', action='store', required=True,
                                 metavar='alarm-id', help='Alarm identifier')

    def get_arguments(self):
        """
        Extracts the specific arguments of this CLI
        """
        ApiCli.get_arguments(self)
        self._alarm_id = self.args.alarm_id if self.args.alarm_id is not None else None

    def handle_key_word_args(self):
        self._alarm_id = self._kwargs['id'] if 'id' in self._kwargs else None

    def get_api_parameters(self):
        self.method = "DELETE"
        self.path = "v2/alarms/{0}".format(self._alarm_id)

    def get_description(self):
        return 'Deletes an alarm definition from a {0} account'.format(self.product_name)

    def _handle_results(self):
        """
        Handle the results of the API call
        """

        # Only process if we get HTTP return code other 200.
        if self._api_result.status_code != requests.codes.ok:
            print(self.colorize_json(self._api_result.text))

    def _handle_api_results(self):
        # Only process if we get HTTP result of 200
        if self._api_result.status_code != requests.codes.ok:
            pass
        return None

    def good_response(self, status_code):
        """
        Determines what status codes represent a good response from an API call.
        """
        return status_code == requests.codes.no_content