from __future__ import division
import logging

import numpy as np

from .unionize_record import Unionize


class TissuecyteBaseUnionize(Unionize):

    __slots__ = ['sum_pixels', 'sum_projection_pixels', 'sum_projection_pixel_intensity', 
                 'max_voxel_index', 'max_voxel_density', 'projection_density', 
                 'projection_energy', 'projection_intensity', 'direct_sum_projection_pixels', 
                 'sum_pixel_intensity']


    def __init__(self):
        '''A unionize record summarizing observations from a tissuecyte 
        projection experiment
        '''
        
        for key in self.__slots__:
            setattr(self, key, 0)
        
        
    def propagate(self, ancestor, copy_all=False):
        '''Update a rootward unionize with data from this unionize record
        
        Parameters
        ----------
        ancestor : TissuecyteBaseUnionize
            will be updated
            
        Returns
        -------
        ancestor : TissuecyteBaseUnionize
        
        '''
        
        ancestor.sum_pixels += self.sum_pixels
        ancestor.sum_projection_pixels += self.sum_projection_pixels
        ancestor.sum_projection_pixel_intensity += self.sum_projection_pixel_intensity
        ancestor.sum_pixel_intensity += self.sum_pixel_intensity

        if ancestor.max_voxel_density <= self.max_voxel_density:
            ancestor.max_voxel_density = self.max_voxel_density
            ancestor.max_voxel_index = self.max_voxel_index

        if copy_all:
            ancestor.direct_sum_projection_pixels += self.direct_sum_projection_pixels
        
        return ancestor
        
        
    def set_max_voxel(self, density_array, low):
        '''Find the voxel of greatest density in this unionizes spatial domain
        
        Parameters
        ----------
        density_array : ndarray
            Float values are densities per voxel
        low : int
            index in full flattened, sorted array of starting voxel
        
        '''
    
        if self.sum_projection_pixels > 0:
        
            self.max_voxel_index = np.argmax(density_array)
            self.max_voxel_density = density_array[self.max_voxel_index]
            
            self.max_voxel_index += low
            
            
    def output(self, output_spacing_iso, volume_scale, target_shape, sort):
        '''Generate derived data for this unionize
        
        Parameters
        ----------
        output_spacing_iso : numeric
            Isometric spacing of reference space in microns
        volume_scale : numeric
            Scale factor mapping pixels to microns^3
        target_shape : array-like of numeric
            Shape of reference space
        
        '''
        
        if self.sum_pixels > 0:
            self.projection_density = self.sum_projection_pixels / self.sum_pixels
            self.projection_energy = self.sum_projection_pixel_intensity / self.sum_pixels
        
        if self.sum_projection_pixels > 0:
            self.projection_intensity = self.sum_projection_pixel_intensity / self.sum_projection_pixels
        
        output = {k: getattr(self, k) for k in self.__slots__}

        output['volume'] = self.sum_pixels * volume_scale
        output['direct_projection_volume'] = self.direct_sum_projection_pixels * volume_scale
        output['projection_volume'] = self.sum_projection_pixels * volume_scale
        output['sum_pixel_intensity'] = self.sum_pixel_intensity

        if self.max_voxel_index > 0:
            self.max_voxel_index = sort[self.max_voxel_index]
            mv_pos = np.unravel_index([self.max_voxel_index], dims=target_shape, order='C')
            if len(mv_pos[0]) == 0:
                mv_pos = [[0], [0], [0]]
        else:
            mv_pos = [[0], [0], [0]]

        output['max_voxel_x'] = mv_pos[0][0] * output_spacing_iso
        output['max_voxel_y'] = mv_pos[1][0] * output_spacing_iso
        output['max_voxel_z'] = mv_pos[2][0] * output_spacing_iso
        del output['max_voxel_index']
        
        return output
        
        
class TissuecyteInjectionUnionize(TissuecyteBaseUnionize):
    
    def calculate(self, low, high, data_arrays):
        data_arrays = self.slice_arrays(low, high, data_arrays)
        
        self.sum_pixels = np.multiply(data_arrays['sum_pixels'], data_arrays['injection_fraction']).sum()
        self.sum_projection_pixels = np.multiply(data_arrays['sum_pixels'], data_arrays['injection_density']).sum()
        self.direct_sum_projection_pixels = self.sum_projection_pixels
        self.sum_projection_pixel_intensity = np.multiply(data_arrays['sum_pixels'], data_arrays['injection_energy']).sum()
        self.sum_pixel_intensity = data_arrays['injection_sum_pixel_intensities'].sum()

        self.set_max_voxel(data_arrays['injection_density'], low)
            
            
class TissuecyteProjectionUnionize(TissuecyteBaseUnionize):
    
    def calculate(self, low, high, data_arrays, ij_record):
        data_arrays = self.slice_arrays(low, high, data_arrays)
        
        nex = np.logical_or(data_arrays['injection_fraction'], np.logical_not(data_arrays['aav_exclusion_fraction']))
        
        self.sum_pixels = data_arrays['sum_pixels'][nex].sum()
        self.sum_pixels -= ij_record.sum_pixels
        
        self.sum_projection_pixels = np.multiply(data_arrays['sum_pixels'], data_arrays['projection_density'])[nex].sum()
        self.sum_projection_pixels -= ij_record.sum_projection_pixels
        self.direct_sum_projection_pixels = self.sum_projection_pixels
        
        self.sum_projection_pixel_intensity = np.multiply(data_arrays['sum_pixels'], data_arrays['projection_energy'])[nex].sum()
        self.sum_projection_pixel_intensity -= ij_record.sum_projection_pixel_intensity

        self.sum_pixel_intensity = float(data_arrays['sum_pixel_intensities'][nex].sum())
        self.sum_pixel_intensity -= ij_record.sum_pixel_intensity
        
        valid_density = np.multiply(nex, data_arrays['projection_density'])
        valid_density = np.multiply(valid_density, 1 - data_arrays['injection_fraction']) 
        self.set_max_voxel(valid_density, low)
