/*******************************************************************************
 * Copyright (c) 2010 Attila Kovacs <attila_kovacs@post.harvard.edu>.
 * 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_kovacs@post.harvard.edu> - initial API and implementation
 ******************************************************************************/

// Copyright (c) 2009,2010 Attila Kovacs

package crush;

import util.*;

import java.util.*;


public abstract class SourceModel<InstrumentType extends Instrument<?>, ScanType extends Scan<? extends InstrumentType, ?>> implements Cloneable {
	public InstrumentType instrument;
	public Configurator options;
	public Vector<ScanType> scans;
	public boolean isValid = false;
	public int generation = 0;
	public String commandLine;
		
	protected Vector<Integration<?,?>> queue = new Vector<Integration<?,?>>();
	
		
	public SourceModel(InstrumentType instrument) {
		this.instrument = instrument;
	}
	
	@Override
	public Object clone() {
		try { return super.clone(); }
		catch(CloneNotSupportedException e) { return null; }
	}	
	
	public boolean hasOption(String name) {
		return options.isConfigured(name);
	}

	public Configurator option(String name) {
		return options.get(name);
	}
	
	@SuppressWarnings("unchecked")
	public SourceModel<InstrumentType, ScanType> copy() {
		SourceModel<InstrumentType, ScanType> copy = (SourceModel<InstrumentType, ScanType>) clone();
		copy.queue = new Vector<Integration<?,?>>();
		return copy;
	}
	
	public void create(Collection<? extends Scan<?,?>> collection) {
		this.scans = new Vector<ScanType>();
		for(Scan<?,?> scan : collection) scans.add((ScanType) scan);
		
		double janskyPerBeam = scans.get(0).instrument.janskyPerBeam();
		for(Scan<?,?> scan : scans) for(Integration<?,?> integration : scan) {
			integration.sourceModel = this;
			integration.gain *= janskyPerBeam / integration.instrument.janskyPerBeam();
		}

		// Set the global units to those of the first scan...
		instrument.options.process("jansky", Double.toString(janskyPerBeam));
	}

	public void reset() {
		isValid = false;
	}
	
	public abstract void add(SourceModel<?, ?> model, double weight);
	
	public abstract void add(Integration<?,?> integration);
	
	public abstract void process(Scan<?,?> scan);
		
	public abstract void sync(Integration<?,?> integration);
	
	public abstract void setBase();
		
	public abstract void write(String path) throws Exception;
	
	public synchronized void extract() throws InterruptedException {	
		System.err.print("[Source] ");
		
		Vector<ExtractionThread> threads = new Vector<ExtractionThread>();
	
		for(int i=0; i<CRUSH.useCPUs; i++) threads.add(new ExtractionThread());
		for(int i=0; i<scans.size(); i++) threads.get(i % threads.size()).addScan(scans.get(i));

		for(int i=1; i<CRUSH.useCPUs && i<threads.size();) {
			if(threads.get(i).collection.size() == 0) threads.remove(i);
			else i++;
		}

		reset();
		
		for(ExtractionThread thread : threads) thread.start();

		while(!queue.isEmpty()) wait();

		sync();
		
		System.err.println();
	}
	
	public synchronized void sync() throws InterruptedException {
		// Coupled with blanking...
		if(hasOption("source.nosync")) return;
		if(hasOption("source.coupling")) System.err.print("(coupling) ");
		
		System.err.print("(sync) ");
		
		Vector<SyncThread> threads = new Vector<SyncThread>();

		for(int i=0; i<CRUSH.useCPUs; i++) threads.add(new SyncThread());
		for(int i=0; i<scans.size(); i++) threads.get(i % threads.size()).addScan(scans.get(i));

		for(int i=1; i<CRUSH.useCPUs && i<threads.size();) {
			if(threads.get(i).collection.size() == 0) threads.remove(i);
			else i++;
		}
	
		for(SyncThread thread : threads) thread.start();

		while(!queue.isEmpty()) wait();
		
		generation++;
		
		setBase();
	}
	

	protected synchronized void checkout(Integration<?,?> scan) { 
		queue.remove(scan); 
		notifyAll();
	}

	public double getBlankingLevel() {
		return hasOption("blank") ? option("blank").getDouble() : Double.NaN;
	}
		
	public double getClippingLevel() {
		return hasOption("clip") ? option("clip").getDouble() : Double.NaN;
	}
	
	public double getPointSize(Instrument<?> instrument) { return instrument.resolution; }

	public double getSourceSize(Instrument<?> instrument) {
		return instrument.getSourceSize();
	}
	
	public abstract String getSourceName();
	
	public String getDefaultCoreName() {
		String name = getSourceName() + "." + scans.get(0).serialNo;
		if(scans.size() > 1) name += "-" + scans.get(scans.size()-1).serialNo;
		return name;
	}
	
	
	class SyncThread extends Thread {
		private Vector<Scan<?,?>> collection = new Vector<Scan<?,?>>();
	
		public SyncThread() {}
		
		public void addScan(Scan<?,?> scan) { 
			collection.add(scan); 
			queue.addAll(scan);
		}

		@Override
		public void run() {
			for(Scan<?,?> scan : collection) for(Integration<?,?> integration : scan) {
				sync(integration);
				checkout(integration);
			}
		}	
	}
	

	
	class ExtractionThread extends Thread {
		private Vector<Scan<?,?>> collection = new Vector<Scan<?,?>>();
		
		public ExtractionThread() {}
		
		public void addScan(Scan<?,?> scan) { 
			collection.add(scan); 
			queue.addAll(scan);
		}

		@Override
		public void run() {
			for(Scan<?,?> scan : collection) {
				
				SourceModel<InstrumentType, ScanType> scanSource = copy();
				scanSource.reset();
				
				for(Integration<?,?> integration : scan) scanSource.add(integration);
				
				scanSource.process(scan);
				add(scanSource, scan.weight);
				
				for(Integration<?,?> integration : scan) checkout(integration);
			}
		}	
	}
}
