from datetime import date, datetime
from uuid import UUID

from .exceptions import NotFoundException
from .field import Field


class AbstractBaseDB:
  """
  A simple, un-typed, persistent storage interface that provides basic CRUD operations and indexing capabilities.

  Core concepts:
  Freeform binary 'data' is stored against a 'data_type' string. Data objects must be picklable. It is generally
  assumed that your data is formatted as a dictionary, but only the 'init_index' function actually requires that
  assumption. New data is automatically assigned a UUID as its primary key.

  Example usage:
  >>> db = ... # DB instance
  >>> db.create('user', { 'name': 'Alex', 'age': 8 }) -> <user_id>
  >>> db.get('user', <user_id>) -> { 'name': 'Alex', 'age': 8 }
  >>> db.list('user', Field('name') == 'Alex') -> [ (<user_id>, { 'name': 'Alex', 'age': 8 }) ]
  >>> db.list('user', Field('name') == 'Kelly') -> []
  >>> db.update('user', <user_id>, { 'name': 'Alex', 'age': 9 })
  >>> db.get('user', <user_id>) -> { 'name': 'Alex', 'age': 9 }
  >>> db.list('user', Field('name') == 'Alex' and FieldFilterException('age') > 0) -> raises FieldFilterException
  >>> db.list('user', Field('name') == Field('age')) -> raises FieldFilterException
  >>> db.delete('user', <user_id>)
  >>> db.update('user', <user_id>, { 'name': 'Alex' })
  >>> db.create('user', { 'name': 'Kelly' }) -> <user_id_2>
  """


  def create(self, data_type, *data):
    """
    Stores a new object specified by 'data' for the given 'data_type'. If a 'fmt' was provided when initializing the
    data type, then the 'data' will be validated against that 'fmt' and the result will be stored. See discussion on
    'init_data_type' for more discussion.

    Returns the id (UUID) of the newly created object
    """
    raise NotImplementedError()


  def get(self, data_type, *data_ids):
    """
    Returns the data stored against the 'data_ids'
    Raises a NotFoundException if the 'data_ids' cannot be found
    """
    raise NotImplementedError()


  def update(self, data_type, data_id, data):
    """
    Replaces the data stored against the 'data_id' with that specified by 'data'. If a 'fmt' was provided when
    initializing the data type, then the 'data' will be validated against that 'fmt' and the result will be stored. See
    discussion on 'init_data_type' for more discussion.

    Returns nothing
    Raises a NotFoundException if the 'data_id' cannot be found
    """
    raise NotImplementedError()


  def delete(self, data_type, *data_ids):
    """
    Deletes the specified item from the database.

    Raises a NotFoundException if the 'data_id' cannot be found
    """
    raise NotImplementedError()


  def list(self, data_type, *conditions, reverse=False, limit=None):
    """
    Returns a list containing tuples of (id, data) that match all of the specified conditions

    Parameters:
    - data_type: list data of this type. Must have been initialized using the 'init' function.
    - condition: an optional 'Condition' object. A condition can be created using the 'condition' function coupled with
      a comparison operation (see usage examples for more clarity). An input 'condition' can only refer to index names
      that were initialized using the 'init' function.
    - reverse: set to True to sort the returned list in reverse order
    - limit: impose a limit on the size of the returned list

    Examples:
    >>> db = ... # DB instance
    >>>
    >>> # List all users (sorted by ID)
    >>> db.list('user')
    >>>
    >>> # List the oldest 10 users
    >>> db.list('user', Field('age'), reverse=True, limit=10)
    >>>
    >>> # List users who are exactly 50
    >>> db.list('user', Field('age') == 50)
    >>> db.list('user', 50 == Field('age'))
    >>>
    >>> # List users who are under 18, sorted by age
    >>> db.list('user', Field('age') < 18)
    >>> 
    >>> # List users between 8 and 12 (inclusive)
    >>> db.list('user', 8 <= Field('age') <= 12)
    >>> 
    >>> # List users whose name starts with 'alex'
    >>> db.list('user', Field('name') == 'alex')
    >>> 
    >>> # List users whose name is 'Alex' and who are between 20 and 40 (inclusive)
    >>> db.list('user', Field('name') == 'Alex', 20 <= Field('age') <= 40)
    """

    # This default implementation simply proxies the 'iter' function and should probably be overwritten
    return list(self.iter(data_type, *conditions, reverse=reverse, limit=limit))


  def iter(self, data_type, *conditions, reverse=False, limit=None):
    """
    Identical to 'list' except that the results are returned as an iterator

    Note: this is more flexible and robust than the 'list' function, as the number of results it returns is not
    constrained by memory, but it may carry some overhead which might be noticeable for small data sets depending on
    the implementation.
    """
    raise NotImplementedError()


  def count(self, data_type, *conditions, limit=None):
    raise NotImplementedError()
