from builtins import (str, super)
from past.builtins import basestring

from copy import deepcopy

from . import abstract


#class CellMethod(abstract.Properties):
class CellMethod(abstract.Container):
    '''A cell method construct of the CF data model.

One or more cell method constructs describe how the cell values of the
field construct represent the variation of the physical quantity
within its cells, i.e. the structure of the data at a higher
resolution.

A single cell method construct consists of a set of axes, a "method"
property which describes how a value of the field construct's data
array describes the variation of the quantity within a cell over those
axes (e.g. a value might represent the cell area average), and
descriptive qualifiers serving to indicate more precisely how the
method was applied (e.g. recording the spacing of the original data,
or the fact that the method was applied only over El Nino years).

.. versionadded:: 1.7.0

    '''
    def __init__(self, axes=None, method=None, qualifiers=None,
                 source=None, copy=True):
        '''**Initialisation**

:Parameters:

    axes: (sequence of) `str`, optional
        Set the axes of the cell method construct, specified either by
        the construct identifiers of domain axis constructs, standard
        names, or the special string ``'area'``.

          *Parameter example:*
             ``axes='domainaxis0'``
        
          *Parameter example:*
             ``axes=['domainaxis0']``
        
          *Parameter example:*
             ``axes=('domainaxis0', 'domainaxis1')``
        
          *Parameter example:*
             ``axes='area'``
        
          *Parameter example:*
             ``axes=['domainaxis2', 'time']``
        
         The axes may also be set after initialisation with the
         `set_axes` method.

    method: `str`, optional
        Set the axes of the cell method construct. Either one or more
        domain axis construct identifiers or standard names. Ignored
        if the *source* parameter is set.

          *Parameter example:*
             ``method='mean'

         The method may also be set after initialisation with the
         `set_method` method.

    qualifiers: `dict`, optional
        Set descriptive qualifiers. The dictionary keys are qualifer
        names, with corresponding values. Ignored if the *source*
        parameter is set.

        *Parameter example:*
          ``qualifiers={'comment': 'sampled instantaneously'}``
       
        *Parameter example:*
          ``qualifiers={'where': 'sea', ''over': 'ice'}``
        
        Qualifiers may also be set after initialisation with the
        `qualifiers` and `set_qualifier` methods.

    source: optional
        Initialize the axes, method and qualifiers from those of
        *source*.

    copy: `bool`, optional
        If False then do not deep copy input parameters prior to
        initialization By default parameters are deep copied.

        '''
#        super().__init__(properties=properties, source=source,
#                         copy=copy)
#
#        if source:
#            try:
#                axes = source.get_axes(None)
#            except AttributeErrror:
#                axes = None              
#
#            try:
#                method = source.get_method(None)
#            except AttributeErrror:
#                method = None              
#        #--- End: if
#
#        if axes is not None:                
#            axes = self.set_axes(axes)
#
#        if method is not None:                
#            method = self.set_method(method)

        super().__init__()

        if source:
            try:
                axes = source.get_axes(None)
            except AttributeErrror:
                axes = None              

            try:
                method = source.get_method(None)
            except AttributeErrror:
                method = None              

            try:
                qualifiers = source.qualifiers()
            except AttributeError:
                qualifiers = None
        #--- End: if

        if axes is not None:                
            axes = self.set_axes(axes)

        if method is not None:                
            method = self.set_method(method)

        if qualifiers is None:
            qualifiers = {}

        self._set_component('qualifiers', qualifiers, copy=copy)
    #--- End: def

    # ----------------------------------------------------------------
    # Attributes
    # ----------------------------------------------------------------
    @property
    def construct_type(self):
        '''Return a description of the construct type.
        
.. versionadded:: 1.7.0
        
:Returns:

    `str`
        The construct type.

**Examples:**

>>> f.construct_type
'cell_method'

        '''
        return 'cell_method'
    #--- End: def

    # ----------------------------------------------------------------
    # Methods
    # ----------------------------------------------------------------
    def copy(self):
        '''Return a deep copy.

``f.copy()`` is equivalent to ``copy.deepcopy(f)``.

.. versionadded:: 1.7.0

:Returns:

        The deep copy.

**Examples:**

>>> g = f.copy()

        '''
        return type(self)(source=self, copy=True)
    #--- End: def

    def del_axes(self, default=ValueError()):
        '''Remove the axes of the cell method.

.. versionadded:: 1.7.0

.. seealso:: `get_axes`, `has_axes`, `set_axes`

:Parameters:

    default: optional
        Return the value of the *default* parameter if axes have not
        been set. If set to an `Exception` instance then it will be
        raised instead.

:Returns:

    `tuple`
        The removed axes, identified by domain axis construct key or
        standard name.

**Examples:**

>>> c.set_axes('domainaxis1')
>>> c.has_axes()
True
>>> c.get_axes()
('domainaxis1',)
>>> c.del_axes()
>>> c.has_axes()
False
>>> c.get_axes('NO AXES')
'NO AXES'
>>> c.del_axes('NO AXES')
'NO AXES'

        '''
        try:
            return self._del_component('axes')
        except ValueError:
            return self._default(default,
                                 "{!r} has no axes".format(
                                     self.__class__.__name__))
    #--- End: def
    
    def del_method(self, default=ValueError()):
        '''Remove the method of the cell method.

.. versionadded:: 1.7.0

.. seealso:: `get_method`, `has_method`, `set_method`

:Parameters:

    default: optional
        Return the value of the *default* parameter if the method has
        not been set. If set to an `Exception` instance then it will
        be raised instead.

:Returns:

    `str`
        The removed method.

**Examples:**

>>> c.set_method('minimum')
>>> c.has_method()
True
>>> c.get_method()
('time',)
>>> c.del_method()
>>> c.has_method()
False
>>> c.get_method('NO METHOD')
'NO METHOD'
>>> c.del_method('NO METHOD')
'NO METHOD'

        '''
        try:
            return self._del_component('method')
        except ValueError:
            return self._default(default,
                                 "{!r} has no method".format(
                                     self.__class__.__name__))
    #--- End: def
    
    def del_qualifier(self, qualifier, default=ValueError()):
        '''Remove a qualifier of the cell method.

.. versionadded:: 1.7.0

.. seealso:: `get_qualifier`, `has_qualifier`, `qualifiers`,
             `set_qualifier`

:Parameters:

    qualifier:
         The name of the qualifier to be removed.

        *Parameter example:*
           ``qualifier='where'``

    default: optional
        Return the value of the *default* parameter if the qualifier
        has not been set. If set to an `Exception` instance then it
        will be raised instead.

:Returns:

        The removed qualifier.

**Examples:**

>>> c.set_qualifier('where', 'land')
>>> c.get_qualifier('where', 'no qualifier')
'land'
>>> c.del_qualifier('where')
'land'
>>> c.get_qualifier('where')
ValueError: 'CellMethod' has no 'where' qualifier
>>> c.del_qualifier('where', 'no qualifier')
'no qualifier'

        '''
        try:
            return self._get_component('qualifiers').pop(qualifier)
        except KeyError:
            return self._default(default, "{!r} has no {!r} qualifier".format(
                self.__class__.__name__, qualifier))
    #--- End: def

    def get_axes(self, default=ValueError()):
        '''Return the axes of the cell method.

.. versionadded:: 1.7.0

.. seealso:: `del_axes`, `has_axes`, `set_axes`

:Parameters:

    default: optional
        Return the value of the *default* parameter if axes have not
        been set. If set to an `Exception` instance then it will be
        raised instead.

:Returns:

    `tuple`
        The axes.

**Examples:**

>>> c.set_axes('area')
>>> c.get_axes('area')
'area'
>>> c.del_axes('axes')
'area'
>>> c.get_axes('axes')
ValueError: 'CellMethod' has no axes
>>> c.get_axes('where', 'no axes')
'no axes'

        '''
        try:
            return self._get_component('axes')
        except ValueError:
            return self._default(default,
                                 "{!r} has no axes".format(
                                     self.__class__.__name__))
    #--- End: def

    def get_method(self, default=ValueError()):
        '''Return the method of the cell method.

.. versionadded:: 1.7.0

.. seealso:: `del_method`, `has_method`, `set_method`

:Parameters:

    default: optional
        Return the value of the *default* parameter if the method has
        not been set. If set to an `Exception` instance then it will
        be raised instead.

:Returns:

    `str`
        The method.

**Examples:**

>>> c.set_method('minimum')
>>> c.has_method()
True
>>> c.get_method()
'minimum'
>>> c.del_method()
>>> c.has_method()
False
>>> c.get_method('NO METHOD')
'NO METHOD'
>>> c.del_method('NO METHOD')
'NO METHOD'

        '''
        try:
            return self._get_component('method')
        except ValueError:
            return self._default(default,
                                 "{!r} has no method".format(
                                     self.__class__.__name__))
    #--- End: def

    def get_qualifier(self, qualifier, default=ValueError()):
        '''Return a qualifier of the cell method.

.. versionadded:: 1.7.0

.. seealso:: `del_qualifier`, `has_qualifier`, `qualifiers`,
             `set_qualifier`

:Parameters:

    qualifier:
        The name of the qualifier to be returned.

        *Parameter example:*
            ``qualifier='where'``

    default: optional
        Return the value of the *default* parameter if the qualifier
        has not been set. If set to an `Exception` instance then it
        will be raised instead.

:Returns:

        The value of the qualifier. 

**Examples:**

>>> c.set_qualifier('where', 'land')
>>> c.get_qualifier('where', 'no qualifier')
'land'
>>> c.del_qualifier('where')
'land'
>>> c.get_qualifier('where')
ValueError: 'CellMethod' has no 'where' qualifier
>>> c.get_qualifier('where', 'no qualifier')
'no qualifier'

        '''
        try:
            return self._get_component('qualifiers')[qualifier]
        except KeyError:
            return self._default(default, "{!r} has no {!r} qualifier".format(
                self.__class__.__name__, qualifier))
    #--- End: def

    def has_axes(self):
        '''Whether the axes of the cell method have been set.

.. versionadded:: 1.7.0

.. seealso:: `del_axes`, `get_axes`, `set_axes`

:Returns:

     `bool`
        True if the axes have been set, otherwise False.

**Examples:**

>>> c.set_axes('domainaxis1')
>>> c.has_axes()
True
>>> c.get_axes()
('domainaxis1',)
>>> c.del_axes()
>>> c.has_axes()
False
>>> c.get_axes('NO AXES')
'NO AXES'
>>> c.del_axes('NO AXES')
'NO AXES'

'''
        return self._has_component('axes')
    #--- End: def

    def has_method(self):
        '''Whether the method of the cell method has been set.

.. versionadded:: 1.7.0

.. seealso:: `del_axes`, `get_method`, `set_method`

:Returns:

     `bool`
        True if the method has been set, otherwise False.

**Examples:**

>>> c.set_method('minimum')
>>> c.has_method()
True
>>> c.get_method()
'minimum'
>>> c.del_method()
>>> c.has_method()
False
>>> c.get_method('NO METHOD')
'NO METHOD'
>>> c.del_method('NO METHOD')
'NO METHOD'

'''
        return self._has_component('method')
    #--- End: def

    def has_qualifier(self, qualifier):
        '''Whether a qualifier of the cell method has been set.

.. versionadded:: 1.7.0

.. seealso:: `del_qualifier`, `get_qualifier`, `qualifiers`,
             `set_qualifier`

:Parameters:

    qualifier: `str`
        The name of the qualifier.

:Returns:

     `bool`
        True if the qualifier has been set, otherwise False.

**Examples:**

>>> c.set_qualifier('where', 'land')
>>> c.has_qualifier('where')
True
>>> c.del_qualifier('where')
'land'
>>> c.has_qualifier('where')
False

        '''
        return qualifier in self._get_component('qualifiers')
    #--- End: def

    def qualifiers(self):
        '''Return all qualifiers of the cell method.

.. versionadded:: 1.7.0

.. seealso:: `del_qualifier`, `get_qualifier`, `has_qualifier`,
             `set_qualifier`

:Returns:

    `dict`
        The qualifiers.

**Examples:**

>>> c.qualifiers()
{'interval': [<Data(): 0.1 degrees>], 
 'where': 'land'}

>>> f.qualifiers()
{}

        '''
        return self._get_component('qualifiers').copy()
    #--- End: def

    def set_axes(self, value, copy=True):
        '''Set the axes of the cell method.

.. versionadded:: 1.7.0

.. seealso:: `del_axes`, `get_axes`, `has_axes`

:Parameters:

    value: (sequence of) `str`
        The axes, specified either by the construct identifiers of
        domain axis constructs, standard names, or the special string
        ``'area'``.

        *Parameter example:*
          ``axes='domainaxis0'``

        *Parameter example:*
          ``axes='time'``

        *Parameter example:*
          ``axes='area'``

        *Parameter example:*
          ``axes=['domainaxis0', 'domainaxis2']``

        *Parameter example:*
          ``axes=['time', 'area']``

        *Parameter example:*
          ``axes=['domainaxis0', 'time']``

:Returns:

     `None`

**Examples:**

>>> c.set_axes('domainaxis1')
>>> c.has_axes()
True
>>> c.get_axes()
('domainaxis1',)
>>> c.del_axes()
>>> c.has_axes()
False
>>> c.get_axes('NO AXES')
'NO AXES'
>>> c.del_axes('NO AXES')
'NO AXES'

>>> c.set_axes(['domainaxis1', 'domainaxis0'])

>>> c.set_axes(['time', 'domainaxis0'])

>>> c.set_axes('time')

        '''
        if copy:
            value = deepcopy(value)
        
        if isinstance(value, basestring):
            value = (value,)
        else:
            value = tuple(value)
            
        return self._set_component('axes', value, copy=False)
    #--- End: def

    def set_method(self, value, copy=True):
        '''Set the method of the cell method.

.. versionadded:: 1.7.0

.. seealso:: `del_method`, `get_method`, `has_method`

:Parameters:

    value: `str`
        The value for the method.

    copy: `bool`, optional
        If True then set a deep copy of *value*.

:Returns:

     `None`

**Examples:**

>>> c.set_method('minimum')
>>> c.has_method()
True
>>> c.get_method()
'minimum'
>>> c.del_method()
>>> c.has_method()
False
>>> c.get_method('NO METHOD')
'NO METHOD'
>>> c.del_method('NO METHOD')
'NO METHOD'

        '''
        return self._set_component('method', value, copy=copy)
    #--- End: def

    def set_qualifier(self, qualifier, value, copy=True):
        '''Set a qualifier of the cell method.

.. versionadded:: 1.7.0

.. seealso:: `del_qualifier`, `get_qualifier`, `has_qualifier`,
             `qualifiers`
:Parameters:

    qualifier: `str`
        The name of the qualifier to be set.

    value: 
        The value for the qualifier.

    copy: `bool`, optional
        If True then set a deep copy of *value*.

:Returns:

     `None`

**Examples:**

>>> c.set_qualifier('where', 'land')
>>> c.get_qualifier('where')
'land'

        '''
        if copy:
            value = deepcopy(value)
        
        self._get_component('qualifiers')[qualifier] = value
    #--- End: def
    
#--- End: class
