from collections import Mapping, Iterable

from . import alphabets

__all__ = ['bx_encode', 'bx_decode', 'BaseKeyGenerator']

def bx_encode(n, alphabet):
   """\
   Encodes an integer :attr:`n` in base ``len(alphabet)`` with 
   digits in :attr:`alphabet`.
 
   ::

      # 'ba'
      bx_encode(3, 'abc')

   :param n:            a positive integer.
   :param alphabet:     a 0-based iterable.
   """
 
   if not isinstance(n, int):
      raise TypeError('an integer is required')

   base = len(alphabet)

   if n == 0:
      return alphabet[0]

   digits = []
   
   while n > 0:
      digits.append(alphabet[n % base])
      n = n // base
   
   digits.reverse()
   return ''.join(digits)

def bx_decode(string, alphabet, mapping=None):
   """\
   Transforms a string in :attr:`alphabet` to an integer.
   
   If :attr:`mapping` is provided, each key must map to its 
   positional value without duplicates.

   ::

      mapping = {'a': 0, 'b': 1, 'c': 2}

      # 3
      bx_decode('ba', 'abc', mapping)
   
   :param string:       a string consisting of key from `alphabet`.
   :param alphabet:     a 0-based iterable.
   
   :param mapping:      a :class:`Mapping <collection.Mapping>`. If `None`, 
                        the inverse of `alphabet` is used, with values mapped
                        to indices.
   """
  
   mapping = mapping or dict([(d, i) for (i, d) in enumerate(alphabet)])
   base = len(alphabet)

   if not string:
      raise ValueError('string cannot be empty')

   if not isinstance(mapping, Mapping):
      raise TypeError('a Mapping is required')

   sum = 0

   for digit in string:
      try:
         sum = base*sum + mapping[digit]
      except KeyError:
         raise ValueError(
            "invalid literal for bx_decode with base %i: '%s'" % (base, digit))

   return sum

class BaseKeyGenerator(Iterable):
   """\
   A class which yields a unique string on every iteration (a `key`).
   If the key generator is deterministic, then two generators should 
   yield the same keys given the same parameters.

   `alphabet` can be any iterable, as long as each item is not 
   contained within any other item. For instance, ``('00', '0', '1')`` 
   would be an ambiguous alphabet, since ``00`` could be interpreted as 
   two numbers.

   Each yielded key is expected to be encoded with :meth:`encode`

   ::

      class KeyGenerator(BaseKeyGenerator):
         def __iter__(self):
            ...
            yield self.encode(num)


   Keys of a minimum length or starting at a certain *unencoded* value can be
   generated by specifying :attr:`min_length` or :attr:`start`.

   ::

      hexabet = '0123456789abcef'
      keygen = KeyGenerator(start=255, alphabet=hexabet)

      # ['ff', '100', '101']
      [key for key in islice(k, 0, 3)]


   .. admonition:: Subclassing

      Subclasses should implement :meth:`__iter__ <BaseKeyGenerator.__iter__>`.

   :param alphabet:     an iterable.

   :param start:        the number to start iteration at.

   :param min_length:   the length of the string to start iteration at. Leading
                        zero-characters are not counted in the length.
                        Only one of `start` or `min_length` should be given.
   """
  
   def __init__(self, alphabet=None, min_length=None, start=None):           
      min_length = max(1, min_length)
      alphabet = alphabet or alphabets.DEFAULT

      if start is None:
         start = len(alphabet) ** (min_length-1)      

      # Raise an error if there are duplicates
      seen = set()
      for sym in alphabet:
         if sym in seen:
            msg = "alphabet contains duplicate symbol '{0}'".format(sym)
            raise ValueError(msg)

         seen.add(sym)

      self.alphabet = alphabet
      self.start = start

   def encode(self, n):
      return bx_encode(n, self.alphabet)

   def __iter__(self):
      raise NotImplementedError
