# -*- coding: utf-8 -*-
#  This software and supporting documentation are distributed by
#      Institut Federatif de Recherche 49
#      CEA/NeuroSpin, Batiment 145,
#      91191 Gif-sur-Yvette cedex
#      France
#
# This software is governed by the CeCILL license version 2 under
# French law and abiding by the rules of distribution of free software.
# You can  use, modify and/or redistribute the software under the
# terms of the CeCILL license version 2 as circulated by CEA, CNRS
# and INRIA at the following URL "http://www.cecill.info".
#
# As a counterpart to the access to the source code and  rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty  and the software's author,  the holder of the
# economic rights,  and the successive licensors  have only  limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading,  using,  modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean  that it is complicated to manipulate,  and  that  also
# therefore means  that it is reserved for developers  and  experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and,  more generally, to use and operate it in the
# same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL license version 2 and that you accept its terms.
"""
This module defines the class :py:class:`ReadDiskItem` which is a subclass :py:class:`brainvisa.data.neuroData.Parameter`.
It is used to define an input data file as a parameter in a :py:class:`brainvisa.processes.Process` :py:class:`brainvisa.data.neuroData.Signature`.
"""
from __future__ import print_function
from __future__ import absolute_import
import os
import operator
# from soma.debug import print_stack
from soma.path import split_query_string
from soma.undefined import Undefined
from brainvisa.data.neuroData import Parameter
from brainvisa.processes import getDiskItemType
import brainvisa.processes
from brainvisa.data.neuroDiskItems import getFormat, getFormats, DiskItem, isSameDiskItemType, File, Directory
from brainvisa.processing.neuroException import HTMLMessage
import six
import sys
from functools import reduce
#----------------------------------------------------------------------------
def diskItemFilter(database, diskItem, required, explainRejection=False):
    if diskItem.type is not None:
        types = database.getTypeChildren(
            *database.getAttributeValues('_type', {}, required))
        if types and diskItem.type.name not in types:
            if explainRejection:
                return 'DiskItem type ' + repr(diskItem.type.name) + ' is not in ' + repr(tuple(types))
            return False
    formats = database.getAttributeValues('_format', {}, required)
    if formats and diskItem.format is not None:
        if diskItem.format.name not in formats:
            if explainRejection:
                if diskItem.format is None:
                    value = None
                else:
                    value = diskItem.format.name
                return 'DiskItem format ' + repr(value) + ' is not in ' + repr(tuple(formats))
            return False
    for key in required:
        if key in ('_type', '_format', '_declared_attributes_location'):
            continue
        values = database.getAttributeValues(key, {}, required)
        itemValue = diskItem.get(key, '')
        if (key == 'name_serie'):
            if itemValue != values:
                if explainRejection:
                    return 'DiskItem attribute ' + repr(key) + ' = ' + repr(itemValue) + ' != ' + repr(values)
                return False
        else:
            if itemValue not in values:
                if explainRejection:
                    return 'DiskItem attribute ' + repr(key) + ' = ' + repr(itemValue) + ' is not in ' + repr(tuple(values))
                return False
    return True
#----------------------------------------------------------------------------
[docs]class ReadDiskItem(Parameter):
    """
    The expected value for this parameter must be a readable :py:class:`brainvisa.data.neuroDiskItems.DiskItem`.
    This parameter type uses BrainVISA data organization to select possible files.
    :Syntax:
    ::
      ReadDiskItem( file_type_name, formats [, required_attributes, enableConversion=1, ignoreAttributes=0 ])
      formats <- format_name
      formats <- [ format_name, ... ]
      required_attributes <- { name : value, ...}
    file_type_name enables to select files of a specific type, that is to say DiskItem objects whose type is either file_name_type or a derived type. The formats list gives the exhaustive list of accepted formats for this parameter. But if there are some converters ( see the section called “Role”) from other formats to one of the accepted formats, they will be accepted too because BrainVISA can automatically convert the parameter (if enableConversion value is 1, which is the default). Warning : the type and formats given in parameters of ReadDiskItem constructor must have been defined in BrainVISA types and hierarchies files. required_attributes enables to add some conditions on the parameter value : it will have to match the given attributes value.
    This method of files selection ease file selection by showing the user only files that matches type and format required for this parameter. It also enables BrainVISA to automatically fill some parameters values. The ReadDiskItem class has methods to search matching diskitems in BrainVISA databases :
      * :py:meth:`ReadDiskItem.findItems(\<database directory diskitem\> <ReadDiskItem.findItems>`, <attributes>) : this method returns a list of diskitems that exist in that database and match type, format and required attributes of the parameter. It is possible to specify additional attributes in the method parameters. Found items will have the selected value for these attributes if they have the attribute, but these attributes are not mandatory. That's the difference with the required attributes set in the constructor.
      * :py:meth:`ReadDiskItem.findValues(\<value\>) <ReadDiskItem.findValues>` : this method searches diskitems matching the value in parameter. This value can be a diskitem, a filename, a dictionary of attributes.
      * :py:meth:`ReadDiskItem.findValue(\<value\>) <ReadDiskItem.findValue>` : this method returns the best among possible value, that is to say with the more common attributes, highest priority. If there is an ambiguity, it returns None.
    **Examples**
    >>> ReadDiskItem( 'Volume 3D', [ 'GIS Image', 'VIDA image' ] )
    >>> ReadDiskItem( 'Cortical folds graph', 'Graph', requiredAttributes = { 'labelled' : 'No', 'side' : 'left'} )
    In the first example, the parameter will accept only a file whose type is 3D Volume and format is either GIS image or VIDA image, or a format that can be converted to GIS or VIDA image. These types and formats must have been defined first. In the second example, the parameter value type must be "Cortical folds graph", its format must be "Graph". The required attributes add some conditions : the graph isn't labelled and represents the left hemisphere.
    """
    def __init__(self, diskItemType, formats, requiredAttributes={},
                enableConversion=True, ignoreAttributes=False, _debug=None, 
                exactType=False, section=None, enableMultiResolution=False ):
        Parameter.__init__(self, section)
        self._debug = _debug
        self.type = getDiskItemType(diskItemType)
        formatsList = list(getFormats(formats))
        if len(formatsList) != 0:
            self.formats = (formatsList[0], ) + tuple(sorted(formatsList))
        else:
            self.formats = ()
        self.enableConversion = enableConversion
        self.exactType = exactType
        self.enableMultiResolution = enableMultiResolution
        self._formatsWithConversion = None
        self.requiredAttributes = requiredAttributes
        self._write = False
        # self._modified = 0
        self.ignoreAttributes = ignoreAttributes
        self._selectedAttributes = {}
        self.valueLinkedNotifier.add(self.valueLinked)
    _formatsAndConversionCache = {}
    def _getDatabase(self):
        '''Returns the database this disk item belongs to
        '''
        # WARNING: don't import earlier to prevent a circular inclusion!
        from brainvisa.data import neuroHierarchy
        return neuroHierarchy.databases
    database = property(_getDatabase)
    # Allow direct affectation to requiredAttributes for backward compatibility
    def _getRequiredAttributes(self):
        #_debug = self._debug
        # if _debug is not None:
            # print('!_getRequiredAttributes!', self, self.type, self.formats,
            # self.enableConversion, file=_debug)
        if self._requiredAttributes['_format'] is None:
            cache = self._formatsAndConversionCache.get(
                (self.type.name, self.formats))
            # if _debug is not None:
        # print('!_getRequiredAttributes! 1', cache, file=_debug)
            if cache is None:
                formats = set(self.database.formats.getFormat(
                    f.name, f).name for f in self.formats)
                # if _debug is not None:
                 # print('!_getRequiredAttributes! 2', formats, file=_debug)
                formatsWithConversion = set()
                any = getDiskItemType('Any type')
                for f in getFormats(self.formats):
                    convs = brainvisa.processes.getConvertersTo(
                        (any, f), checkUpdate=False)
                    convs.update(brainvisa.processes.getConvertersTo(
                        (self.type, f), keepType=0, checkUpdate=False))
                    # if _debug is not None:
                      # print('!_getRequiredAttributes! 3', self.type,
                      # object.__repr__( self.type ), f, object.__repr__( f ),
                      # convs, file=_debug)
                    for type_format, converter in six.iteritems(convs):
                        typ, format = type_format
                        formatName = self.database.formats.getFormat(
                            format.name, format).name
                        # if _debug is not None:
                          # print('!_getRequiredAttributes! 4', formatName,
                          # file=_debug)
                        if formatName not in formats:
                            formatsWithConversion.add(formatName)
                cache = (formats, formatsWithConversion)
                self._formatsAndConversionCache[
                    (self.type.name, self.formats)] = cache
            formats, self._formatsWithConversion = cache
            # if _debug is not None:
                # print('!_getRequiredAttributes! 5', formats,
                # self._formatsWithConversion, file=_debug)
            if self.enableConversion:
                self._requiredAttributes['_format'] = self._formatsWithConversion.union(
                    formats)
            else:
                self._requiredAttributes['_format'] = formats
        self._requiredAttributes['_type'] = self.type.name
        # if _debug is not None:
            # print('!_getRequiredAttributes! 6', self._requiredAttributes[
            # '_format' ], file=_debug)
        return self._requiredAttributes
    def _setRequiredAttributes(self, value):
        self._requiredAttributes = value.copy()
        self._requiredAttributes['_type'] = self.type.name
        self._requiredAttributes['_format'] = None
    requiredAttributes = property(
        _getRequiredAttributes, _setRequiredAttributes)
[docs]    def valueLinked(self, parameterized, name, value):
        """This method is a callback called when the valueLinkedNotifier is activated.
        This notifier is shared between this parameter and the initial parameter in the static signature of the process.
        So when this function is called self is the initial parameter in the static signature.
        That why self is not used in this function.
        """
        if isinstance(value, DiskItem):
            parameterized.signature[
                name]._selectedAttributes = value.hierarchyAttributes()
        elif isinstance(value, dict):
            parameterized.signature[name]._selectedAttributes = value
        else:
            parameterized.signature[name]._selectedAttributes = {} 
[docs]    def checkValue(self, name, value):
        Parameter.checkValue(self, name, value)
        if ((value is not None) and (self.mandatory == True)):
            if not value.isReadable():
                raise RuntimeError(
                    HTMLMessage(_t_('the parameter <em>%s</em> is not readable or does not exist : %s') % (six.text_type(name), six.text_type(value)))) 
    def get_formats_order(self, database_dir):
          if database_dir in (None, ''):
              return self.formats
          from brainvisa.configuration import neuroConfig
          dbs = [d for d in neuroConfig.dataPath
                 if d.directory == database_dir]
          if len(dbs) != 1:
              return self.formats
          dbs = dbs[0]
          forder = getattr(dbs.expert_settings, 'preferred_formats_order',
                           None)
          if forder is None:
              return self.formats
          fdict = {}
          for f in self.formats:  # TODO: could use a cache for this dict
              fdict[f.name] = f
          hprio = [fdict[f] for f in forder if f in fdict]
          lprio = [f for f in self.formats if f not in hprio]
          return hprio + lprio
[docs]    def findValue(self, selection, requiredAttributes=None,
                  preferExisting=False, ignore_db_formats_sorting=False,
                  _debug=Undefined):
        '''Find the best matching value for the ReadDiskItem, according to the given selection criterions.
        The "best matching" criterion is the maximum number of common attributes with the selection, with required attributes satisfied.
        In case of WriteDiskItem, if preferExisting, also search for value already in database.
        If there is an ambiguity (no matches, or several equivalent matches), *None* is returned.
        Parameters
        ----------
        selection: diskitem, or dictionary, or str (filename)
        requiredAttributes: dictionary (optional)
        preferExisting: boolean
        ignore_db_formats_sorting: boolean
            by default, in Axon >= 4.6.2, formats are sorted according to
            database-specific formats orders, thus overriding process-specified
            formats orders. This flag allows to disable this behavior.
        _debug: file-like object (optional)
        Returns
        -------
        matching_value: :py:class:`DiskItem <brainvisa.data.neuroDiskItems.DiskItem>` instance, or *None*
        '''
        if _debug is Undefined:
            _debug = self._debug
        if selection is None:
            return None
        if requiredAttributes is None:
            requiredAttributes = self.requiredAttributes
        else:
            r = self.requiredAttributes.copy()
            r.update(requiredAttributes)
            requiredAttributes = r
        if _debug is not None:
            print('\n' + '-' * 70, file=_debug)
            print(self.__class__.__name__ +
                  '(\'' + str(self.type) + '\').findValue', file=_debug)
            if isinstance(selection, DiskItem):
                print('  value type = DiskItem', file=_debug)
                print('  fullPath = ', repr(selection), file=_debug)
                print('  type = ', repr(selection.type), file=_debug)
                print('  format = ', repr(selection.format), file=_debug)
                print('  attributes:', file=_debug)
                for n, v in selection.attributes().items():
                    print('   ', n, '=', repr(v), file=_debug)
            else:
                print('  value type =', type(selection), file=_debug)
                print('  value = ', selection, file=_debug)
            print('  required attributes:', file=_debug)
            for n, v in six.iteritems(requiredAttributes):
                print('   ', n, '=', repr(v), file=_debug)
            # print('- ' * 35, file=_debug)
            # print_stack( file=_debug )
            # print('- ' * 35, file=_debug)
        result = None
        fullSelection = None
        write = False
        if isinstance(selection, DiskItem):
            if selection.getHierarchy('_database') is None:
                # If DiskItem is not in a database, required attributes are
                # ignored (except _format)
                rr = {}
                if '_format' in requiredAttributes:
                    rr['_format'] = requiredAttributes['_format']
                requiredAttributes = rr
            if (selection.type is None or (selection.type is self.type) or
               (not self.exactType and (isSameDiskItemType( selection.type, self.type ) or isSameDiskItemType( self.type, selection.type )))) \
               
and self.diskItemFilter(selection, requiredAttributes):
                result = selection
            else:
                if _debug is not None:
                    print('  DiskItem rejected because:', self.diskItemFilter(
                        selection, requiredAttributes, explainRejection=True), file=_debug)
                if selection._write:
                    if _debug is not None:
                        print(
                            '  DiskItem has the _write attribute set to True',
                              file=_debug)
                    write = True
                fullSelection = selection.globalAttributes()
                if selection.type is None:
                    fullSelection['_type'] = None
                else:
                    fullSelection['_type'] = selection.type.name
                if selection.format is None:
                    fullSelection['_format'] = None
                else:
                    fullSelection['_format'] = selection.format.name
        elif isinstance(selection, six.string_types):
            if selection.startswith('{'):
                # String value is a dictionary
                return self.findValue(eval(selection), requiredAttributes=requiredAttributes, _debug=_debug)
            fullSelection = None
            if selection == '':
                return None  # avoid using cwd
            selection, query_string = split_query_string(selection)
            fileName = os.path.normpath(os.path.abspath(selection))
            for database in self.database.iterDatabases():
                if fileName.startswith(database.directory + os.sep):
                    break
            else:
                database = self.database
            result = database.getDiskItemFromFileName(
                fileName + query_string, None)
            if result is None:
                if _debug is not None:
                    print('  DiskItem not found in databases', file=_debug)
                directory = getFormat('Directory') in self.formats
                result = database.createDiskItemFromFileName(
                    fileName + query_string, None, directory=directory)
                if result is None:
                    if _debug is not None:
                        print(
                            '  DiskItem not created in databases from file name',
                              file=_debug)
                    # WARNING FIXME QUESTION ??
                    # Denis 2018/08/07
                    # createDiskItemFromFileName() 3 lines earlier already
                    # calls createDiskItemFromFormatExtension(), so if we
                    # are here, createDiskItemFromFormatExtension can just not
                    # work, right ?
                    result = database.createDiskItemFromFormatExtension(
                        fileName + query_string, None)
                    if result is None:
                        if _debug is not None:
                            print(
                                '  DiskItem not created in databases from format extension', 
                                file=_debug)
                        if os.path.exists( fileName ):
                            from brainvisa.tools.aimsGlobals import aimsFileInfo
                            file_type = aimsFileInfo(fileName).get('file_type')
                            if _debug is not None:
                                print(
                                    '  aimsFileInfo returned file_type =', repr(
                                        file_type),
                                      file=_debug)
                            if file_type == 'DICOM':
                                if _debug is not None:
                                    print(
                                        '  creating DICOM DiskItem', file=_debug)
                                result = File(fileName + query_string, None)
                                result.format = getFormat('DICOM image')
                                result.type = None
                                result._files = [fileName]
                                result.readAndUpdateMinf()
                    else:
                        result.readAndUpdateMinf()
                else:
                    result.readAndUpdateMinf()
                    if _debug is not None:
                        print('  DiskItem created in databases', file=_debug)
                if result is None:
                    if _debug is not None:
                        print(
                            '  no format found for file name extension', file=_debug)
                    directoryFormat = getFormat('Directory')
                    if directoryFormat in self.formats:
                        # In this case, item is a directory
                        if _debug is not None:
                            print(
                                '  no extension ==> create Directory item', file=_debug)
                        result = Directory(fileName + query_string, None)
                        result.format = directoryFormat
                        result._files = [fileName]
                        result._identified = False
                        result.type = self.type
                        result.readAndUpdateMinf()
                elif _debug is not None:
                    print(
                        '  found matching format:', result.format, file=_debug)
            elif _debug is not None:
                print('  DiskItem found in databases', file=_debug)
            if result is not None:
                if result.type is None:
                    # Set the result type if this is not already done
                    result.type = self.type
                if result.getHierarchy('_database') is None:
                    # If DiskItem is not in a database, required attributes are
                    # ignored
                    requiredAttributes = {
                        '_format': requiredAttributes.get('_format')}
                    # if the diskitem format does not match the required
                    # format, and if required format contains Series of
                    # diskitem.format, try to change the format of the diskItem
                    # to a format series.
                    if not self.diskItemFilter(result, requiredAttributes):
                        if ("Series of " + result.format.name in requiredAttributes.get("_format")):
                            database.changeDiskItemFormatToSeries(result)
                if not self.diskItemFilter(result, requiredAttributes):
                    if _debug is not None:
                        print('  DiskItem rejected because:', self.diskItemFilter(
                            result, requiredAttributes, explainRejection=True), file=_debug)
                    result = None
            if result is None and self._parameterized is not None \
                
and self._name is not None \
                    
and not self._parameterized().isDefault(self._name):
                try:
                    result = database.createDiskItemFromFormatExtension(
                        selection + query_string)
                    if result is not None:
                        result.type = self.type
                except Exception:
                    pass
        elif isinstance(selection, dict):
            if '_declared_attributes_location' in list(selection.keys()):
                # keep it could cause problems because it should not be
                # compared between disk items
                del selection['_declared_attributes_location']
            else:
                pass
            fullSelection = dict(selection)
        if result is None and fullSelection is not None:
            values = []
            if preferExisting and (write or self._write):
                fullAttributes = fullSelection.copy()
                fullAttributes.update(requiredAttributes)
                values = list(self._findValues(fullSelection, fullAttributes,
                                               write=False,
                                               _debug=_debug))
            if not values:
                values = list(self._findValues(fullSelection, requiredAttributes,
                                               write=(write or self._write),
                                               _debug=_debug))
            if values:
                if len(values) == 1:
                    result = values[0]
                else:
                    # Find the item with the "smallest" "distance" from item
                    values = sorted((self.diskItemDistance(i, selection), i)
                                    for i in values)
                    if _debug is not None:
                        print('  findValue priority sorted items:', file=_debug)
                        print('dist to:',
                              selection.attributes() if isinstance(selection, DiskItem) else selection,
                              file=_debug)
                        for l in values:
                            print('   ', l, l[1].attributes(), file=_debug)
                    if values[0][0] != values[1][0]:
                        result = values[0][1]
                    else:
                        refOrder, refDiskItem = values[0]
                        refHierarchy = refDiskItem.hierarchyAttributes()
                        # WARNING: this _declared_attributes_location attribute causes
                        # problems since it should not be compared between disk
                        # items
                        try:
                            del refHierarchy['_declared_attributes_location']
                        except KeyError:
                            pass
                        differentOnFormatOnly = [refDiskItem]
                        for checkOrder, checkDiskItem in values[1:]:
                            if checkOrder != refOrder:
                                break
                            checkHierarchy = checkDiskItem.hierarchyAttributes(
                            )
                            # WARNING: this _declared_attributes_location attribute causes
                            # problems since it should not be compared between
                            # disk items
                            try:
                                del checkHierarchy[
                                    '_declared_attributes_location']
                            except KeyError:
                                pass
                            if ((refHierarchy == checkHierarchy) and (refDiskItem.format.name != checkDiskItem.format.name)):
                                differentOnFormatOnly.append(checkDiskItem)
                            else:
                                differentOnFormatOnly = []
                                break
                        if differentOnFormatOnly:
                            formatsToTest = self.get_formats_order(
                                differentOnFormatOnly[0].getHierarchy(
                                    '_database'))
                            for preferredFormat in formatsToTest:
                                l = [
                                    i for i in differentOnFormatOnly if i.format is preferredFormat]
                                if l:
                                    result = l[0]
                                    break
                            if _debug is not None:
                                print(
                                    '  top priority values differ only by formats:',
                                      file=_debug)
                                for i in differentOnFormatOnly:
                                    print('   ', i.format, file=_debug)
                                if result:
                                    print(
                                        '  chosen format:', result.format, file=_debug)
                        elif _debug is not None:
                            print(
                                '  top priority values differ on ontology attributes ==> no selection on format', file=_debug)
                            print(
                                'ref   element:', refDiskItem.fullPath(), refHierarchy, file=_debug)
                            print(
                                'check element:', checkDiskItem.fullPath(), checkHierarchy, file=_debug)
        # this block of code was originally in WriteDiskItem.findValue().
        # We do not remember what it was exactly meant for, and did not do
        # correctly its job. We don't completely remove it because it might solve
        # a very specific situation, but we also fix it: check requiredAttributes,
        # and select preferred format instead of the first one.
        # (Denis 2015/10/09)
        if result is None and write and isinstance( selection, DiskItem ) and \
            
(selection.type is None or selection.type is self.type
             or (not self.exactType
                 and isSameDiskItemType(selection.type, self.type))):
            format = requiredAttributes.get('_format') or self.formats[0]
            if _debug is not None:
                print(
                    '  No unique best selection found. But compatible input DiskItem. Checking format.', file=_debug)
                print('    Format:', format, file=_debug)
            if format in self.formats and format != selection.format:
                # check requiredAttributes
                sel_attr = selection.hierarchyAttributes()
                ok = True
                for attrib, value in six.iteritems(requiredAttributes):
                    if not attrib.startswith('_') and \
                            
(attrib not in sel_attr or sel_attr[attrib] != value):
                        ok = False
                        break
                if _debug:
                    print('    checking RequiredAttributes:', ok, file=_debug)
                if ok:
                    result = self.database.changeDiskItemFormat(
                        selection, format.name)
                    if _debug is not None:
                        print(
                            '  selection is compatible with a different format, select: %s'
                          % format, file=_debug)
        if _debug is not None:
            print('-> findValue return', result, file=_debug)
            if result is not None:
                print('-> type:', result.type, file=_debug)
                print('-> format:', result.format, file=_debug)
                print('-> attributes:', file=_debug)
                for n, v in result.attributes().items():
                    print('->  ', n, '=', v, file=_debug)
            print('-' * 70 + '\n', file=_debug)
            _debug.flush()
        return result 
[docs]    def diskItemDistance(self, diskItem, other):
        '''Returns a value that represents a sort of distance between two
           DiskItems.
            The distance is not a number but distances can be sorted.'''
        # Count the number of common hierarchy attributes
        if isinstance(other, DiskItem):
            if other.type is not None:
                other_type = other.type.name
            else:
                other_type = None
            other_priority = other.priority()
        else:
            other_type = other.get('_type')
            other_priority = 0
        if diskItem.type is not None:
            diskItem_type = diskItem.type.name
        else:
            diskItem_type = None
        if isinstance(other, DiskItem):
            getHierarchy = other.getHierarchy
            getNonHierarchy = other.getNonHierarchy
        else:
            getHierarchy = other.get
            getNonHierarchy = other.get
        filtered_out_attribs = set(
            ['_ontology', '_declared_attributes_location'])
        hierarchyCommon = reduce(
            operator.add,
          ((getHierarchy(n) == v)
           for n, v in six.iteritems(diskItem.hierarchyAttributes())
           if n not in filtered_out_attribs),
          (int(diskItem_type == other_type)))
        # Count the number of common non hierarchy attributes
        nonHierarchyCommon = reduce(
            operator.add,
          ((getNonHierarchy(n) == v)
           for n, v in six.iteritems(diskItem.nonHierarchyAttributes())),
          (int(diskItem_type == other_type)))
        if getattr(other, '_write', False) and diskItem.isReadable():
            readable = -1
        else:
            readable = 0
        return (-hierarchyCommon, other_priority - diskItem.priority(), -nonHierarchyCommon, readable) 
[docs]    def findValues(self, selection, requiredAttributes={}, write=False,
                   _debug=Undefined):
        '''Find all DiskItems matching the selection criterions
        Parameters
        ----------
        selection: :py:class:`DiskItem <brainvisa.data.neuroDiskItems.DiskItem>` or dictionary
        requiredAttributes: dictionary (optional)
        write: bool (optional)
            if write is True, look for write diskitems
        _debug: file-like object (optional)
        '''
        return self._findValues(selection, requiredAttributes, write, _debug) 
    def _findValues(self, selection, requiredAttributes, write, _debug=Undefined):
        if _debug is Undefined:
            _debug = self._debug
        if requiredAttributes is None:
            requiredAttributes = self.requiredAttributes
        keySelection = {}
        if selection:
            # in selection attributes, choose only key attributes because the
            # request must not be too restrictive to avoid failure. The results
            # will be sorted by distance to the selection later.
            keyAttributes = self.database.getTypesKeysAttributes(
                self.type.name)
            keySelection = dict((i, selection[i]) for i in keyAttributes
                                if i in selection)
        # type is required in database selection
        if '_type' not in keySelection:
            keySelection['_type'] = self.type.name
        readValues = (i for i in self.database.findDiskItems(
            keySelection, _debug=_debug, exactType=self.exactType,
            write=self._write, **requiredAttributes)
            if self.diskItemFilter(i, requiredAttributes))
        if write:
            # use selection attributes to create a new diskitem
            fullPaths = set()
            for item in readValues:
                fullPaths.add(item.fullPath())
                yield item
            requiredAttributes = dict(requiredAttributes)  # copy
            if '_type' not in requiredAttributes:
                # if selection is a diskitem with a different type as self
                requiredAttributes['_type'] = self.type.name
            if '_format' not in requiredAttributes:
                requiredAttributes['_format'] = self.formats
            # Do not allow formats that require a conversion in DiskItem
            # creation
            if self._formatsWithConversion:
                oldFormats = requiredAttributes.get('_format')
                if oldFormats is not None:
                    requiredAttributes['_format'] = [
                        i for i in oldFormats if i not in self._formatsWithConversion]
            for item in self.database.createDiskItems(selection, _debug=_debug, exactType=self.exactType, **requiredAttributes):
                if self.diskItemFilter(item, requiredAttributes):
                    if item.fullPath() not in fullPaths:
                        yield item
                elif _debug is not None:
                    print(' ', item, 'rejected because:', self.diskItemFilter(
                        item, requiredAttributes, explainRejection=True), file=_debug)
            if self._formatsWithConversion:
                requiredAttributes['_format'] = oldFormats
        else:
            for i in readValues:
                yield i
    def diskItemFilter(self, *args, **kwargs):
        return diskItemFilter(self.database, *args, **kwargs)
        # if diskItem.type is not None:
            # types = self.database.getTypeChildren( *self.database.getAttributeValues( '_type', {}, required ) )
            # if types and diskItem.type.name not in types:
                # if explainRejection:
                    # return 'DiskItem type ' + repr(diskItem.type.name) + ' is not in ' + repr( tuple(types) )
                # return False
        # formats = self.database.getAttributeValues( '_format', {}, required )
        # if formats and not(diskItem.format is None) :
            # if diskItem.format.name not in formats:
                # if explainRejection:
                    # if diskItem.format is None :
                        # value = None
                    # else :
                        # value = diskItem.format.name
                    # return 'DiskItem format ' + repr(value) + ' is not in ' + repr( tuple(formats) )
                # return False
        # for key in required:
            # if key in ( '_type', '_format' ): continue
            # values = self.database.getAttributeValues( key, {}, required )
            # itemValue = diskItem.get( key, Undefined )
            # if itemValue is Undefined or itemValue not in values:
                # if explainRejection:
                    # if itemValue is Undefined:
                        # return 'DiskItem do not have the required ' + repr( key ) + ' attribute'
                    # else:
                        # return 'DiskItem attribute ' + repr(key) + ' = ' + repr( itemValue ) + ' is not in ' + repr( tuple(values) )
                # return False
        # return True
[docs]    def typeInfo(self, translator=None):
        if translator:
            translate = translator.translate
        else:
            translate = _t_
        formats = ''
        for f in self.formats:
            if formats:
                formats += ', '
            formats += translate(f.name)
        return ((translate('Type'), translate(self.type.name)),
                (translate('Access'), translate('input')),
                (translate('Formats'), formats)) 
[docs]    def toolTipText(self, parameterName, documentation):
        result = '<center>' + parameterName
        if not self.mandatory:
            result += ' (' + _t_('optional') + ')'
        result += '</center><hr><b>' + _t_( 'Type' ) + \
                  
':</b> ' + self.type.name + '<br><b>' + _t_( 'Formats' ) + \
                  
':</b><blockquote>'
        br = 0
        for f in self.formats:
            if br:
                result += '<br>'
            else:
                br = 1
            result += f.name + ': ' + str(f.getPatterns().patterns[0].pattern)
        result += '</blockquote><b>' + _t_( 'Description' ) + '</b>:<br>' + \
                  
documentation
        return result 
[docs]    def editor(self, parent, name, context):
        # WARNING: don't import at the beginning of the module,
        # it would cause a circular inclusion
        from brainvisa.data.qtgui.readdiskitemGUI import DiskItemEditor
        return DiskItemEditor(self, parent, name, context=context,
                              write=self._write,
                              process=getattr(context, 'parameterized', None)) 
[docs]    def listEditor(self, parent, name, context):
        # WARNING: don't import at the beginning of the module,
        # it would cause a circular inclusion
        from brainvisa.data.qtgui.readdiskitemGUI import DiskItemListEditor
        return DiskItemListEditor(self, parent, name, context=context,
                                  write=self._write,
                                  process=getattr(context, 'parameterized', None))