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

package crush.gismo;

import java.io.*;
import java.util.*;

import crush.*;
import crush.array.*;
import util.*;
import nom.tam.fits.*;

public class Gismo extends MonoArray<GismoPixel> implements GroundBased {
	/**
	 * 
	 */
	private static final long serialVersionUID = 3009881856872575936L;

	public static int pixels = 128;
	
	// Array is 16x8 (rows x cols)
	
	private Vector2D arrayPointingCenter = new Vector2D(8.5, 4.5); // row,col
	//Vector2D pointingCenterOffset = new Vector2D(); // The offset of the pointing center rel. to the rotation center...
	//double nativeSamplingInterval;
	
	double rotation;
	
	double focusX, focusY, focusZ;
	double focusYOffset, focusZOffset;
	String focusMode;
	
	
	public Gismo() {
		super("gismo", pixels);
		resolution = 16.7 * Unit.arcsec;
		ensureCapacity(pixels);
		for(int c=0; c<pixels; c++) add(new GismoPixel(this, c));
		
		// TODO calculate this?
		integrationTime = samplingInterval = 0.1 * Unit.sec;
	
		mount = Mount.LEFT_NASMYTH;
	}

	@Override
	public Instrument<GismoPixel> copy() {
		Gismo copy = (Gismo) super.copy();
		copy.arrayPointingCenter = (Vector2D) arrayPointingCenter.clone();
		return copy;
	}
	
	@Override
	public String getTelescopeName() {
		return "IRAM-30M";
	}
	
	@Override
	public GismoPixel getChannelInstance(int backendIndex) {
		return new GismoPixel(this, backendIndex);
	}

	@Override
	public Scan<?, ?> getScanInstance() {
		return new GismoScan(this);
	}

	@Override
	public void addDivisions() {
		super.addDivisions();
		
		try { addDivision(getDivision("mux", GismoPixel.class.getField("mux"), Channel.FLAG_DEAD)); }
		catch(Exception e) { e.printStackTrace(); }
		
		try { addDivision(getDivision("pins", GismoPixel.class.getField("pin"), Channel.FLAG_DEAD)); }
		catch(Exception e) { e.printStackTrace(); }	
		
		try { addDivision(getDivision("rows", GismoPixel.class.getField("row"), Channel.FLAG_DEAD)); }
		catch(Exception e) { e.printStackTrace(); }	
		
		try { addDivision(getDivision("cols", GismoPixel.class.getField("col"), Channel.FLAG_DEAD)); }
		catch(Exception e) { e.printStackTrace(); }	
	}
	
	@Override
	public void addModalities() {
		super.addModalities();
		
		try { addModality(new CorrelatedModality("mux", "m", divisions.get("mux"), GismoPixel.class.getField("muxGain"))); }
		catch(NoSuchFieldException e) { e.printStackTrace(); }
		
		try { addModality(new CorrelatedModality("pins", "p", divisions.get("pins"), GismoPixel.class.getField("pinGain"))); }
		catch(NoSuchFieldException e) { e.printStackTrace(); }
		
		/*
		addModality(new CorrelatedModality("rows", "r", divisions.get("rows")));
		addModality(new CorrelatedModality("cols", "c", divisions.get("cols")));
		*/
		
		modalities.get("mux").setGainFlag(GismoPixel.FLAG_MUX);
		modalities.get("pins").setGainFlag(GismoPixel.FLAG_PIN);
	}
	
	@Override
	public void loadChannelData() {
		// Update the pointing centers...
		if(hasOption("pcenter")) arrayPointingCenter = option("pcenter").getVector2D();
		
		Vector2D pixelSize = GismoPixel.defaultSize;
		
		// Set the pixel size...
		if(hasOption("pixelsize")) {
			pixelSize = new Vector2D();
			StringTokenizer tokens = new StringTokenizer(option("pixelsize").getValue(), " \t,:xX");
			pixelSize.x = Double.parseDouble(tokens.nextToken()) * Unit.arcsec;
			pixelSize.y = tokens.hasMoreTokens() ? Double.parseDouble(tokens.nextToken()) * Unit.arcsec : pixelSize.x;
		}

		setPlateScale(pixelSize);
		
		if(hasOption("rotation")) rotate(option("rotation").getDouble() * Unit.deg);
		
		super.loadChannelData();
	}
	
	public void setPlateScale(Vector2D size) {
		// Make all pixels the same size. Also calculate their positions...
		for(GismoPixel pixel : this) pixel.size = size;
		Vector2D center = GismoPixel.getPosition(size, arrayPointingCenter.x - 1.0, arrayPointingCenter.y - 1.0);			
		
		// Set the pointing center...
		setReferencePosition(center);
	}
	
	public void rotate(double angle) {
		for(GismoPixel pixel : this) pixel.position.rotate(angle);
	}
	
	@Override
	public void readWiring(String fileName) throws IOException {
		System.err.println(" Loading wiring data from " + fileName);
		fileName = Util.getSystemPath(fileName);
		
		BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(fileName)));
		Hashtable<Integer, GismoPixel> lookup = getChannelLookup();
		
		int pinGroup = hasOption("correlated.pins.group") ? option("correlated.pins.group").getInt() : 1;
	
		String line = null;
		while((line = in.readLine()) != null) if(line.length() > 0) if(line.charAt(0) != '#') {
			StringTokenizer tokens = new StringTokenizer(line);
		 	GismoPixel pixel = lookup.get(Integer.parseInt(tokens.nextToken()));
			pixel.mux = Integer.parseInt(tokens.nextToken());
		 	pixel.pin = Integer.parseInt(tokens.nextToken()) % pinGroup;
		}
		
		in.close();
	}

	@Override
	public void setReferencePosition(Vector2D position) {
		for(GismoPixel pixel : this) pixel.calcPosition();
		super.setReferencePosition(position);
	}
	
	
	protected void parseScanPrimaryHDU(BasicHDU hdu) throws HeaderCardException, FitsException {
		Header header = hdu.getHeader();
		
		// Platform
		/*
		String platform = header.getStringValue("PLATFORM");
		if(platform == null) platform = "Cassegrain";
		
		isNasmyth = platform.equalsIgnoreCase("NASMYTH");
		
		System.err.println(" " + (isNasmyth ? "Nasmyth" : "Cassegrain") + " mount assumed.");
		*/
		
		//rotatorAngle = header.getDoubleValue("ROTATOR", 0.0) * Unit.deg;
		//rotatorOffset = header.getDoubleValue("ROTOFFST") * Unit.deg;
		//rotatorMode = header.getStringValue("ROTMODE");
		
		//rotatorZeroAngle = header.getDoubleValue("ROTZERO") * Unit.deg;
		//if(rotatorMode == null) rotatorMode = "Unknown";
		
		// Focus
		focusX =  header.getDoubleValue("FOCUS_X");
		focusY =  header.getDoubleValue("FOCUS_Y");
		focusZ =  header.getDoubleValue("FOCUS_Z");

		focusYOffset =  header.getDoubleValue("FOCUS_YO");
		focusZOffset =  header.getDoubleValue("FOCUS_ZO");

		focusMode = header.getStringValue("FOCMODE");
		if(focusMode == null) focusMode = "Unknown";
		
		/*
		System.err.println(" Focus [" + focusMode + "]"
				+ " X=" + Util.f2.format(focusX)
				+ " Y=" + Util.f2.format(focusY)
				+ " Z=" + Util.f2.format(focusZ)
				+ " YOff=" + Util.f2.format(focusYOffset) 
				+ " ZOff=" + Util.f2.format(focusZOffset)
		);
		*/	
		
		System.err.println(" Focus: dZ = " + Util.f2.format(focusZOffset) + " mm.");
	}
	
	protected void parseHardwareHDU(BinaryTableHDU hdu) throws HeaderCardException, FitsException {
		Object[] row = hdu.getRow(0);
		
		int iMask = hdu.findColumn("PIXMASK");
		
		if(iMask < 0) return;
		
		try {
			short[] mask = (short[]) row[iMask];
			for(Channel channel : this) if(mask[channel.dataIndex-1] == 0) channel.flag(Channel.FLAG_DEAD); 
		}
		catch(ClassCastException e) {
			byte[] mask = (byte[]) row[iMask];
			for(Channel channel : this) if(mask[channel.dataIndex-1] == 0) channel.flag(Channel.FLAG_DEAD);
		}
	
	}


	@Override
	public int maxPixels() {
		return storeChannels;
	}    
	
}
