/*******************************************************************************
 * Copyright (c) 2013 Attila Kovacs <attila[AT]sigmyne.com>.
 * All rights reserved. 
 * 
 * This file is part of crush.
 * 
 *     crush is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     crush is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 * 
 *     You should have received a copy of the GNU General Public License
 *     along with crush.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Attila Kovacs <attila[AT]sigmyne.com> - initial API and implementation
 ******************************************************************************/
package crush.filters;

import java.util.ArrayList;
import java.util.Arrays;

import crush.Channel;
import crush.ChannelGroup;
import crush.Integration;


public class MultiFilter extends VariedFilter {
	/**
	 * 
	 */
	private static final long serialVersionUID = -9155204147970228847L;

	private ArrayList<Filter> filters = new ArrayList<>();
	
	private int enabled = 0;
	
	public MultiFilter(Integration<?> integration) {
		super(integration);
	}
	
	@SuppressWarnings("unchecked")
    @Override
	public MultiFilter clone() {	
		MultiFilter clone = (MultiFilter) super.clone();
		if(filters != null) clone.filters = (ArrayList<Filter>) filters.clone();
		return clone;
	}
	
	@Override
    public MultiFilter copy(boolean withContents) {
	    MultiFilter copy = (MultiFilter) super.copy(withContents);
	    if(filters != null) for(int i=filters.size(); --i >= 0; ) {
	        Filter f = filters.get(i);
	        if(f != null) copy.filters.set(i, f.copy(withContents));
	    }
	    return copy;
	}
		
	public ArrayList<Filter> getFilters() { return filters; }
	
	@Override
	protected void setIntegration(Integration<?> integration) {
		super.setIntegration(integration);
		
		if(filters != null) for(Filter filter : filters) {
			filter.setTempData(getTempData());
			filter.setIntegration(integration);
		}
	}
	
	
	@Override
	public void setChannels(ChannelGroup<?> channels) {
		super.setChannels(channels);
		if(filters != null) for(Filter filter : filters) filter.setChannels(channels);		
	}
	
	public synchronized int size() {
		return filters.size();
	}
	
	public synchronized boolean contains(Filter filter) {
		return filters.contains(filter);
	}
	
	public synchronized boolean contains(Class<? extends Filter> filterClass) {
		for(Filter filter : filters) if(filter.getClass().equals(filterClass)) return true;
		return false;
	}
	
	public Filter get(Class<? extends Filter> filterClass) {
		for(Filter filter : filters) if(filter.getClass().equals(filterClass)) return filter;
		return null;
	}
	
	public synchronized void addFilter(Filter filter) {
		if(filter.integration == null) filter.setIntegration(integration);
		else if(filter.integration != integration) 
			throw new IllegalStateException("Cannot compound filter from a different integration.");
		filter.setChannels(getChannels());
		filters.add(filter);
	}

	public synchronized void setFilter(int i, Filter filter) {
		if(filter.integration == null) filter.setIntegration(integration);
		else if(filter.integration != integration) 
			throw new IllegalStateException("Cannot compound filter from a different integration.");
		filter.setChannels(getChannels());
		filters.set(i, filter);
	}
	
	public synchronized void remove(Filter filter) {
		filters.remove(filter);
	}
	
	public synchronized Filter remove(int i) {
		return filters.remove(i);
	}
	
	
	@Override
	public void updateConfig() {
		super.updateConfig();
		for(Filter filter : filters) filter.updateConfig();			
	}
	
	@Override 
	public boolean isEnabled() {
		if(!super.isEnabled()) return false;
		
		enabled = 0;
		for(Filter filter : filters) if(filter.isEnabled()) enabled++;
		return enabled > 0;
	}
	
	/*
	@Override
	public boolean apply(boolean report) {
		updateConfig();
		
		if(enabled > 1) return super.apply(report);
		else for(Filter filter : filters) if(filter.isEnabled()) return filter.apply(report);
		return false;
	}
	*/
	
	@Override
	protected void preFilter() {
		super.preFilter();
		for(Filter filter : filters) if(filter.isEnabled()) filter.preFilter();		
	}
	
	@Override
	protected void postFilter() {
		for(Filter filter : filters) if(filter.isEnabled()) filter.postFilter();	
		super.postFilter();
	}
	

	@Override
	protected synchronized void fftFilter(Channel channel) {
		final float[] data = getTempData();
		final float[] filtered = integration.getFloats();
		
		Arrays.fill(data, integration.size(), data.length, 0.0F);
		Arrays.fill(filtered, 0.0F);
		
		integration.getFFT().real2Amplitude(data);	
		data[0] = 0.0F;
		
		// Apply the filters sequentially...
		for(int n=0; n<filters.size(); n++) {
			final Filter filter = filters.get(n);
			
			if(!filter.isEnabled()) continue;
			
			// A safety check to make sure the filter uses the spectrum from the master data array...
			if(filter.getTempData() != data) filter.setTempData(data);
			
			filter.points = points;
			filter.preFilter(channel);
			filter.updateProfile(channel);
			
			final float nyquistPass = (float) filter.responseAt(nf);
			filtered[1] = data[1] * (1.0F - nyquistPass);
			data[1] *= nyquistPass;
		
			for(int i=2; i<data.length; ) {
				final float pass = (float) filter.responseAt(i >> 1);
			
				// Apply the filter to the real part...
				filtered[i] = (1.0F - pass) * data[i];
				data[i++] *= pass;
				
				// Apply the filter to the imaginary part
				filtered[i] = (1.0F - pass) * data[i];
				data[i++] *= pass;
			}
			
			filter.postFilter(channel);
		}
		
		// Convert to rejected signal...
		integration.getFFT().amplitude2Real(filtered);
		
		// Remove the DC component...
		if(isPedantic) levelForChannel(channel, filtered);
		
		// Subtract the rejected signal...
		final int c = channel.getIndex();
		integration.validParallelStream().forEach(f -> remove(filtered[f.index], f, c));
		
		Integration.recycle(filtered);
	}
	
	@Override
	protected double responseAt(int fch) {
		double pass = 1.0;
		for(Filter filter : filters) if(filter.isEnabled()) {
			pass *= filter.responseAt(fch);
			// Once the filtering is total, there is no need to continue compounding...
			if(pass == 0.0) return 0.0;
		}
		return pass;
	}

	
	@Override
	public String getID() {
		String id = "";
		for(Filter filter : filters) if(filter.isEnabled()) {
			if(id.length() != 0) id += ":";
			id += filter.getID(); 
		}
		return id;
	}

	@Override
	public String getConfigName() {
		return "filter";
	}

}
