// (C)2007 Attila Kovacs <attila@submm.caltech.edu>

import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import java.text.*;

public class LabocaPixel implements Cloneable {
	Vector2D position;
	
	int index;
	int backendIndex;
	int flag = 0;
	
	double sourceGain = 1.0;
	double gain = 1.0;
	double boxGain = 1.0;
	double cableGain = 1.0;
	double ampGain = 1.0;
	double waferGain = 1.0;
	double regionGain = 1.0;
	double mainBeamEfficiency = 1.0;
	double temperatureGain = 0.0;

	double noiseWhitening = 1.0;
	double sourceWhitening = 1.0; // The whitening on source...
	double sourceFiltering = 1.0; // filtering on source...
	float[] filterResponse; // The whitening filter's spectral response...
	
	double weight = 1.0;
	double sourceWeight = 1.0;
	double dof = Double.NaN;
	//double lastDependents = 0.0, temp;
	double lastDependents = 0.0, dependents = 0.0, temp;
	
	int spikes = 0;
		
	public int box = -1;
	public int cable = -1;
	public int pin = -1;
	public int amp = -1;
	public int wafer = -1;
	public int region = -1;
	
	public Vector<LabocaPixel> neighbours;
		
	static int[] waferForChannel, regionForChannel, boxForChannel, cableForChannel, ampForChannel, pinForChannel;
	static int[] flagForChannel;
	
	public LabocaPixel(int backendChannel) {
		index = backendChannel;
		backendIndex = index;
	}
	
	public Object clone() {
		try { return super.clone(); }
		catch(CloneNotSupportedException e) { return null; }		
	}
	
	public final boolean isFlagged(int pattern) {
		return (flag & pattern) != 0;
	}
	
	public final boolean isUnflagged(int pattern) {
		return (flag & pattern) == 0;
	}
	
	public static void fillWiringInfo(LabocaPixel[] channel) {		
		for(int c=0; c<channel.length; c++) if(channel[c] != null) {
			if(waferForChannel != null) if(c < waferForChannel.length) channel[c].wafer = waferForChannel[c];
			if(regionForChannel != null) if(c < regionForChannel.length) channel[c].region = regionForChannel[c];
			if(boxForChannel != null) if(c < boxForChannel.length) channel[c].box = boxForChannel[c];
			if(cableForChannel != null) if(c < cableForChannel.length) channel[c].cable = cableForChannel[c];
			if(ampForChannel != null) if(c < ampForChannel.length) channel[c].amp = ampForChannel[c];
			if(pinForChannel != null) if(c < pinForChannel.length) channel[c].pin = pinForChannel[c];
			if(flagForChannel != null) if(c < flagForChannel.length) channel[c].flag |= flagForChannel[c];	
		}		
	}
	
	public static void readWiring(String fileName) throws IOException {	
		waferForChannel = new int[320];
		regionForChannel = new int[320];
		boxForChannel = new int[320];
		cableForChannel = new int[320];
		ampForChannel = new int[320];
		flagForChannel = new int[320];
		pinForChannel = new int[320];
		
		BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(fileName)));
		
		String line = null;
		while((line = in.readLine()) != null) if(line.length() > 0) if(line.charAt(0) != '#') {
			StringTokenizer tokens = new StringTokenizer(line);
		 	int c = Integer.parseInt(tokens.nextToken()) - 1;
			boxForChannel[c] = Integer.parseInt(tokens.nextToken()) - 1;
			cableForChannel[c] = Integer.parseInt(tokens.nextToken()) - 1;
			tokens.nextToken(); // amp line
			ampForChannel[c] = 16 * boxForChannel[c] + tokens.nextToken().charAt(0) - 'A';
			tokens.nextToken(); // cable name
			pinForChannel[c] = Integer.parseInt(tokens.nextToken()) - 1; // cable pin
			
			int bol = Integer.parseInt(tokens.nextToken()) - 1;
			char state = tokens.nextToken().charAt(0);
			boolean hasComment = tokens.hasMoreTokens();
			
			if(bol < 0 || state != 'B' || hasComment) flagForChannel[c] |= LabocaPixel.FLAG_DEAD;
		}	
	}

	public static void validatePositions(LabocaPixel[] channel, int referenceChannel) {
		Vector2D refPos = (Vector2D) channel[referenceChannel].position.clone();
		for(int c=0; c<channel.length; c++) {
			channel[c].position.subtract(refPos);
			if(channel[c].position.length() > 900.0*Unit.arcsec) channel[c].flag |= FLAG_BLIND;
		}
	}
	

	public static void normalizeGains(LabocaPixel[] channel, int method) {	
		double aveG = 0.0, sumw = 0.0;
		for(int c=0; c<channel.length; c++) if(channel[c].isUnflagged(~FLAG_BLIND)) { 
			double G = channel[c].gain;
			if(method == GAINS_BIDIRECTIONAL) G = Math.abs(G);
			aveG += channel[c].weight * G; 
			sumw += channel[c].weight;
		}	
		if(sumw > 0) aveG /= sumw;
		for(int c=0; c<channel.length; c++) channel[c].gain  /= aveG;
	}
	
	/*
	public static void flagGains(LabocaPixel[] channel, int method, double min, double max) {
		for(int c=0; c<channel.length; c++) {
			double G = channel[c].gain;
			if(method == GAINS_BIDIRECTIONAL) G = Math.abs(G);
			
			if(G < min) channel[c].flag |= LabocaPixel.FLAG_DEAD;
			else if(G > max) channel[c].flag |= LabocaPixel.FLAG_GAIN;
			else channel[c].flag &= ~(LabocaPixel.FLAG_DEAD | LabocaPixel.FLAG_GAIN);
		}
	}
	*/
	
	public final static int GAINS_POSITIVE = 0;
	public final static int GAINS_BIDIRECTIONAL = 1;
	
	
	// create the channel groups based on the wiring scheme.
	public static void markBlinds(LabocaPixel[] channel) {		
		for(int c=0; c<channel.length; c++) {
			if(channel[c].position.length() > 10.0 * Unit.deg) channel[c].flag |= FLAG_BLIND;
		}
	}
	
	public static void kill(LabocaPixel[] channel, int flagPattern) {
		for(int c=0; c<channel.length; c++) if(channel[c].isFlagged(flagPattern)) channel[c].flag |= FLAG_DEAD;
	}

	public static int[] getAllChannels(LabocaPixel[] channel, int discardFlags) {
		// All good channels
		int bads = 0;
		for(int c=0; c<channel.length; c++) if(channel[c].isFlagged(discardFlags)) bads++;	

		int[] allChannels = new int[channel.length - bads];
		for(int c=0,k=0; c<channel.length; c++) if(channel[c].isUnflagged(discardFlags)) allChannels[k++] = c;	
		
		return allChannels;
	}
	
	
	public static int[][] getChannelGroups(LabocaPixel[] channel, Field field, int discardFlags) throws IllegalAccessException {
		int groups = 0;
		
		for(int c=0; c<channel.length; c++) if(channel[c].isUnflagged(discardFlags)) {
			int group = field.getInt(channel[c]);
			if(group >= groups) groups = group + 1;
		}
			
		int[][] selection = new int[groups][];
		
		for(int g=0; g<groups; g++) {
			int channelsInGroup = 0;
			for(int c=0; c<channel.length; c++) if(channel[c].isUnflagged(discardFlags)) {
				int group = field.getInt(channel[c]);
				if(group == g) channelsInGroup++;
				selection[g] = new int[channelsInGroup];
			}
		}
		
		int[] lastOf = new int[groups];
		for(int c=0; c<channel.length; c++) if(channel[c].isUnflagged(discardFlags)) {
			int group = field.getInt(channel[c]);
			if(group >= 0) selection[group][lastOf[group]++] = c;
		}
		
		// For debugging purposes, write out the grouping data...
		/*
		try {
			PrintWriter out = new PrintWriter(new BufferedOutputStream(new FileOutputStream(field.getName() + ".grp")));
			for(int g=0; g<selection.length; g++) {
				out.print((g+1) + ":\t");
				for(int k=0; k<selection[g].length; k++) out.print((channel[selection[g][k]].backendIndex + 1) + " ");
				//for(int k=0; k<selection[g].length; k++) out.print((channel[selection[g][k]].backendIndex + 1) + "->" + (channel[selection[g][k]].index + 1) + " ");
				out.println();
			}
			out.flush();
			out.close();
		} catch(IOException e) {}
		*/

		return selection;
	}
	
	public static void findNeighbours(LabocaPixel[] channel, double mind, double maxd) {
		for(int c=0; c<channel.length; c++) channel[c].neighbours = new Vector<LabocaPixel>();
		
		for(int c1=0; c1<channel.length; c1++) if(channel[c1].isUnflagged(FLAG_DEAD)) 
			for(int c2=c1+1; c2<channel.length; c2++) if(channel[c2].isUnflagged(FLAG_DEAD)) {
				double d = Vector2D.difference(channel[c1].position, channel[c2].position).length();
				if(d >= mind && d <= maxd) {
					channel[c1].neighbours.add(channel[c2]);
					channel[c2].neighbours.add(channel[c1]);
				}
			}
	}
				

	public static void readPixelRCP(LabocaPixel[] channel, String fileName, LabocaPixel reference)  throws IOException {
		int channels = channel.length;
		BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(fileName)));
		String line;

		// flag all channels by assigning some unreal position
		for(int c=0; c<channels; c++) {
			channel[c].flag |= LabocaPixel.FLAG_BLIND;
			channel[c].position = new Vector2D(-1000.0*Unit.deg, -1000.0*Unit.deg);
		}

		while((line = in.readLine()) != null) if(line.length() > 0) if(line.charAt(0) != '!') {
			try {
				StringTokenizer tokens = new StringTokenizer(line);
				int c = Integer.parseInt(tokens.nextToken())-1;
				channel[c].flag &= ~FLAG_BLIND;
				channel[c].sourceGain = Double.parseDouble(tokens.nextToken());
				tokens.nextToken();
				channel[c].position.x = Double.parseDouble(tokens.nextToken()) * Unit.arcsec;
				channel[c].position.y = Double.parseDouble(tokens.nextToken()) * Unit.arcsec;
				//channel[c].mainBeamEfficiency /= channel[c].gain;
			}
			catch(NumberFormatException e){}
		}
		
		setReference(channel, reference);
		markBlinds(channel);
	}
	

	public static void generateRCPFrom(String rcpFileName, String pixelFileName) throws IOException {
		LabocaPixel[] channel = new LabocaPixel[320];
		for(int c=0; c<channel.length; c++) channel[c] = new LabocaPixel(c);
		readPixelRCP(channel, rcpFileName, channel[317]);
		loadPixelData(channel, pixelFileName);
		printPixelRCP(channel);
	}
	
	public static void printPixelRCP(LabocaPixel[] channel)  throws IOException {
		for(int c=0; c<channel.length; c++) {
			System.out.print((channel[c].backendIndex + 1) + "  ");
			System.out.print(Util.f3.format(channel[c].gain * channel[c].sourceGain) + "  ");
			System.out.print(Util.f3.format(channel[c].gain * channel[c].sourceGain) + "  ");
			System.out.print(Util.f2.format(channel[c].position.x / Unit.arcsec) + "  ");
			System.out.print(Util.f2.format(channel[c].position.y / Unit.arcsec) + "  ");
			System.out.println();
		}
	}
	
	public String gainString(DecimalFormat df) {
		String text = 
			df.format(gain) + " \t" +
			df.format(cableGain);
		return text;
	}
	
	public void parseGains(StringTokenizer tokens) {
		gain = Double.parseDouble(tokens.nextToken());
		cableGain = Double.parseDouble(tokens.nextToken());
	}
	
	public String gainHeader() {
		return "gain\t\tGcable";
	}
	
	public static void writePixelData(LabocaPixel[] channel, String filename) throws IOException {
		PrintWriter out = new PrintWriter(new FileOutputStream(filename));

		out.println("# BEch\t" + channel[0].gainHeader() + "\t\tweight\t\tflag\tbox\tcable\tamp\twafer"); 

		for(int c=0; c<channel.length; c++) if(channel[c].isUnflagged(FLAG_DEAD)) {
			out.println((channel[c].backendIndex+1) + "\t" +
					channel[c].gainString(Util.e3) + " \t" +
					Util.e3.format(channel[c].weight) + " \t" +
					"0x" + Integer.toHexString(channel[c].flag) + "\t" +
					(channel[c].box+1) + "\t" +
					(channel[c].cable+1) + "\t" +
					(channel[c].amp+1) + "\t" +
					(channel[c].wafer+1));
		}

		out.close();
		
		System.err.println(" Written " + filename);
	}


	public static void loadPixelData(LabocaPixel[] channel, String fileName) throws IOException {
		BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(fileName)));
		String line;
	
		//final int criticalFlags = FLAG_DEAD | FLAG_BLIND | FLAG_GAIN;
		final int criticalFlags = ~0;
		
		while((line=in.readLine()) != null) if(line.length() > 0) if(line.charAt(0) != '#') {
					
			try {
				StringTokenizer tokens = new StringTokenizer(line);
				int backendChannel = Integer.parseInt(tokens.nextToken())-1;
				int ch = -1;
				for(int c=0; c<channel.length; c++) if(channel[c].backendIndex == backendChannel) {
					ch = c;
					break;
				}
				channel[ch].parseGains(tokens);
								
				// If using RCP gains, use 6.1 Jy/uV conversion...
				//channel[ch].mainBeamEfficiency /= channel[ch].gain;
				channel[ch].weight = Double.parseDouble(tokens.nextToken());
				// Add flags from pixel data file on top of those already specified...
				channel[ch].flag |= criticalFlags & Integer.decode(tokens.nextToken());	
				
				/*
				channel[ch].box = Integer.parseInt(tokens.nextToken()) - 1;
				channel[ch].cable = Integer.parseInt(tokens.nextToken()) - 1;
				channel[ch].amp = Integer.parseInt(tokens.nextToken()) - 1;
				channel[ch].wafer = Integer.parseInt(tokens.nextToken()) - 1;
				*/
			}
			catch(NumberFormatException e){ e.printStackTrace(); }
		}

	}
	
	public static void readTemperatureGains(String fileName, LabocaPixel[]  channel) throws IOException {
		// Read gains into LabocaPixel -> temperatureGain:
		BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(fileName)));
		String line;
		while((line = in.readLine()) != null) if(line.length() > 0) if(line.charAt(0) != '#') {
			StringTokenizer tokens = new StringTokenizer(line);
			int c = Integer.parseInt(tokens.nextToken()) - 1;
			channel[c].temperatureGain = Double.parseDouble(tokens.nextToken());
		}
		
		System.err.println("   Loaded He3 gains from " + fileName);
	}
	
	
	public static void writeTemperatureGains(String fileName, LabocaPixel[] channel) throws IOException {
		System.err.println("   Writing He3 gains to " + fileName);
		
		PrintWriter out = new PrintWriter(new BufferedOutputStream(new FileOutputStream(fileName)));
		out.println("# He3 Temperature Gains Data.");
		out.println("# Measured by MiniCrush ver " + MiniCRUSH.version);
		out.println("# ");
		out.println("# BEch\tGain");
		out.println("#     \t(V/K)");
		out.println("# ----\t-----");
		for(int c=0; c<channel.length; c++) out.println((channel[c].backendIndex + 1) + "\t" + Util.e6.format(channel[c].temperatureGain));
		
		out.flush();
		out.close();
		System.err.println(" Written gain data to " + fileName + ".");
		
	}
	
	public static void setReference(LabocaPixel[] channel, LabocaPixel reference) {
		Vector2D refpos = (Vector2D) reference.position.clone();
		for(int c=0; c<channel.length; c++) channel[c].position.subtract(refpos);
	}

	public final static int FLAG_DEAD = 1<<0;
	public final static int FLAG_DOF = 1<<1;
	public final static int FLAG_GAIN = 1<<2;
	public final static int FLAG_SENSITIVITY = 1<<3;
	public final static int FLAG_SPIKY = 1<<4;
	public final static int FLAG_CABLE = 1<<5;
	public final static int FLAG_BOX = 1<<6;
	public final static int FLAG_AMP = 1<<7;
	public final static int FLAG_WAFER = 1<<8;
	public final static int FLAG_REGION = 1<<9;
	public final static int FLAG_XACCEL = 1<<10;
	public final static int FLAG_YACCEL = 1<<11;
	public final static int FLAG_DISCONTINUITY = 1<<12;
	public final static int FLAG_FEATURES = 1<<13;
	public final static int CHOPPED_LEFT_BEAM= 1<<14;
	public final static int CHOPPED_RIGHT_BEAM= 1<<15;
	public final static int CHOPPED_BEAM = CHOPPED_LEFT_BEAM | CHOPPED_RIGHT_BEAM;
	
	public final static int FLAG_BLIND = 1<<16;
	


}