"""Define text roles for GitHub

* ghissue - Issue
* ghpull - Pull Request
* ghuser - User

Adapted from bitbucket example here:
https://bitbucket.org/birkenfeld/sphinx-contrib/src/tip/bitbucket/sphinxcontrib/bitbucket.py

Authors
-------

* Doug Hellmann
* Min RK
"""
#
# Original Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

from docutils import nodes, utils
from docutils.parsers.rst.roles import set_classes

def make_link_node(rawtext, app, type, slug, options):
    """Create a link to a github resource.

    :param rawtext: Text being replaced with link node.
    :param app: Sphinx application context
    :param type: Link type (issues, changeset, etc.)
    :param slug: ID of the thing to link to
    :param options: Options dictionary passed to role func.
    """

    try:
        base = app.config.github_project_url
        if not base:
            raise AttributeError
        if not base.endswith('/'):
            base += '/'
    except AttributeError as err:
        raise ValueError('github_project_url configuration value is not set (%s)' % str(err))

    ref = base + type + '/' + slug + '/'
    set_classes(options)
    prefix = "#"
    if type == 'pull':
        prefix = "PR " + prefix
    node = nodes.reference(rawtext, prefix + utils.unescape(slug), refuri=ref,
                           **options)
    return node

def ghissue_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
    """Link to a GitHub issue.

    Returns 2 part tuple containing list of nodes to insert into the
    document and a list of system messages.  Both are allowed to be
    empty.

    :param name: The role name used in the document.
    :param rawtext: The entire markup snippet, with role.
    :param text: The text marked with the role.
    :param lineno: The line number where rawtext appears in the input.
    :param inliner: The inliner instance that called us.
    :param options: Directive options for customization.
    :param content: The directive content for customization.
    """

    try:
        issue_num = int(text)
        if issue_num <= 0:
            raise ValueError
    except ValueError:
        msg = inliner.reporter.error(
            'GitHub issue number must be a number greater than or equal to 1; '
            '"%s" is invalid.' % text, line=lineno)
        prb = inliner.problematic(rawtext, rawtext, msg)
        return [prb], [msg]
    app = inliner.document.settings.env.app
    #app.info('issue %r' % text)
    if 'pull' in name.lower():
        category = 'pull'
    elif 'issue' in name.lower():
        category = 'issues'
    else:
        msg = inliner.reporter.error(
            'GitHub roles include "ghpull" and "ghissue", '
            '"%s" is invalid.' % name, line=lineno)
        prb = inliner.problematic(rawtext, rawtext, msg)
        return [prb], [msg]
    node = make_link_node(rawtext, app, category, str(issue_num), options)
    return [node], []

def ghuser_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
    """Link to a GitHub user.

    Returns 2 part tuple containing list of nodes to insert into the
    document and a list of system messages.  Both are allowed to be
    empty.

    :param name: The role name used in the document.
    :param rawtext: The entire markup snippet, with role.
    :param text: The text marked with the role.
    :param lineno: The line number where rawtext appears in the input.
    :param inliner: The inliner instance that called us.
    :param options: Directive options for customization.
    :param content: The directive content for customization.
    """
    # app = inliner.document.settings.env.app
    #app.info('user link %r' % text)
    ref = 'https://www.github.com/' + text
    node = nodes.reference(rawtext, text, refuri=ref, **options)
    return [node], []

def ghcommit_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
    """Link to a GitHub commit.

    Returns 2 part tuple containing list of nodes to insert into the
    document and a list of system messages.  Both are allowed to be
    empty.

    :param name: The role name used in the document.
    :param rawtext: The entire markup snippet, with role.
    :param text: The text marked with the role.
    :param lineno: The line number where rawtext appears in the input.
    :param inliner: The inliner instance that called us.
    :param options: Directive options for customization.
    :param content: The directive content for customization.
    """
    app = inliner.document.settings.env.app
    #app.info('user link %r' % text)
    try:
        base = app.config.github_project_url
        if not base:
            raise AttributeError
        if not base.endswith('/'):
            base += '/'
    except AttributeError as err:
        raise ValueError('github_project_url configuration value is not set (%s)' % str(err))

    ref = base + text
    node = nodes.reference(rawtext, text[:6], refuri=ref, **options)
    return [node], []

def setup(app):
    """Install the plugin.

    :param app: Sphinx application context.
    """
    app.info('Initializing GitHub plugin')
    app.add_role('ghissue', ghissue_role)
    app.add_role('ghpull', ghissue_role)
    app.add_role('ghuser', ghuser_role)
    app.add_role('ghcommit', ghcommit_role)
    app.add_config_value('github_project_url', None, 'env')
    return


"""
The command-line interface for happy!
"""
import json
import os
import subprocess
import sys

import click

from happy import Happy

def _infer_tarball_url():
    """Returns the tarball URL inferred from an app.json, if present."""
    try:
        with click.open_file('app.json', 'r') as f:
            contents = f.read()

        app_json = json.loads(contents)
    except IOError:
        return None

    repository = app_json.get('repository')

    if not repository:
        return None
    else:
        return app_json.get('repository') + '/tarball/master/'

def _write_app_name(app_name):
    """Writes the app name to the .happy file."""
    with click.open_file('.happy', 'w') as f:
        f.write(str(app_name))

def _read_app_name():
    """Reads the app name from the .happy file."""
    try:
        with click.open_file('.happy', 'r') as f:
            return f.read().strip()
    except IOError:
        return None

def _delete_app_name_file():
    """Deletes the .happy file. :("""
    try:
        os.remove('.happy')
    except (IOError, OSError):
        pass

@click.group(name='happy')
def cli():
    """Quickly set up and tear down Heroku apps!"""

@cli.command(name='up')
@click.option('--tarball-url', help='URL of the tarball containing app.json.')
@click.option('--auth-token', help='Heroku API auth token.')
@click.option('--env', multiple=True, help='Env override, e.g. KEY=value.')
@click.argument('app_name', required=False)
def up(tarball_url, auth_token, env, app_name):
    """Brings up a Heroku app."""
    tarball_url = tarball_url or _infer_tarball_url()

    if not tarball_url:
        click.echo('No tarball URL found.')
        sys.exit(1)

    if env:
        # Split ["KEY=value", ...] into {"KEY": "value", ...}
        env = {
            arg.split('=')[0]: arg.split('=')[1]
            for arg in env
        }

    happy = Happy(auth_token=auth_token)

    click.echo('Creating app... ', nl=False)

    build_id, app_name = happy.create(
        tarball_url=tarball_url,
        env=env,
        app_name=app_name,
    )

    click.echo(app_name)

    click.echo('Building... ', nl=False)

    happy.wait(build_id)

    _write_app_name(app_name)

    click.echo('done')
    click.echo("It's up! :) https://%s.herokuapp.com" % app_name)

@cli.command(name='down')
@click.option('--auth-token', help='Heroku API auth token.')
@click.option('--force', is_flag=True, help='Force deletion without input.')
@click.argument('app_name', required=False)
def down(auth_token, force, app_name):
    """Brings down a Heroku app."""
    if not app_name:
        click.echo(
            'WARNING: Inferring the app name when deleting is deprecated. '
            'Starting with happy 2.0, the app_name parameter will be required.'
        )

    app_name = app_name or _read_app_name()

    if not app_name:
        click.echo('No app name given.')
        sys.exit(1)

    if not force:
        click.confirm(
            'Are you sure you want to delete %s?' % app_name,
            abort=True,
        )

    happy = Happy(auth_token=auth_token)

    click.echo('Destroying app %s... ' % app_name, nl=False)

    happy.delete(app_name=app_name)

    _delete_app_name_file()

    click.echo('done')
    click.echo("It's down. :(")


"""Iterator attribute module"""

import collections
import inspect
from typing import Any, Union, Iterable, Callable

def iter_attribute(iterable_name) -> Union[Iterable, Callable]:
    """Decorator implementing Iterator interface with nicer manner.

    Example
    -------

    @iter_attribute('my_attr'):
    class DecoratedClass:
        ...

    Warning:
    ========

    When using PyCharm or MYPY you'll probably see issues with decorated class not being recognized as Iterator.
    That's an issue which I could not overcome yet, it's probably due to the fact that interpretation of object
    is being done statically rather than dynamically. MYPY checks for definition of methods in class code which
    changes at runtime. Since __iter__ and __next__ are added dynamically MYPY cannot find those
    defined in objects before object of a class is created. Possible workarounds for this issue are:

        1. Define ``dummy`` __iter__ class like:

            @iter_attribute('attr')
            class Test:
                def __init__(self) -> None:
                    self.attr = [1, 2, 3]

                def __iter__(self):
                    pass

        2. After creating object use cast or assert function denoting that particular instance inherits
            from collections.Iterator:

            assert isinstance(my_object, collections.Iterator)

    :param iterable_name: string representing attribute name which has to be iterated
    :return: DecoratedClass with implemented '__iter__' and '__next__' methods.
    """

    def create_new_class(decorated_class) -> Union[Iterable, Callable]:
        """Class extender implementing __next__ and __iter__ methods.

        :param decorated_class: class to be extended with iterator interface
        :return: new class
        """
        assert inspect.isclass(decorated_class), 'You can only decorate class objects!'
        assert isinstance(iterable_name, str), 'Please provide attribute name string'

        decorated_class.iterator_attr_index = 0

        def __iter__(instance) -> Iterable:
            """Implement __iter__ method

            :param instance: __iter__ uses instance of class which is being extended
            :return: instance of decorated_class
            """
            return instance

        def __next__(instance) -> Any:
            """Implement __next__ method

            :param instance: __next__ uses instance of class which is being extended
            :return: instance of decorated_class
            """
            assert hasattr(instance, iterable_name), \
                'Decorated object does not have attribute named {}'.format(iterable_name)
            assert isinstance(getattr(instance, iterable_name), collections.Iterable), \
                '{} of object {} is not iterable'.format(iterable_name, instance.__class__.__name__)
            ind = instance.iterator_attr_index
            while ind < len(getattr(instance, iterable_name)):
                val = getattr(instance, iterable_name)[ind]
                instance.iterator_attr_index += 1
                return val
            instance.iterator_attr_index = 0
            raise StopIteration

        dct = dict(decorated_class.__dict__)
        dct['__iter__'] = __iter__
        dct['__next__'] = __next__
        dct['iterator_attr_index'] = decorated_class.iterator_attr_index
        return type(decorated_class.__name__, (collections.Iterable,), dct)

    return create_new_class


import hmac
import hashlib
import base64
import urllib
import urlparse
import time

try:
    from django.http import JsonResponse
except ImportError:
    from .utility import JsonResponse

from .models import APIKeys
from . import API_AUTH_ALLOWED_TIME_DRIFT

def api_auth_encrypt(sk, msg):
    dig = hmac.new(str(sk), msg, digestmod=hashlib.sha256).digest()
    return base64.b64encode(dig).decode()

def _auth_url(url, body):
    url_parts = urlparse.urlparse(url)
    qs = urlparse.parse_qs(url_parts.query)  # dict, no sequence, for info query only
    sign = qs.get("signature", None)
    ak = qs.get("apikey", None)
    ts = qs.get("timestamp", None)

    qsl = filter(lambda x: x[0] != "signature", urlparse.parse_qsl(url_parts.query))

    if not (ak and sign and ts):
        raise Exception("parameter missing")

    # time stamp check, avoid replay attack
    if abs(time.time() - float(ts[0])) > API_AUTH_ALLOWED_TIME_DRIFT:
        raise Exception("time drift > %s sec" % API_AUTH_ALLOWED_TIME_DRIFT)

    user, sk = APIKeys.permission_check(ak[0], url_parts.path.lstrip("/"))
    if not (user and sk):  # user may be none
        raise Exception("entry point not allowed for the API key")

    orgiurl = urlparse.urlunparse(list(url_parts[0:4]) + [urllib.urlencode(qsl, True), ] + list(url_parts[5:]))
    if sign[0] != urllib.unquote_plus(api_auth_encrypt(sk, orgiurl+body if body else orgiurl)):
        raise Exception("signature error")
    return user

def api_auth(function=None):
    """
    check:
    1, timestamp in reasonable range
    2, api key exists
    3, entry point allowed for this api key
    4, signature correctness

    effect:
    - add user object in request if the api key is binding with an user
    """
    def real_decorator(func):
        def wrapped(request, *args, **kwargs):
            try:
                user = _auth_url(request.get_full_path(), request.body)
                request.user = user  # put user object in request
            except Exception as e:
                return JsonResponse({"error": str(e)}, status=403)
            return func(request, *args, **kwargs)
        setattr(wrapped, '__djapiauth__', True)
        return wrapped
    return real_decorator if not function else real_decorator(function)


# -*- coding: utf-8 -*-
import string
from random import choice, randint

def text(length, choices=string.ascii_letters):
    """ returns a random (fixed length) string

    :param length: string length
    :param choices: string containing all the chars can be used to build the string

    .. seealso::
       :py:func:`rtext`
    """
    return ''.join(choice(choices) for x in range(length))

def rtext(maxlength, minlength=1, choices=string.ascii_letters):
    """ returns a random (variable length) string.

        :param maxlength: maximum string length
        :param minlength: minimum string length
        :param choices: string containing all the chars can be used to build the string

        .. seealso::
           :py:func:`text`
        """
    return ''.join(choice(choices) for x in range(randint(minlength, maxlength)))


from random import randint
import string
from sample_data_utils.text import text

def digits(length):
    """
        return a random integer

    :param length: number of digits
    """
    return int(text(int(length), choices=string.digits))

def hexnum(length):
    """
        return a random  string represents hexadecimal number of `length` size

    :param length: length (without 0x prefix)
    """
    return text(int(length), choices=string.hexdigits)

def binary(length):
    """
        returns a a random string that represent a binary representation

    :param length: number of bits
    """
    num = randint(1, 999999)
    mask = '0' * length
    return (mask + ''.join([str(num >> i & 1) for i in range(7, -1, -1)]))[-length:]


from random import randrange, choice, shuffle
from netaddr import *
from sample_data_utils.numeric import hexnum

NOT_NET = [10, 127, 172, 192, 169]
PRIVATE_NET = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16']
DEDICATED_NET = ['100.64.0.0/10', ]
LOCAL_NET = ['169.254.0.0/16', '127.0.0.0/8']

NO_PUBLIC = PRIVATE_NET + DEDICATED_NET + LOCAL_NET + ['192.0.2.0/24', '192.88.99.0/24', '198.18.0.0/15']

def ipaddress(not_valid=None):
    """
        returns a string representing a random ip address

    :param not_valid: if passed must be a list of integers representing valid class A netoworks that must be ignored
    """
    not_valid_class_A = not_valid or []

    class_a = [r for r in range(1, 256) if r not in not_valid_class_A]
    shuffle(class_a)
    first = class_a.pop()

    return ".".join([str(first), str(randrange(1, 256)),
                     str(randrange(1, 256)), str(randrange(1, 256))])

def ip(private=True, public=True, max_attempts=10000):
    """
        returns a :class:`netaddr.IPAddress` instance with a random value

    :param private: if False does not return private networks
    :param public:  if False does not return public networks
    :param max_attempts:
    """
    if not (private or public):
        raise ValueError('Cannot disable both `private` and `public` network')

    if private != public:
        if private:
            is_valid = lambda address: address.is_private()
            not_valid = [n for n in range(1, 255) if n not in NOT_NET]
        else:
            is_valid = lambda address: not address.is_private()
            not_valid = NOT_NET

        attempt = 0
        while attempt < max_attempts:
            attempt += 1
            ip = IPAddress(ipaddress(not_valid))
            if is_valid(ip):
                return ip
    else:
        return IPAddress(ipaddress())

def ipv4():
    return IPAddress(ipaddress()).ipv4()

def ipv6():
    return IPAddress(ipaddress()).ipv6()

def node(network):
    return str(choice(list(IPNetwork(network))))

def mac_address(vendors=True):
    manufactures = ('00:24:e8:',  # Dell Inc.
                    '00:21:6a:',  # intel
                    '08:00:27:',  # cadmus computer systems
                    )
    if vendors:
        return choice(manufactures) + ":".join([hexnum(2) for i in range(3)])
    else:
        return ":".join([hexnum(2) for i in range(6)])


import datetime
import time
import random
from dateutil.rrule import rrule, DAILY, weekdays

def date_to_timestamp(d):
    return int(time.mktime(d.timetuple()))

def date(start, end):
    """Get a random date between two dates"""

    stime = date_to_timestamp(start)
    etime = date_to_timestamp(end)

    ptime = stime + random.random() * (etime - stime)

    return datetime.date.fromtimestamp(ptime)

def day(start, end, weekday=weekdays):
    return random.choice(
        list(
            rrule(DAILY,
                  byweekday=weekday,
                  dtstart=start,
                  until=end)
        )
    ).date()


"""
Heroku API helpers.
"""
import json

from requests import Session

class APIError(Exception):
    """A Heroku API error!!! Oh no!!!!!!!"""

class BuildError(Exception):
    """Something went wrong with the build!!!!!"""

class Heroku(object):
    """Methods for interacting with the Heroku API."""
    def __init__(self, auth_token=None):
        """Intialize the class.

        :param auth_token: A Heroku API auth token.
        """
        self._auth_token = auth_token

    def _get_session(self):
        """Returns a prepared ``Session`` instance."""
        session = Session()

        session.headers = {
            'Content-type': 'application/json',
            'Accept': 'application/vnd.heroku+json; version=3',
        }

        if self._auth_token:
            session.trust_env = False  # Effectively disable netrc auth
            session.headers['Authorization'] = 'Bearer %s' % self._auth_token

        return session

    def api_request(self, method, endpoint, data=None, *args, **kwargs):
        """Sends an API request to Heroku.

        :param method: HTTP method.
        :param endpoint: API endpoint, e.g. ``/apps``.
        :param data: A dict sent as JSON in the body of the request.
        :returns: A dict represntation of the JSON response.
        """
        session = self._get_session()

        api_root = 'https://api.heroku.com'
        url = api_root + endpoint

        if data:
            data = json.dumps(data)

        response = session.request(method, url, data=data, *args, **kwargs)

        if not response.ok:
            try:
                message = response.json().get('message')
            except ValueError:
                message = response.content

            raise APIError(message)

        return response.json()

    def create_build(self, tarball_url, env=None, app_name=None):
        """Creates an app-setups build. Returns response data as a dict.

        :param tarball_url: URL of a tarball containing an ``app.json``.
        :param env: Dict containing environment variable overrides.
        :param app_name: Name of the Heroku app to create.
        :returns: Response data as a ``dict``.
        """
        data = {
            'source_blob': {
                'url': tarball_url
            }
        }

        if env:
            data['overrides'] = {'env': env}

        if app_name:
            data['app'] = {'name': app_name}

        return self.api_request('POST', '/app-setups', data=data)

    def check_build_status(self, build_id):
        """Checks the status of an app-setups build.

        :param build_id: ID of the build to check.
        :returns: ``True`` if succeeded, ``False`` if pending.
        """
        data = self.api_request('GET', '/app-setups/%s' % build_id)

        status = data.get('status')

        if status == 'pending':
            return False
        elif status == 'succeeded':
            return True
        else:
            raise BuildError(str(data))

    def delete_app(self, app_name):
        """Deletes an app.

        :param app_name: Name of the app to delete.
        """
        self.api_request('DELETE', '/apps/%s' % app_name)


from functools import wraps
from sample_data_utils.exception import MaxAttemptException

def infinite():
    """
        auto inc generator
    """
    i = 0
    while 1:
        yield i
        i += 1

_sequence_counters = {}

def sequence(prefix, cache=None):
    """
    generator that returns an unique string

    :param prefix: prefix of string
    :param cache: cache used to store the last used number

    >>> next(sequence('abc'))
    'abc-0'
    >>> next(sequence('abc'))
    'abc-1'
    """
    if cache is None:
        cache = _sequence_counters
    if cache == -1:
        cache = {}

    if prefix not in cache:
        cache[prefix] = infinite()

    while cache[prefix]:
        yield "{0}-{1}".format(prefix, next(cache[prefix]))

def _get_memoized_value(func, args, kwargs):
    """Used internally by memoize decorator to get/store function results"""
    key = (repr(args), repr(kwargs))

    if not key in func._cache_dict:
        ret = func(*args, **kwargs)
        func._cache_dict[key] = ret

    return func._cache_dict[key]

def memoize(func):
    """Decorator that stores function results in a dictionary to be used on the
    next time that the same arguments were informed."""

    func._cache_dict = {}

    @wraps(func)
    def _inner(*args, **kwargs):
        return _get_memoized_value(func, args, kwargs)

    return _inner

_cache_unique = {}

def unique(func, num_args=0, max_attempts=100, cache=None):
    """
    wraps a function so that produce unique results

    :param func:
    :param num_args:

    >>> import random
    >>> choices = [1,2]
    >>> a = unique(random.choice, 1)
    >>> a,b = a(choices), a(choices)
    >>> a == b
    False
    """
    if cache is None:
        cache = _cache_unique

    @wraps(func)
    def wrapper(*args):
        key = "%s_%s" % (str(func.__name__), str(args[:num_args]))
        attempt = 0
        while attempt < max_attempts:
            attempt += 1
            drawn = cache.get(key, [])
            result = func(*args)
            if result not in drawn:
                drawn.append(result)
                cache[key] = drawn
                return result

        raise MaxAttemptException()

    return wrapper


from __future__ import print_function

from argparse import ArgumentParser, HelpFormatter

import sys

class BaseCommand(object):
    """
    The base command object

    :var description: The brief description of the command
    :var sub_commands: A dictionary mapping names to sub commands. Each value should be a class
        inheriting from ``BaseCommand``. If not set the first sentence of the docstring is used.
    :var help: The full help text to display. If not set the docstring is used.
    :var arg_parse_class: The class to use as the root argument parser (should extend or implement ``argparse.ArgumanetParser``)
    :var formatter_class: The class to use for formatting help text
    """
    description = None
    help = None
    sub_commands = {}
    arg_parse_class = ArgumentParser
    formatter_class = HelpFormatter

    def __init__(self, name=None, argv=None):
        """
        Creates the command
        :param name: The name the command is registered to
        :param argv: List of argument values. If ``None``, ``sys.argv[1:]`` is used.
        """
        self.name = name
        self._arg_parser = None
        self.argv = argv if argv is not None else sys.argv[1:]

    @property
    def sub_parser_dest_name(self):
        """
        The name of the argument the name of the sub command will be stored in
        """
        if self.name:
            return u'{0}__sub_command'.format(self.name)
        return 'sub_command'

    @property
    def arg_parser(self):
        if not self._arg_parser:
            self._arg_parser = self.get_root_argparser()
            self.add_args(self._arg_parser)
            self.register_sub_commands(self._arg_parser)

        return self._arg_parser

    def parse_args(self):
        """
        Parses the command line arguments

        :return: The arguments taken from the command line
        """
        return self.arg_parser.parse_args(self.argv)

    def add_args(self, parser):
        """
        Adds arguments to the argument parser. This is used to modify which arguments are processed by the command.

        For a full description of the argument parser see https://docs.python.org/3/library/argparse.html.

        :param parser: The argument parser object
        """
        pass

    def register_sub_commands(self, parser):
        """
        Add any sub commands to the argument parser.

        :param parser: The argument parser object
        """
        sub_commands = self.get_sub_commands()
        if sub_commands:
            sub_parsers = parser.add_subparsers(dest=self.sub_parser_dest_name)

            for name, cls in sub_commands.items():
                cmd = cls(name)

                sub_parser = sub_parsers.add_parser(name, help=name, description=cmd.get_help(), formatter_class=cmd.get_formatter_class())

                cmd.add_args(sub_parser)
                cmd.register_sub_commands(sub_parser)

    def get_root_argparser(self):
        """
        Gets the root argument parser object.
        """
        return self.arg_parse_class(description=self.get_help(), formatter_class=self.get_formatter_class())

    def get_sub_commands(self):
        """
        Gets a dictionary mapping names to sub commands. Values should be classes inheriting from Base.

        :return: The list of sub commands.
        """
        return self.sub_commands

    def get_description(self):
        """
        Gets the description of the command. If its not supplied the first sentence of the doc string is used.
        """
        if self.description:
            return self.description
        elif self.__doc__ and self.__doc__.strip():
            return self.__doc__.strip().split('.')[0] + '.'
        else:
            return ''

    def get_help(self):
        """
        Gets the help text for the command. If its not supplied the doc string is used.
        """
        if self.help:
            return self.help
        elif self.__doc__ and self.__doc__.strip():
            return self.__doc__.strip()
        else:
            return ''

    def get_formatter_class(self):
        """
        Gets the formatter class for formatting help text
        """
        return self.formatter_class

    def action(self, args):
        """
        Performs the action of the command.

        This should be implemented by sub classes.

        :param args: The arguments parsed from parse_args
        :return: The status code of the action (0 on success)
        """
        self.arg_parser.print_help()
        return 1

    def run(self, args=None):
        """
        Runs the command passing in the parsed arguments.

        :param args: The arguments to run the command with. If ``None`` the arguments
            are gathered from the argument parser. This is automatically set when calling
            sub commands and in most cases should not be set for the root command.

        :return: The status code of the action (0 on success)
        """
        args = args or self.parse_args()

        sub_command_name = getattr(args, self.sub_parser_dest_name, None)
        if sub_command_name:
            sub_commands = self.get_sub_commands()
            cmd_cls = sub_commands[sub_command_name]
            return cmd_cls(sub_command_name).run(args)

        return self.action(args) or 0


from django.forms import ModelForm
from django.contrib.auth import get_user_model
from django import forms
from .models import APIKeys

def get_api_key_form(userfilter={}):
    """
    userfileter: when binding api key with user, filter some users if necessary
    """
    class APIKeyForm(ModelForm):
        class Meta:
            model = APIKeys
            exclude = ("apitree",)
        user = forms.ModelChoiceField(queryset=get_user_model().objects.filter(**userfilter),
                                      required=True,)
    return APIKeyForm


coding = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
ecoding = coding + '-.'
codeset =  {
    'simple': {
        'coding': coding,
        'max_value':  61,
        'char': ',',
        'dchar': '',
        'none': '_',
        'value': lambda x: coding[x]
    },
    'text': {
        'coding': '',
        'max_value':  100,
        'none': '-1',
        'char': '|',
        'dchar': ',',
        'value': lambda x: '%.1f' % float(x)
    },
    'extended': {
        'coding':  ecoding,
        'max_value':  4095,
        'none':  '__',
        'dchar': '',
        'char': ',',
        'value': lambda x: '%s%s' % \
             (ecoding[int(float(x) / 64)], ecoding[int(x % 64)])
    }
}

class Encoder:
    """Data encoder that handles simple,text, and extended encodings

    Based on javascript encoding algorithm and pygooglecharts"""
    def __init__(self, encoding=None, scale=None, series=''):
        self.series = series or ''
        if encoding is None:
            encoding = 'text'
        assert(encoding in ('simple','text','extended')),\
            'Unknown encoding: %s'%encoding
        self.encoding = encoding
        self.scale = scale
        self.codeset = codeset[encoding]

    def scalevalue(self, value):
        return value # one day...
        if self.encoding != 'text' and self.scale and \
                isinstance(value, int) or isinstance(value, float):
            if type(self.scale) == type(()):
                lower,upper = self.scale
            else:
                lower,upper = 0,float(self.scale)
            value = int(round(float(value - lower) * \
                            self.codeset['max_value'] / upper))
        return min(value, self.codeset['max_value'])

    def encode(self,  *args, **kwargs):
        """Encode wrapper for a dataset with maximum value

        Datasets can be one or two dimensional
        Strings are ignored as ordinal encoding"""
        if isinstance(args[0], str):
            return self.encode([args[0]],**kwargs)
        elif isinstance(args[0], int) or isinstance(args[0], float):
            return self.encode([[args[0]]],**kwargs)
        if len(args)>1:
            dataset = args
        else:
            dataset = args[0]
        typemap = list(map(type,dataset))
        code = self.encoding[0]
        if type('') in typemap:
            data = ','.join(map(str,dataset))
        elif type([]) in typemap or type(()) in typemap:
            data = self.codeset['char'].join(map(self.encodedata, dataset))
        elif len(dataset) == 1 and hasattr(dataset[0], '__iter__'):
            data = self.encodedata(dataset[0])
        else:
            try:
                data = self.encodedata(dataset)
            except ValueError:
                data = self.encodedata(','.join(map(unicode,dataset)))
        if not '.' in data and code == 't':
            code = 'e'
        return '%s%s:%s'%(code,self.series,data)

    def encodedata(self, data):
        sub_data = []
        enc_size = len(self.codeset['coding'])
        for value in data:
            if value in (None,'None'):
                sub_data.append(self.codeset['none'])
            elif isinstance(value, str):
                sub_data.append(value)
            elif value >= -1:
                try:
                    sub_data.append(self.codeset['value'](self.scalevalue(value)))
                except:
                    raise ValueError('cannot encode value: %s'%value)
        return self.codeset['dchar'].join(sub_data)

    def decode(self, astr):
        e = astr[0]
        dec_data = []
        for data in astr[2:].split(self.codeset['char']):
            sub_data = []
            if e == 't':
                sub_data.extend(map(float, data.split(',')))
            elif e == 'e':
                flag = 0
                index = self.codeset['coding'].index
                for i in range(len(data)):
                    if not flag:
                        this,next = index(data[i]),index(data[i+1])
                        flag = 1
                        sub_data.append((64 * this) + next)
                    else: flag = 0
            elif e == 's':
                sub_data.extend(map(self.codeset['coding'].index, data))
            dec_data.append(sub_data)
        return dec_data


import re
from functools import lru_cache
from io import StringIO
from urllib.parse import quote_plus

import numpy as np
import pandas as pd
import requests

from .constants import (ACTIVITY_COLUMN_ORDER, ACTIVITY_COLUMN_TRANSLATION,
                        DEFAULT_HOST)
from .exceptions import (ActivityDoesNotExist, AthleteDoesNotExist,
                         GoldenCheetahNotAvailable)

class GoldenCheetahClient:
    """Class that provides access to GoldenCheetah's REST API
    Can be used to retrieve lists of and single activities, including raw data.
    """
    def __init__(self, athlete=None, host=DEFAULT_HOST):
        """Initialize GC client.

        Keyword arguments:
        athlete -- Full name of athlete
        host -- the full host (including \'http://\')
        """
        if athlete is not None:
            self.athlete = athlete
        self.host = host

    def get_athletes(self):
        """Get all available athletes
        This method is cached to prevent unnecessary calls to GC.
        """
        response = self._get_request(self.host)
        response_buffer = StringIO(response.text)

        return pd.read_csv(response_buffer)

    def get_activity_list(self):
        """Get activity list for client.athlete"""
        return self._request_activity_list(self.athlete)

    def get_athlete_zones(self):
        """Get athlete zones for client.athlete.
        Not implemented yet.
        """
        pass

    def get_activity(self, filename):
        """Get raw activity data for filename for self.athlete
        This call is slow and therefore this method is memory cached.

        Keyword arguments:
        filename -- filename of activity (e.g. \'2015_04_29_09_03_16.json\')
        """
        return self._request_activity_data(self.athlete, filename)

    def get_activities(self, filenames):
        """Get raw activity data for filenames for self.athlete
        This call is slow and therefore this method is memory cached.

        Keyword arguments:
        filenames -- filenames of activity (e.g. \'2015_04_29_09_03_16.json\')
        """
        return [self.get_activity(f) for f in filenames]

    def get_last_activities(self, n):
        """Get all activity data for the last activity

        Keyword arguments:
        """
        filenames = self.get_activity_list().iloc[-n:].filename.tolist()
        last_activities = [self.get_activity(f) for f in filenames]
        return last_activities

    def get_last_activity(self):
        """Get all activity data for the last activity

        Keyword arguments:
        """
        last_activities = self.get_last_activities(n=1)
        return last_activities[0]

    def _request_activity_list(self, athlete):
        """Actually do the request for activity list
        This call is slow and therefore this method is memory cached.

        Keyword arguments:
        athlete -- Full name of athlete
        """
        response = self._get_request(self._athlete_endpoint(athlete))
        response_buffer = StringIO(response.text)

        activity_list = pd.read_csv(
            filepath_or_buffer=response_buffer,
            parse_dates={'datetime': ['date', 'time']},
            sep=',\s*',
            engine='python'
        )
        activity_list.rename(columns=lambda x: x.lower(), inplace=True)
        activity_list.rename(
            columns=lambda x: '_' + x if x[0].isdigit() else x, inplace=True)

        activity_list['has_hr'] = activity_list.average_heart_rate.map(bool)
        activity_list['has_spd'] = activity_list.average_speed.map(bool)
        activity_list['has_pwr'] = activity_list.average_power.map(bool)
        activity_list['has_cad'] = activity_list.average_heart_rate.map(bool)
        activity_list['data'] = pd.Series(dtype=np.dtype("object"))
        return activity_list

    def _request_activity_data(self, athlete, filename):
        """Actually do the request for activity filename
        This call is slow and therefore this method is memory cached.

        Keyword arguments:
        athlete -- Full name of athlete
        filename -- filename of request activity (e.g. \'2015_04_29_09_03_16.json\')
        """
        response = self._get_request(self._activity_endpoint(athlete, filename)).json()

        activity = pd.DataFrame(response['RIDE']['SAMPLES'])
        activity = activity.rename(columns=ACTIVITY_COLUMN_TRANSLATION)

        activity.index = pd.to_timedelta(activity.time, unit='s')
        activity.drop('time', axis=1, inplace=True)

        return activity[[i for i in ACTIVITY_COLUMN_ORDER if i in activity.columns]]

    def _athlete_endpoint(self, athlete):
        """Construct athlete endpoint from host and athlete name

        Keyword arguments:
        athlete -- Full athlete name
        """
        return '{host}{athlete}'.format(
            host=self.host,
            athlete=quote_plus(athlete)
        )

    def _activity_endpoint(self, athlete, filename):
        """Construct activity endpoint from host, athlete name and filename

        Keyword arguments:
        athlete -- Full athlete name
        filename -- filename of request activity (e.g. \'2015_04_29_09_03_16.json\')
        """
        return '{host}{athlete}/activity/{filename}'.format(
            host=self.host,
            athlete=quote_plus(athlete),
            filename=filename
        )

    @lru_cache(maxsize=256)
    def _get_request(self, endpoint):
        """Do actual GET request to GC REST API
        Also validates responses.

        Keyword arguments:
        endpoint -- full endpoint for GET request
        """
        try:
            response = requests.get(endpoint)
        except requests.exceptions.RequestException:
            raise GoldenCheetahNotAvailable(endpoint)

        if response.text.startswith('unknown athlete'):
            match = re.match(
                pattern='unknown athlete (?P<athlete>.+)',
                string=response.text)
            raise AthleteDoesNotExist(
                athlete=match.groupdict()['athlete'])

        elif response.text == 'file not found':
            match = re.match(
                pattern='.+/activity/(?P<filename>.+)',
                string=endpoint)
            raise ActivityDoesNotExist(
                filename=match.groupdict()['filename'])

        return response


import os
import re
import shutil
import sys
from setuptools import setup, find_packages

# allow setup.py to be run from any path
os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))

def get_readme():
    with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme:
        return readme.read()

def get_version():
    """
    Return package version as listed in `__version__` in `init.py`.
    """
    with open(os.path.join(os.path.dirname(__file__), 'argparsetree', '__init__.py')) as init_py:
        return re.search('__version__ = [\'"]([^\'"]+)[\'"]', init_py.read()).group(1)

version = get_version()

if sys.argv[-1] == 'publish':
    if os.system('pip freeze | grep wheel'):
        print('wheel not installed.\nUse `pip install wheel`.\nExiting.')
        sys.exit()
    if os.system('pip freeze | grep twine'):
        print('twine not installed.\nUse `pip install twine`.\nExiting.')
        sys.exit()
    os.system('python setup.py sdist bdist_wheel')
    os.system('twine upload dist/*')
    print('You probably want to also tag the version now:')
    print('  git tag -a {v} -m \'version {v}\''.format(v=version))
    print('  git push --tags')
    shutil.rmtree('dist')
    shutil.rmtree('build')
    shutil.rmtree('argparsetree.egg-info')
    sys.exit()

setup(
    name='argparsetree',
    version=version,
    packages=find_packages(exclude=('tests', 'tests.*')),
    include_package_data=True,
    package_data={
        '': [
            'requirements-package.in',
            'LICENSE',
        ],
    },
    exclude_package_data={
        '': ['__pycache__', '*.py[co]'],
    },
    license='MIT',
    description='Package for creating complex command line argument trees using argparse',
    long_description=get_readme(),
    url='https://github.com/wildfish/argparsetree',
    author='Wildfish',
    author_email='developers@wildfish.com',
    keywords='class based argparse',
    classifiers=[
        'Development Status :: 4 - Beta',
        'License :: OSI Approved :: BSD License',
        'Operating System :: OS Independent',
        'Programming Language :: Python',
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.5',
        'Programming Language :: Python :: 3.6',
    ],
)


"""
Quickly set up and tear down Heroku apps!
"""
from .heroku import Heroku

from time import sleep

class Happy(object):
    """The happiest interface of all."""
    def __init__(self, auth_token=None):
        """Initializes the class.

        :param auth_token: A Heroku API auth token.
        """
        self._api = Heroku(auth_token=auth_token)

    def create(self, tarball_url, env=None, app_name=None):
        """Creates a Heroku app-setup build.

        :param tarball_url: URL of a tarball containing an ``app.json``.
        :param env: (optional) Dict containing environment variable overrides.
        :param app_name: (optional) Name of the Heroku app to create.
        :returns: A tuple with ``(build_id, app_name)``.
        """
        data = self._api.create_build(
            tarball_url=tarball_url,
            env=env,
            app_name=app_name,
        )

        return (data['id'], data['app']['name'])

    def wait(self, build_id):
        """Waits for an app-setup build to finish.

        :param build_id: ID of the app-setup build for which to wait.
        """
        while True:
            if self._api.check_build_status(build_id):
                break
            sleep(3)

    def delete(self, app_name):
        """Deletes a Heroku app.

        :param app_name: Name of the Heroku app to delete.
        """
        self._api.delete_app(app_name=app_name)


import json
import datetime
import decimal
from django.utils import six
from django.http import HttpResponse
from django.core.urlresolvers import RegexURLResolver
from django.conf.urls import url
from django.utils.module_loading import import_by_path
from django.views.decorators.csrf import csrf_exempt

def is_aware(value):
    """
    Determines if a given datetime.datetime is aware.

    The logic is described in Python's docs:
    http://docs.python.org/library/datetime.html#datetime.tzinfo
    """
    return value.tzinfo is not None and value.tzinfo.utcoffset(value) is not None

class DjangoJSONEncoder(json.JSONEncoder):
    """
    JSONEncoder subclass that knows how to encode date/time and decimal types.
    """
    def default(self, o):
        # See "Date Time String Format" in the ECMA-262 specification.
        if isinstance(o, datetime.datetime):
            r = o.isoformat()
            if o.microsecond:
                r = r[:23] + r[26:]
            if r.endswith('+00:00'):
                r = r[:-6] + 'Z'
            return r
        elif isinstance(o, datetime.date):
            return o.isoformat()
        elif isinstance(o, datetime.time):
            if is_aware(o):
                raise ValueError("JSON can't represent timezone-aware times.")
            r = o.isoformat()
            if o.microsecond:
                r = r[:12]
            return r
        elif isinstance(o, decimal.Decimal):
            return str(o)
        else:
            return super(DjangoJSONEncoder, self).default(o)

class JsonResponse(HttpResponse):
    """
    An HTTP response class that consumes data to be serialized to JSON.

    :param data: Data to be dumped into json. By default only ``dict`` objects
      are allowed to be passed due to a security flaw before EcmaScript 5. See
      the ``safe`` parameter for more information.
    :param encoder: Should be an json encoder class. Defaults to
      ``django.core.serializers.json.DjangoJSONEncoder``.
    :param safe: Controls if only ``dict`` objects may be serialized. Defaults
      to ``True``.
    """

    def __init__(self, data, encoder=DjangoJSONEncoder, safe=True, **kwargs):
        if safe and not isinstance(data, dict):
            raise TypeError('In order to allow non-dict objects to be '
                            'serialized set the safe parameter to False')
        kwargs.setdefault('content_type', 'application/json')
        data = json.dumps(data, cls=encoder)
        super(JsonResponse, self).__init__(content=data, **kwargs)

def url_with_auth(regex, view, kwargs=None, name=None, prefix=''):
    """
    if view is string based, must be a full path
    """
    from djapiauth.auth import api_auth
    if isinstance(view, six.string_types):  # view is a string, must be full path
        return url(regex, api_auth(import_by_path(prefix + "." + view if prefix else view)))
    elif isinstance(view, (list, tuple)):  # include
        return url(regex, view, name, prefix, **kwargs)
    else:  # view is an object
        return url(regex, api_auth(view))

def is_protected_api(u):
    """ if a url is registered as protected """
    return u.callback and getattr(u.callback, "__djapiauth__", False)

def traverse_urls(urlpattern, prefixre=[], prefixname=[], patternFunc=None, resolverFunc=None):
    """
    urlpattern: urlpattern object

    prefix?? : for multiple level urls defined in different urls.py files
    prefixre: compiled regex object
    prefixnames: regex text

    patternFunc: function to process RegexURLPattern
    resolverFunc: function to process RegexURLResolver
    """
    for u in urlpattern:
        if isinstance(u, RegexURLResolver):  # inspect sub urls
            if resolverFunc:
                resolverFunc(u, prefixre, prefixname)

            traverse_urls(u.url_patterns,
                          prefixre + [u.regex, ],
                          prefixname + [u._regex, ],
                          patternFunc,
                          resolverFunc)
        else:
            if patternFunc:
                patternFunc(u, prefixre, prefixname)

class AuthMixin(object):
    """
    for class-based views
    derived classed can turn auth off by set api_auth to False
    """
    api_auth = True

    @classmethod
    def as_view(cls, **initkwargs):
        from djapiauth.auth import api_auth
        view = super(AuthMixin, cls).as_view(**initkwargs)
        view = api_auth(view) if cls.api_auth else view
        return csrf_exempt(view) if "post" in cls.http_method_names else view


import random
from sample_data_utils.storage import load_data
from sample_data_utils.text import rtext
from sample_data_utils.utils import memoize

GENDER_MALE = 'm'
GENDER_FEMALE = 'f'

@memoize
def _get_firstnames(language, gender):
    fn = load_data(language, "firstnames_%s.txt" % gender)
    return fn

@memoize
def _get_lastnames(language):
    fn = load_data(language, "lastnames.txt")
    return fn

@memoize
def _get_titles(languages=None):
    choices = []
    languages = languages or ['en']
    for lang in languages:
        fn = load_data(lang, "title.txt")
        choices.extend([line.split('/') for line in fn])
    return choices

def title(languages=None, genders=None):
    """
    returns a random title

    .. code-block:: python

        >>> d.title()
        u'Mrs.'
        >>> d.title(['es'])
        u'El Sr.'
        >>> d.title(None, [GENDER_FEMALE])
        u'Mrs.'

    :param languages: list of allowed languages. ['en'] if None
    :param genders: list of allowed genders. (GENDER_FEMALE, GENDER_MALE) if None
    """
    languages = languages or ['en']
    genders = genders or (GENDER_FEMALE, GENDER_MALE)

    choices = _get_titles(languages)
    gender = {'m':0, 'f':1}[random.choice(genders)]

    return random.choice(choices)[gender]

def gender():
    """
    randomly returns 'm' or 'f'
    """
    return random.choice((GENDER_FEMALE, GENDER_MALE))

def person(languages=None, genders=None):
    """
    returns a random tuple representing person information

    .. code-block:: python

        >>> d.person()
        (u'Derren', u'Powell', 'm')
        >>> d.person(genders=['f'])
        (u'Marge', u'Rodriguez', u'Mrs.', 'f')
        >>> d.person(['es'],['m'])
        (u'Jacinto', u'Delgado', u'El Sr.', 'm')

    :param language:
    :param genders:
    """
    languages = languages or ['en']
    genders = genders or (GENDER_FEMALE, GENDER_MALE)

    lang = random.choice(languages)
    g = random.choice(genders)
    t = title([lang], [g])
    return first_name([lang], [g]), last_name([lang]), t, g

def name():
    return rtext(30, 10).capitalize()

def fullname():
    return "%s %s" % (name(), name())

def first_name(languages=None, genders=None):
    """
        return a random first name
    :return:

    >>> from mock import patch
    >>> with patch('%s._get_firstnamess' % __name__, lambda *args: ['aaa']):
    ...     first_name()
    'Aaa'
    """
    choices = []
    languages = languages or ['en']
    genders = genders or [GENDER_MALE, GENDER_FEMALE]
    for lang in languages:
        for gender in genders:
            samples = _get_firstnames(lang, gender)
            choices.extend(samples)
    return random.choice(choices).title()

def last_name(languages=None):
    """
        return a random last name

    >>> from mock import patch
    >>> with patch('%s._get_lastnames' % __name__, lambda *args: ['aaa']):
    ...     last_name()
    'Aaa'
    >>> with patch('%s.get_lastnames' % __name__, lambda lang: ['%s_lastname'% lang]):
    ...     last_name(['it'])
    'It_Lastname'
    """
    choices = []
    languages = languages or ['en']
    for lang in languages:
        samples = _get_lastnames(lang)
        choices.extend(samples)
    return random.choice(choices).title()


# -*- coding: utf-8 -*-
################################################################################
#  GChartWrapper - v0.8
#  Copyright (C) 2009  Justin Quick <justquick@gmail.com>
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License version 3 as published
#  by the Free Software Foundation.
#
#  Thanks to anyone who does anything for this project.
#  If you have even the smallest revision, please email me at above address.
################################################################################
"""
GChartWrapper - Google Chart API Wrapper

The wrapper can render the URL of the Google chart based on your parameters.
With the chart you can render an HTML img tag to insert into webpages on the fly,
show it directly in a webbrowser, or save the chart PNG to disk. New versions
can generate PIL PngImage instances.

Example

    >>> G = GChart('lc',['simpleisbetterthancomplexcomplexisbetterthancomplicated'])
    >>> G.title('The Zen of Python','00cc00',36)
    >>> G.color('00cc00')
    >>> str(G)
    'http://chart.apis.google.com/chart?
        chd=e:simpleisbetterthancomplexcomplexisbetterthancomplicated
        &chs=300x150
        &cht=lc
        &chtt=The+Zen+of+Python'
    >>> G.image() # PIL instance
    <PngImagePlugin.PngImageFile instance at ...>
    >>> 1#G.show() # Webbrowser open
    True
    >>> G.save('tmp.png') # Save to disk
    'tmp.png'

See tests.py for unit test and other examples
"""
from GChartWrapper.constants import *
from GChartWrapper.encoding import Encoder
from copy import copy

def lookup_color(color):
    """
    Returns the hex color for any valid css color name

    >>> lookup_color('aliceblue')
    'F0F8FF'
    """
    if color is None: return
    color = color.lower()
    if color in COLOR_MAP:
        return COLOR_MAP[color]
    return color

def color_args(args, *indexes):
    """
    Color a list of arguments on particular indexes

    >>> c = color_args([None,'blue'], 1)
    >>> c.next()
    None
    >>> c.next()
    '0000FF'
    """
    for i,arg in enumerate(args):
        if i in indexes:
            yield lookup_color(arg)
        else:
            yield arg

class Axes(dict):
    """
    Axes attribute dictionary storage

    Use this class via GChart(...).axes
    Methods are taken one at a time, like so:

    >>> G = GChart()
    >>> G.axes.type('xy')
    {}
    >>> G.axes.label(1,'Label1') # X Axis
    {}
    >>> G.axes.label(2,'Label2') # Y Axis
    {}
    """
    def __repr__(self):
        return '<GChart.Axes %s>' % dict(self.items())

    def __init__(self, parent):
        self.parent = parent
        self.data = {'ticks':[],'labels':[],'positions':[],
            'ranges':[],'styles':[]}
        dict.__init__(self)

    def tick(self, index, length):
        """
        Add tick marks in order of axes by width
        APIPARAM: chxtc     <axis index>,<length of tick mark>
        """
        assert int(length) <= 25, 'Width cannot be more than 25'
        self.data['ticks'].append('%s,%d'%(index,length))
        return self.parent

    def type(self, atype):
        """
        Define the type of axes you wish to use
        atype must be one of x,t,y,r
        APIPARAM: chxt
        """
        for char in atype:
            assert char in 'xtyr', 'Invalid axes type: %s'%char
        if not ',' in atype:
            atype = ','.join(atype)
        self['chxt'] = atype
        return self.parent
    __call__ = type

    def label(self, index, *args):
        """
        Label each axes one at a time
        args are of the form <label 1>,...,<label n>
        APIPARAM: chxl
        """
        self.data['labels'].append(
            str('%s:|%s'%(index, '|'.join(map(str,args)) )).replace('None','')
        )
        return self.parent

    def position(self, index, *args):
        """
        Set the label position of each axis, one at a time
        args are of the form <label position 1>,...,<label position n>
        APIPARAM: chxp
        """
        self.data['positions'].append(
            str('%s,%s'%(index, ','.join(map(str,args)))).replace('None','')
        )
        return self.parent

    def range(self, index, *args):
        """
        Set the range of each axis, one at a time
        args are of the form <start of range>,<end of range>,<interval>
        APIPARAM: chxr
        """
        self.data['ranges'].append('%s,%s'%(index,
                                    ','.join(map(smart_str, args))))
        return self.parent

    def style(self, index, *args):
        """
        Add style to your axis, one at a time
        args are of the form::
            <axis color>,
            <font size>,
            <alignment>,
            <drawing control>,
            <tick mark color>
        APIPARAM: chxs
        """
        args = color_args(args, 0)
        self.data['styles'].append(
            ','.join([str(index)]+list(map(str,args)))
        )
        return self.parent

    def render(self):
        """Render the axes data into the dict data"""
        for opt,values in self.data.items():
            if opt == 'ticks':
                self['chxtc'] = '|'.join(values)
            else:
                self['chx%s'%opt[0]] = '|'.join(values)
        return self

class GChart(dict):
    """Main chart class

    Chart type must be valid for cht parameter
    Dataset can be any python iterable and be multi dimensional
    Kwargs will be put into chart API params if valid"""
    def __init__(self, ctype=None, dataset=[], **kwargs):
        self._series = kwargs.pop('series',None)
        self.lines,self.fills,self.markers,self.scales = [],[],[],[]
        self._geo,self._ld = '',''
        self._dataset = dataset
        dict.__init__(self)
        if ctype:
            self['cht'] = self.check_type(ctype)
        self._encoding = kwargs.pop('encoding', None)
        self._scale = kwargs.pop('scale', None)
        self._apiurl = kwargs.pop('apiurl', APIURL)
        for k in kwargs:
            assert k in APIPARAMS, 'Invalid chart parameter: %s' % k
        self.update(kwargs)
        self.axes = Axes(self)

    @classmethod
    def fromurl(cls, qs):
        """
        Reverse a chart URL or dict into a GChart instance

        >>> G = GChart.fromurl('http://chart.apis.google.com/chart?...')
        >>> G
        <GChartWrapper.GChart instance at...>
        >>> G.image().save('chart.jpg','JPEG')
        """
        if isinstance(qs, dict):
            return cls(**qs)
        return cls(**dict(parse_qsl(qs[qs.index('?')+1:])))

    ###################
    # Callables
    ###################
    def map(self, geo, country_codes):
        """
        Creates a map of the defined geography with the given country/state codes
        Geography choices are africa, asia, europe, middle_east, south_america, and world
        ISO country codes can be found at http://code.google.com/apis/chart/isocodes.html
        US state codes can be found at http://code.google.com/apis/chart/statecodes.html
        APIPARAMS: chtm & chld
        """
        assert geo in GEO, 'Geograpic area %s not recognized'%geo
        self._geo = geo
        self._ld = country_codes
        return self

    def level_data(self, *args):
        """
        Just used in QRCode for the moment
        args are error_correction,margin_size
        APIPARAM: chld
        """
        assert args[0].lower() in 'lmqh', 'Unknown EC level %s'%level
        self['chld'] = '%s|%s'%args
        return self

    def bar(self, *args):
        """
        For bar charts, specify bar thickness and spacing with the args
        args are <bar width>,<space between bars>,<space between groups>
        bar width can be relative or absolute, see the official doc
        APIPARAM: chbh
        """
        self['chbh'] = ','.join(map(str,args))
        return self

    def encoding(self, arg):
        """
        Specifies the encoding to be used for the Encoder
        Must be one of 'simple','text', or 'extended'
        """
        self._encoding = arg
        return self

    def output_encoding(self, encoding):
        """
        Output encoding to use for QRCode encoding
        Must be one of 'Shift_JIS','UTF-8', or 'ISO-8859-1'
        APIPARAM: choe
        """
        assert encoding in ('Shift_JIS','UTF-8','ISO-8859-1'),\
            'Unknown encoding %s'%encoding
        self['choe'] = encoding
        return self

    def scale(self, *args):
        """
        Scales the data down to the given size
        args must be of the form::
            <data set 1 minimum value>,
            <data set 1 maximum value>,
            <data set n minimum value>,
            <data set n maximum value>
        will only work with text encoding!
        APIPARAM: chds
        """
        self._scale =  [','.join(map(smart_str, args))]
        return self

    def dataset(self, data, series=''):
        """
        Update the chart's dataset, can be two dimensional or contain string data
        """
        self._dataset = data
        self._series = series
        return self

    def marker(self, *args):
        """
        Defines markers one at a time for your graph
        args are of the form::
            <marker type>,
            <color>,
            <data set index>,
            <data point>,
            <size>,
            <priority>
        see the official developers doc for the complete spec
        APIPARAM: chm
        """
        if len(args[0]) == 1:
            assert args[0] in MARKERS, 'Invalid marker type: %s'%args[0]
        assert len(args) <= 6, 'Incorrect arguments %s'%str(args)
        args = color_args(args, 1)
        self.markers.append(','.join(map(str,args)) )
        return self

    def margin(self, left, right, top, bottom, lwidth=0, lheight=0):
        """
        Set margins for chart area
        args are of the form::
            <left margin>,
            <right margin>,
            <top margin>,
            <bottom margin>|
            <legend width>,
            <legend height>

        APIPARAM: chma
        """
        self['chma'] = '%d,%d,%d,%d'  % (left, right, top, bottom)
        if lwidth or lheight:
            self['chma'] += '|%d,%d' % (lwidth, lheight)
        return self

    def line(self, *args):
        """
        Called one at a time for each dataset
        args are of the form::
            <data set n line thickness>,
            <length of line segment>,
            <length of blank segment>
        APIPARAM: chls
        """
        self.lines.append(','.join(['%.1f'%x for x in map(float,args)]))
        return self

    def fill(self, *args):
        """
        Apply a solid fill to your chart
        args are of the form <fill type>,<fill style>,...
        fill type must be one of c,bg,a
        fill style must be one of s,lg,ls
        the rest of the args refer to the particular style
        APIPARAM: chf
        """
        a,b = args[:2]
        assert a in ('c','bg','a'), 'Fill type must be bg/c/a not %s'%a
        assert b in ('s','lg','ls'), 'Fill style must be s/lg/ls not %s'%b
        if len(args) == 3:
            args = color_args(args, 2)
        else:
            args = color_args(args, 3,5)
        self.fills.append(','.join(map(str,args)))
        return self

    def grid(self, *args):
        """
        Apply a grid to your chart
        args are of the form::
            <x axis step size>,
            <y axis step size>,
            <length of line segment>,
            <length of blank segment>
            <x offset>,
            <y offset>
        APIPARAM: chg
        """
        grids =  map(str,map(float,args))
        self['chg'] = ','.join(grids).replace('None','')
        return self

    def color(self, *args):
        """
        Add a color for each dataset
        args are of the form <color 1>,...<color n>
        APIPARAM: chco
        """
        args = color_args(args, *range(len(args)))
        self['chco'] = ','.join(args)
        return self

    def type(self, type):
        """
        Set the chart type, either Google API type or regular name
        APIPARAM: cht
        """
        self['cht'] = self.check_type(str(type))
        return self

    def label(self, *args):
        """
        Add a simple label to your chart
        call each time for each dataset
        APIPARAM: chl
        """
        if self['cht'] == 'qr':
            self['chl'] = ''.join(map(str,args))
        else:
            self['chl'] = '|'.join(map(str,args))
        return self

    def legend(self, *args):
        """
        Add a legend to your chart
        call each time for each dataset
        APIPARAM: chdl
        """
        self['chdl'] = '|'.join(args)
        return self

    def legend_pos(self, pos):
        """
        Define a position for your legend to occupy
        APIPARAM: chdlp
        """
        assert pos in LEGEND_POSITIONS, 'Unknown legend position: %s'%pos
        self['chdlp'] = str(pos)
        return self

    def title(self, title, *args):
        """
        Add a title to your chart
        args are optional style params of the form <color>,<font size>
        APIPARAMS: chtt,chts
        """
        self['chtt'] = title
        if args:
            args = color_args(args, 0)
            self['chts'] = ','.join(map(str,args))
        return self

    def size(self,*args):
        """
        Set the size of the chart, args are width,height and can be tuple
        APIPARAM: chs
        """
        if len(args) == 2:
            x,y = map(int,args)
        else:
            x,y = map(int,args[0])
        self.check_size(x,y)
        self['chs'] = '%dx%d'%(x,y)
        return self

    def orientation(self, angle):
        """
        Set the chart dataset orientation
        angle is <angle in radians>
        APIPARAM: chp
        """
        self['chp'] = '%f'%angle
        return self
    position = orientation

    def render(self):
        """
        Renders the chart context and axes into the dict data
        """
        self.update(self.axes.render())
        encoder = Encoder(self._encoding, None, self._series)
        if not 'chs' in self:
            self['chs'] = '300x150'
        else:
            size = self['chs'].split('x')
            assert len(size) == 2, 'Invalid size, must be in the format WxH'
            self.check_size(*map(int,size))
        assert 'cht' in self, 'No chart type defined, use type method'
        self['cht'] = self.check_type(self['cht'])
        if ('any' in dir(self._dataset) and self._dataset.any()) or self._dataset:
            self['chd'] = encoder.encode(self._dataset)
        elif not 'choe' in self:
            assert 'chd' in self, 'You must have a dataset, or use chd'
        if self._scale:
            assert self['chd'].startswith('t'),\
                'You must use text encoding with chds'
            self['chds'] = ','.join(self._scale)
        if self._geo and self._ld:
            self['chtm'] = self._geo
            self['chld'] = self._ld
        if self.lines:
            self['chls'] = '|'.join(self.lines)
        if self.markers:
            self['chm'] = '|'.join(self.markers)
        if self.fills:
            self['chf'] = '|'.join(self.fills)

    ###################
    # Checks
    ###################
    def check_size(self,x,y):
        """
        Make sure the chart size fits the standards
        """
        assert x <= 1000, 'Width larger than 1,000'
        assert y <= 1000, 'Height larger than 1,000'
        assert x*y <= 300000, 'Resolution larger than 300,000'

    def check_type(self, type):
        """Check to see if the type is either in TYPES or fits type name

        Returns proper type
        """
        if type in TYPES:
            return type
        tdict = dict(zip(TYPES,TYPES))
        tdict.update({
            'line': 'lc',
            'bar': 'bvs',
            'pie': 'p',
            'venn': 'v',
            'scater': 's',
            'radar': 'r',
            'meter': 'gom',
        })
        assert type in tdict, 'Invalid chart type: %s'%type
        return tdict[type]

    #####################
    # Convience Functions
    #####################
    def getname(self):
        """
        Gets the name of the chart, if it exists
        """
        return self.get('chtt','')

    def getdata(self):
        """
        Returns the decoded dataset from chd param
        """
        #XXX: Why again? not even sure decode works well
        return Encoder(self._encoding).decode(self['chd'])

    def _parts(self):
        return ('%s=%s'%(k,smart_str(v)) for k,v in sorted(self.items()) if v)

    def __str__(self):
        return self.url

    def __repr__(self):
        return '<GChart.%s %s>'%(self.__class__.__name__, dict(self.items()))

    @property
    def url(self):
        """
        Returns the rendered URL of the chart
        """
        self.render()
        return self._apiurl + '&'.join(self._parts()).replace(' ','+')

    def show(self, *args, **kwargs):
        """
        Shows the chart URL in a webbrowser

        Other arguments passed to webbrowser.open
        """
        from webbrowser import open as webopen
        return webopen(str(self), *args, **kwargs)

    def save(self, fname=None):
        """
        Download the chart from the URL into a filename as a PNG

        The filename defaults to the chart title (chtt) if any
        """
        if not fname:
            fname = self.getname()
        assert fname != None, 'You must specify a filename to save to'
        if not fname.endswith('.png'):
            fname += '.png'
        try:
            urlretrieve(self.url, fname)
        except Exception:
            raise IOError('Problem saving %s to file'%fname)
        return fname

    def img(self, **kwargs):
        """
        Returns an XHTML <img/> tag of the chart

        kwargs can be other img tag attributes, which are strictly enforced
        uses strict escaping on the url, necessary for proper XHTML
        """
        safe = 'src="%s" ' % self.url.replace('&','&').replace('<', '<')\
            .replace('>', '>').replace('"', '"').replace( "'", ''')
        for item in kwargs.items():
            if not item[0] in IMGATTRS:
                raise AttributeError('Invalid img tag attribute: %s'%item[0])
            safe += '%s="%s" '%item
        return '<img %s/>'%safe

    def urlopen(self):
        """
        Grabs readable PNG file pointer
        """
        req = Request(str(self))
        try:
            return urlopen(req)
        except HTTPError:
            _print('The server couldn\'t fulfill the request.')
        except URLError:
            _print('We failed to reach a server.')

    def image(self):
        """
        Returns a PngImageFile instance of the chart

        You must have PIL installed for this to work
        """
        try:
            try:
                import Image
            except ImportError:
                from PIL import Image
        except ImportError:
            raise ImportError('You must install PIL to fetch image objects')
        try:
            from cStringIO import StringIO
        except ImportError:
            from StringIO import StringIO
        return Image.open(StringIO(self.urlopen().read()))

    def write(self, fp):
        """
        Writes out PNG image data in chunks to file pointer fp

        fp must support w or wb
        """
        urlfp = self.urlopen().fp
        while 1:
            try:
                fp.write(urlfp.next())
            except StopIteration:
                return

    def checksum(self):
        """
        Returns the unique SHA1 hexdigest of the chart URL param parts

        good for unittesting...
        """
        self.render()
        return new_sha(''.join(sorted(self._parts()))).hexdigest()

# Now a whole mess of convenience classes
# *for those of us who dont speak API*
class QRCode(GChart):
    def __init__(self, content='', **kwargs):
        kwargs['choe'] = 'UTF-8'
        if isinstance(content, str):
            kwargs['chl'] = content
        else:
            kwargs['chl'] = content[0]
        GChart.__init__(self, 'qr', None, **kwargs)

class _AbstractGChart(GChart):
    o,t = {},None
    def __init__(self, dataset, **kwargs):
        kwargs.update(self.o)
        GChart.__init__(self, self.t, dataset, **kwargs)

class Meter(_AbstractGChart):   o,t = {'encoding':'text'},'gom'
class Line(_AbstractGChart):     t = 'lc'
class LineXY(_AbstractGChart):     t = 'lxy'
class HorizontalBarStack(_AbstractGChart):     t = 'bhs'
class VerticalBarStack(_AbstractGChart):     t = 'bvs'
class HorizontalBarGroup(_AbstractGChart):     t = 'bhg'
class VerticalBarGroup(_AbstractGChart):     t = 'bvg'
class Pie(_AbstractGChart):     t = 'p'
class Pie3D(_AbstractGChart):     t = 'p3'
class Venn(_AbstractGChart):     t = 'v'
class Scatter(_AbstractGChart):     t = 's'
class Sparkline(_AbstractGChart):     t = 'ls'
class Radar(_AbstractGChart):     t = 'r'
class RadarSpline(_AbstractGChart):     t = 'rs'
class Map(_AbstractGChart):     t = 't'
class PieC(_AbstractGChart):     t = 'pc'

########################################
# Now for something completely different
########################################
class Text(GChart):
    def render(self): pass
    def __init__(self, *args):
        GChart.__init__(self)
        self['chst'] = 'd_text_outline'
        args = list(map(str, color_args(args, 0, 3)))
        assert args[2] in 'lrh', 'Invalid text alignment'
        assert args[4] in '_b', 'Invalid font style'
        self['chld'] = '|'.join(args).replace('\r\n','|')\
            .replace('\r','|').replace('\n','|')

class Pin(GChart):
    def render(self): pass
    def __init__(self, ptype, *args):
        GChart.__init__(self)
        assert ptype in PIN_TYPES, 'Invalid type'
        if ptype == "pin_letter":
            args = color_args(args, 1,2)
        elif ptype == 'pin_icon':
            args = list(color_args(args, 1))
            assert args[0] in PIN_ICONS, 'Invalid icon name'
        elif ptype == 'xpin_letter':
            args = list(color_args(args, 2,3,4))
            assert args[0] in PIN_SHAPES, 'Invalid pin shape'
            if not args[0].startswith('pin_'):
                args[0] = 'pin_%s'%args[0]
        elif ptype == 'xpin_icon':
            args = list(color_args(args, 2,3))
            assert args[0] in PIN_SHAPES, 'Invalid pin shape'
            if not args[0].startswith('pin_'):
                args[0] = 'pin_%s'%args[0]
            assert args[1] in PIN_ICONS, 'Invalid icon name'
        elif ptype == 'spin':
            args = color_args(args, 2)
        self['chst'] = 'd_map_%s'%ptype
        self['chld'] = '|'.join(map(str, args)).replace('\r\n','|')\
            .replace('\r','|').replace('\n','|')
    def shadow(self):
        image = copy(self)
        chsts = self['chst'].split('_')
        chsts[-1] = 'shadow'
        image.data['chst'] = '_'.join(chsts)
        return image

class Note(GChart):
    def render(self): pass
    def __init__(self, *args):
        GChart.__init__(self)
        assert args[0] in NOTE_TYPES,'Invalid note type'
        assert args[1] in NOTE_IMAGES,'Invalid note image'
        if args[0].find('note')>-1:
            self['chst'] =  'd_f%s'%args[0]
            args = list(color_args(args, 3))
        else:
            self['chst'] = 'd_%s'%args[0]
            assert args[2] in NOTE_WEATHERS,'Invalid weather'
        args = args[1:]
        self['chld'] = '|'.join(map(str, args)).replace('\r\n','|')\
            .replace('\r','|').replace('\n','|')

class Bubble(GChart):
    def render(self): pass
    def __init__(self, btype, *args):
        GChart.__init__(self)
        assert btype in BUBBLE_TYPES, 'Invalid type'
        if btype in ('icon_text_small','icon_text_big'):
            args = list(color_args(args, 3,4))
            assert args[0] in BUBBLE_SICONS,'Invalid icon type'
        elif btype == 'icon_texts_big':
            args = list(color_args(args, 2,3))
            assert args[0] in BUBBLE_LICONS,'Invalid icon type'
        elif btype == 'texts_big':
            args = color_args(args, 1,2)
        self['chst'] = 'd_bubble_%s'%btype
        self['chld'] = '|'.join(map(str, args)).replace('\r\n','|')\
            .replace('\r','|').replace('\n','|')
    def shadow(self):
        image = copy(self)
        image.data['chst'] = '%s_shadow'%self['chst']
        return image


import os
from random import choice
from sample_data_utils.utils import memoize

CONTINENTS = (
    ('AF', 'Africa'),
    ('AS', 'Asia'),
    ('EU', 'Europe'),
    ('NA', 'North America'),
    ('SA', 'South America'),
    ('OC', 'Oceania'),
    ('AN', 'Antartica'))

@memoize
def get_codes():
    """
    >> get_codes()
    ISO	ISO3	ISO-Numeric	fips	Country	Capital	Area(in sq km)	Population	Continent	tld	CurrencyCode	CurrencyName	Phone	Postal Code Format	Postal Code Regex	Languages	geonameid	neighbours	EquivalentFipsCode
    """
    cache_filename = os.path.join(os.path.dirname(__file__), 'data', 'countryInfo.txt')
    data = []
    for line in open(cache_filename, 'r'):
        if not line.startswith('#'):
            data.append(line.split('\t'))

    return data

def continent():
    return choice(CONTINENTS)

def country():
    """
    >> country()
    ['SZ', 'SWZ', '748', 'Swaziland', 'AF', '.sz', 'SZL', 'Lilangeni']

    :return: (iso2,iso3,isonum,CountryName,Continent,tld,CurrencyCode,CurrencyName
    """
    codes = get_codes()
    selection = choice(codes)
    return selection[0:3] + selection[4:5] + selection[8:12]

def iso2():
    codes = get_codes()
    return choice(codes)[0]

def iso3():
    codes = get_codes()
    return choice(codes)[1]

def isonum():
    codes = get_codes()
    return choice(codes)[2]

def lang(*extra):
    # http://www.loc.gov/standards/iso639-2/ISO-639-2_utf-8.txt
    codes = 'aar abk ace ach ada ady afa afh afr ain aka akk alb ale alg alt amh ang anp ' \
            'apa ara arc arg arm arn arp art arw asm ast ath aus ava ave awa aym aze bad ' \
            'bai bak bal bam ban baq bas bat bej bel bem ben ber bho bih bik bin bis bla ' \
            'bnt bos bra bre btk bua bug bul bur byn cad cai car cat cau ceb cel cha chb ' \
            'che chg chi chk chm chn cho chp chr chu chv chy cmc cop cor cos cpe cpf cpp ' \
            'cre crh crp csb cus cze dak dan dar day del den dgr din div doi dra dsb dua ' \
            'dum dut dyu dzo efi egy eka elx eng enm epo est ewe ewo fan fao fat fij fil ' \
            'fin fiu fon fre frm fro frr frs fry ful fur gaa gay gba gem geo ger gez gil ' \
            'gla gle glg glv gmh goh gon gor got grb grc gre grn gsw guj gwi hai hat hau ' \
            'haw heb her hil him hin hit hmn hmo hrv hsb hun hup iba ibo ice ido iii ijo ' \
            'iku ile ilo ina inc ind ine inh ipk ira iro ita jav jbo jpn jpr jrb kaa kab ' \
            'kac kal kam kan kar kas kau kaw kaz kbd kha khi khm kho kik kin kir kmb kok ' \
            'kom kon kor kos kpe krc krl kro kru kua kum kur kut lad lah lam lao lat lav ' \
            'lez lim lin lit lol loz ltz lua lub lug lui lun luo lus mac mad mag mah mai ' \
            'mak mal man mao map mar mas may mdf mdr men mga mic min mis mkh mlg mlt mnc ' \
            'mni mno moh mon mos mul mun mus mwl mwr myn myv nah nai nap nau nav nbl nde ' \
            'ndo nds nep new nia nic niu nno nob nog non nor nqo nso nub nwc nya nym nyn ' \
            'nyo nzi oci oji ori orm osa oss ota oto paa pag pal pam pan pap pau peo per ' \
            'phi phn pli pol pon por pra pro pus qaa-qtz que raj rap rar roa roh rom rum ' \
            'run rup rus sad sag sah sai sal sam san sas sat scn sco sel sem sga sgn shn ' \
            'sid sin sio sit sla slo slv sma sme smi smj smn smo sms sna snd snk sog som ' \
            'son sot spa srd srn srp srr ssa ssw suk sun sus sux swa swe syc syr tah tai ' \
            'tam tat tel tem ter tet tgk tgl tha tib tig tir tiv tkl tlh tli tmh tog ton ' \
            'tpi tsi tsn tso tuk tum tup tur tut tvl twi tyv udm uga uig ukr umb und urd ' \
            'uzb vai ven vie vol vot wak wal war was wel wen wln wol xal xho yao yap yid ' \
            'yor ypk zap zbl zen zgh zha znd zul zun zxx zza'

    return choice(codes.split())


# -*- coding: utf-8 -*-
import decimal
from random import choice, uniform
import sys

def amount(min=1, max=sys.maxsize, decimal_places=2):
    """
        return a random floating number

    :param min: minimum value
    :param max: maximum value
    :param decimal_places: decimal places
    :return:
    """
    q = '.%s1' % '0' * (decimal_places - 1)
    return decimal.Decimal(uniform(min, max)).quantize(decimal.Decimal(q))

def currency():
    """
        returns a random ISO 4217 currency code

    """
    codes = ['AFN', 'ALL', 'DZD', 'USD', 'EUR', 'AOA', 'XCD', 'XCD', 'XCD', 'ARS', 'AMD', 'AWG', 'AUD', 'EUR', 'AZN',
             'BSD', 'BHD', 'BDT', 'BBD', 'BYR', 'EUR', 'BZD', 'XOF', 'BMD', 'BTN', 'BOB', 'BAM', 'BWP', 'NOK', 'BRL',
             'USD', 'BND', 'BGN', 'XOF', 'BIF', 'KHR', 'XAF', 'CAD', 'CVE', 'KYD', 'XAF', 'XAF', 'CLP', 'CNY', 'AUD',
             'AUD', 'COP', 'KMF', 'XAF', 'CDF', 'NZD', 'CRC', 'HRK', 'CUP', 'EUR', 'CZK', 'DKK', 'DJF', 'XCD', 'DOP',
             'ECS', 'EGP', 'SVC', 'XAF', 'ERN', 'EUR', 'ETB', 'EUR', 'FKP', 'DKK', 'FJD', 'EUR', 'EUR', 'EUR', 'EUR',
             'XAF', 'GMD', 'GEL', 'EUR', 'GHS', 'GIP', 'GBP', 'EUR', 'DKK', 'XCD', 'EUR', 'USD', 'QTQ', 'GGP', 'GNF',
             'GWP', 'GYD', 'HTG', 'AUD', 'HNL', 'HKD', 'HUF', 'ISK', 'INR', 'IDR', 'IRR', 'IQD', 'EUR', 'GBP', 'ILS',
             'EUR', 'XOF', 'JMD', 'JPY', 'GBP', 'JOD', 'KZT', 'KES', 'AUD', 'KPW', 'KRW', 'KWD', 'KGS', 'LAK', 'LVL',
             'LBP', 'LSL', 'LRD', 'LYD', 'CHF', 'LTL', 'EUR', 'MOP', 'MKD', 'MGF', 'MWK', 'MYR', 'MVR', 'XOF', 'EUR',
             'USD', 'EUR', 'MRO', 'MUR', 'EUR', 'MXN', 'USD', 'MDL', 'EUR', 'MNT', 'EUR', 'XCD', 'MAD', 'MZN', 'MMK',
             'NAD', 'AUD', 'NPR', 'EUR', 'ANG', 'XPF', 'NZD', 'NIO', 'XOF', 'NGN', 'NZD', 'AUD', 'USD', 'NOK', 'OMR',
             'PKR', 'USD', 'PAB', 'PGK', 'PYG', 'PEN', 'PHP', 'NZD', 'PLN', 'XPF', 'EUR', 'USD', 'QAR', 'EUR', 'RON',
             'RUB', 'RWF', 'SHP', 'XCD', 'XCD', 'EUR', 'XCD', 'WST', 'EUR', 'STD', 'SAR', 'XOF', 'RSD', 'SCR', 'SLL',
             'SGD', 'EUR', 'EUR', 'SBD', 'SOS', 'ZAR', 'GBP', 'SSP', 'EUR', 'LKR', 'SDG', 'SRD', 'NOK', 'SZL', 'SEK',
             'CHF', 'SYP', 'TWD', 'TJS', 'TZS', 'THB', 'XOF', 'NZD', 'TOP', 'TTD', 'TND', 'TRY', 'TMT', 'USD', 'AUD',
             'GBP', 'UGX', 'UAH', 'AED', 'UYU', 'USD', 'USD', 'UZS', 'VUV', 'EUR', 'VEF', 'VND', 'USD', 'USD', 'XPF',
             'MAD', 'YER', 'ZMW', 'ZWD']
    return choice(codes)


from datetime import datetime

import json
import requests
import inflection
import dateutil.parser
import recordtype

from kalibro_client.errors import KalibroClientSaveError, KalibroClientDeleteError, \
    KalibroClientNotFoundError, KalibroClientRequestError

class RequestMethods(object):
    def save_prefix(self):
        return ""

    def update_prefix(self):
        return ""

    def delete_prefix(self):
        return ""

    @classmethod
    def entity_name(cls):
        raise NotImplementedError

    @classmethod
    def endpoint(cls):
        return inflection.pluralize(cls.entity_name())

    @classmethod
    def service_address(cls):
        raise NotImplementedError

    @classmethod
    def request(cls, action, params=None, method='post', prefix=None):
        url = cls.service_address()

        if prefix:
            url += "/" + prefix
        url += "/{}/{}".format(cls.endpoint(), action)

        if params is not None and 'id' in params:
            url = url.replace(':id', str(params.pop('id')))

        response = requests.request(method, url, data=json.dumps(params),
                                    headers={'Content-Type': 'application/json'})
        try:
            response.raise_for_status()
        except requests.exceptions.HTTPError:
            raise KalibroClientRequestError(response)

        return response.json()

class Base(object):
    @classmethod
    def _is_valid_field(cls, name):
        return name in cls._fields

    @classmethod
    def response_to_objects_array(cls, response):
        array = response[inflection.pluralize(cls.entity_name())]
        return cls.array_to_objects_array(array)

    @classmethod
    def array_to_objects_array(cls, array):
        return [cls(**attributes) for attributes in array]

class BaseCRUD(Base, RequestMethods):
    @classmethod
    def find(cls, id):
        try:
            response = cls.request(':id', params={'id': id}, method='get')
        except KalibroClientRequestError as error:
            body = error.response.json()
            raise KalibroClientNotFoundError(body['errors'])

        return cls(**response[cls.entity_name()])

    @classmethod
    def all(cls):
        return cls.response_to_objects_array(cls.request('', method='get'))

    @classmethod
    def exists(cls, id):
        return cls.request(':id/exists', params={'id': id}, method='get')['exists']

    def _update_request(self):
        return self.request(action=str(self.id),
                        params={self.entity_name(): self._asdict(), 'id': str(self.id)},
                        method='put',
                        prefix=self.update_prefix())

    def save(self):
        if not self.id:
            response = self.request(action='',
                                    params={self.entity_name(): self._asdict()},
                                    prefix=self.save_prefix())
        else:
            response = self._update_request()

        if 'errors' not in response:
            response_body = response[self.entity_name()]

            self.id = response_body['id']
            self.created_at = response_body['created_at']
            self.updated_at = response_body['updated_at']
        else:
            raise KalibroClientSaveError(response['errors'])

    def update(self, **kwargs):
        if not self.id:
            raise KalibroClientSaveError("Cannot update a record that is not saved.")

        for attr, value in kwargs.items():
            if self._is_valid_field(attr):
                setattr(self, attr, value)

        try:
            response = self._update_request()
        except KalibroClientRequestError as error:
            body = error.response.json()
            raise KalibroClientSaveError(body['errors'])

        response_body = response[self.entity_name()]
        self.updated_at = response_body['updated_at']

    def delete(self):
        if not isinstance(self.id, (int, long)):
            raise KalibroClientDeleteError('Can not delete object without id')

        response = self.request(action=':id', params={'id': self.id}, method='delete', prefix=self.delete_prefix())

        if 'errors' in response:
            raise KalibroClientDeleteError(response['errors'])

def entity_name_decorator(top_cls):
    """
    Assign an entity name based on the class immediately inhering from Base.

    This is needed because we don't want
    entity names to come from any class that simply inherits our classes,
    just the ones in our module.

    For example, if you create a class Project2 that exists outside of
    kalibro_client and inherits from Project, it's entity name should still
    be Project.
    """
    class_name = inflection.underscore(top_cls.__name__).lower()

    def entity_name(cls):
        return class_name

    top_cls.entity_name = classmethod(entity_name)

    return top_cls

def attributes_class_constructor(name, fields, identity=True, *args, **kwargs):
    if not identity:
        return recordtype.recordtype(name, fields, *args, **kwargs)

    # Create the new class, inheriting from the record type and from this
    # class
    class IdentityClass(recordtype.recordtype(name, fields, *args, **kwargs)):
        def __init__(self, id=None, created_at=None, updated_at=None,
                     *init_args, **init_kwargs):
            super(IdentityClass, self).__init__(*init_args, **init_kwargs)
            self.id = id
            self.created_at = created_at
            self.updated_at = updated_at

        @property
        def id(self):
            return self._id

        @id.setter
        def id(self, value):
            if value is not None:
                value = int(value)

            self._id = value

        @property
        def created_at(self):
            return self._created_at

        @created_at.setter
        def created_at(self, value):
            if value is not None and not isinstance(value, datetime):
                value = dateutil.parser.parse(value)

            self._created_at = value

        @property
        def updated_at(self):
            return self._updated_at

        @updated_at.setter
        def updated_at(self, value):
            if value is not None and not isinstance(value, datetime):
                value = dateutil.parser.parse(value)

            self._updated_at = value

    return IdentityClass