# -*- coding: utf-8 -*-
#pylint: disable-msg=E0611, E1103, C0103, R0901, R0902, R0903, R0904, W0232
#------------------------------------------------------------------------------
# Copyright (c) 2007-2019, Acoular Development Team.
#------------------------------------------------------------------------------
"""Implements support for array microphone arrangements

.. autosummary::
    :toctree: generated/

    MicGeom

"""

# imports from other packages
from numpy import array, average
from traits.api import HasPrivateTraits, Property, File, \
CArray, cached_property, on_trait_change, ListInt
from os import path

from .internal import digest


class MicGeom( HasPrivateTraits ):
    """
    Provides the geometric arrangement of microphones in the array.
    
    The geometric arrangement of microphones is read in from an 
    xml-source with element tag names `pos` and attributes Name, `x`, `y` and `z`. 
    Can also be used with programmatically generated arrangements.
    """

    #: Name of the .xml-file from wich to read the data.
    from_file = File(filter=['*.xml'],
        desc="name of the xml file to import")

    #: Basename of the .xml-file, without the extension; is set automatically / readonly.
    basename = Property( depends_on = 'from_file',
        desc="basename of xml file")

    #: List that gives the indices of channels that should not be considered.
    #: Defaults to a blank list.
    invalid_channels = ListInt(
        desc="list of invalid channels")

    #: Number of microphones in the array; readonly.
    num_mics = Property( depends_on = ['mpos', ],
        desc="number of microphones in the geometry")

    #: Center of the array (arithmetic mean of all used array positions); readonly.
    center = Property( depends_on = ['mpos', ],
        desc="array center")

    #: Positions as (3, :attr:`num_mics`) array of floats, may include also invalid
    #: microphones (if any). Set either automatically on change of the
    #: :attr:`from_file` argument or explicitely by assigning an array of floats.
    mpos_tot = CArray(dtype=float,
        desc="x, y, z position of all microphones")

    #: Positions as (3, :attr:`num_mics`) array of floats, without invalid
    #: microphones; readonly.
    mpos = Property( depends_on = ['mpos_tot', 'invalid_channels'],
        desc="x, y, z position of microphones")

    # internal identifier
    digest = Property( depends_on = ['mpos', ])

    @cached_property
    def _get_digest( self ):
        return digest(self)

    @cached_property
    def _get_basename( self ):
        return path.splitext(path.basename(self.from_file))[0]

    @cached_property
    def _get_mpos( self ):
        if len(self.invalid_channels)==0:
            return self.mpos_tot
        allr=[i for i in range(self.mpos_tot.shape[-1]) if i not in self.invalid_channels]
        return self.mpos_tot[:, array(allr)]

    @cached_property
    def _get_num_mics( self ):
        return self.mpos.shape[-1]

    @cached_property
    def _get_center( self ):
        if self.mpos.any():
            center = average(self.mpos,axis=1)
            # set very small values to zero
            center[abs(center) < 1e-16] = 0.
            return center

    @on_trait_change('basename')
    def import_mpos( self ):
        """
        Import the microphone positions from .xml file.
        Called when :attr:`basename` changes.
        """
        if not path.isfile(self.from_file):
            # no file there
            self.mpos_tot = array([], 'd')
            self.num_mics = 0
            return
        import xml.dom.minidom
        doc = xml.dom.minidom.parse(self.from_file)
        names = []
        xyz = []
        for el in doc.getElementsByTagName('pos'):
            names.append(el.getAttribute('Name'))
            xyz.append(list(map(lambda a : float(el.getAttribute(a)), 'xyz')))
        self.mpos_tot = array(xyz, 'd').swapaxes(0, 1)

