code: /*******************************************************************************
   * 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.instrument.sharc2;
  
  import jnum.Constant;
  import jnum.Unit;
  import jnum.Util;
  import jnum.astro.AstroTime;
  import jnum.astro.CoordinateEpoch;
  import jnum.astro.EquatorialCoordinates;
  import jnum.astro.HorizontalCoordinates;
  import jnum.math.Vector2D;
  import nom.tam.fits.*;
  
  import java.io.*;
  import java.util.*;
  
  import crush.telescope.cso.CSOScan;
  
  import java.text.*;
  
  
  class Sharc2Scan extends CSOScan<Sharc2Integration> {
  	/**
  	 * 
  	 */
  	private static final long serialVersionUID = -2008390740743369604L;
  
  	public long fileSize = -1;
  
  	private double raUnit = Unit.hourAngle;
  	private double decUnit = Unit.deg;
  	
  	Sharc2Scan(Sharc2 instrument) {
  		super(instrument);
  	}
  
  	@Override
      public Sharc2 getInstrument() { return (Sharc2) super.getInstrument(); }
  	
  	@Override
  	public Sharc2Integration getIntegrationInstance() {
  		return new Sharc2Integration(this);
  	}
  	
  	@Override
  	public void calcHorizontal() {
  		super.calcHorizontal();
  			
  		// When the telescope is not tracking, the equatorial coordinates may be bogus...
  		// Use the horizontal coordinates to make sure the equatorial ones make sense...
  		EquatorialCoordinates eq2 = horizontal.toEquatorial(site, LST);		
  		eq2.epoch = apparent.epoch;		
  		eq2.precess(equatorial.epoch);
  		
  		if(eq2.distanceTo(equatorial) > 5.0 * Unit.deg) {
  			info(">>> Fix: invalid (stale) equatorial coordinates.");
  			equatorial = eq2;
  			apparent = horizontal.toEquatorial(site, LST);
  		}	
  	}
  	
  	
  	protected void read(Fits fits, boolean readFully) throws Exception {
  		// Read in entire FITS file		    
  		BasicHDU<?>[] HDU = fits.read();
  		
  		int i = 4; 
  		BasicHDU<?> firstDataHDU = null;
  		while( !(firstDataHDU = HDU[i]).getHeader().getStringValue("EXTNAME").equalsIgnoreCase("SHARC2 Data") ) i++;
  		
  		checkPrematureFits(HDU[0], (BinaryTableHDU) firstDataHDU);
  		
  		parseScanPrimaryHDU(HDU[0]);
  		clear();
  		
  		Sharc2 sharc2 = getInstrument();
  		// Load the instrument settings...
  		sharc2.parseScanPrimaryHDU(HDU[0]);
  		sharc2.parseHardwareHDU((BinaryTableHDU) HDU[1]);
  		sharc2.parseDSPHDU((BinaryTableHDU) HDU[2]);
  		sharc2.parsePixelHDU((BinaryTableHDU) HDU[3]);
  		sharc2.parseDataHeader(firstDataHDU.getHeader());
  		sharc2.configure();
  		sharc2.validate();
  		
  		Sharc2Integration integration = new Sharc2Integration(this);
  		integration.read(HDU, i);
  		add(integration);
  		
  		try { fits.getStream().close(); }
  		catch(IOException e) {}
  		
  		horizontal = null;
  	}
  	
  	
  	private File getFile(String scanDescriptor) throws FileNotFoundException {
  		File scanFile;
  
  		String path = getDataPath();
  		descriptor = scanDescriptor;
  		
  		try { 
  			int scanNo = Integer.parseInt(scanDescriptor);
  			String fileName = path + "sharc2-" + Util.d6.format(scanNo) + ".fits";
  			scanFile = new File(fileName);
  			
  			// Try with various compressed formats...
  			if(!scanFile.exists()) scanFile = new File(fileName + ".gz");
  			if(!scanFile.exists()) scanFile = new File(fileName + ".xz");
  			if(!scanFile.exists()) scanFile = new File(fileName + ".Z");
  			if(!scanFile.exists()) scanFile = new File(fileName + ".bz2");
  			if(!scanFile.exists()) scanFile = new File(fileName + ".zip");
  			
  			if(!scanFile.exists()) throw new FileNotFoundException("Could Not find scan " + scanDescriptor); 
  		} 
  		catch(NumberFormatException e) {
  			scanFile = new File(scanDescriptor) ;	
  			if(!scanFile.exists()) {
  				scanFile = new File(path + scanDescriptor);
  				if(!scanFile.exists()) throw new FileNotFoundException("Could Not find scan " + scanDescriptor); 
  			} 
  		}	
  
  		fileSize = scanFile.length();
  
  		return scanFile;
  	}
  	
  	@Override
  	public void read(String scanDescriptor, boolean readFully) throws Exception {
  		read(getFits(scanDescriptor), readFully);
  	}
  
  	
  	private Fits getFits(String scanDescriptor) throws FileNotFoundException, FitsException {
  		File file = getFile(scanDescriptor);
  		info("Reading " + file.getPath() + "...");
  		return new Fits(getFile(scanDescriptor));
  	}
  	
  	protected void checkPrematureFits(BasicHDU<?> main, BinaryTableHDU data) throws FitsException {
  		equatorial = null;
  		Header header = main.getHeader();
  		if(header.containsKey("RAEP") && header.containsKey("DECEP")) return;
  		
  		info(">>> Fix: Assuming early JSharc data format.");
  		info(">>> Fix: Source coordinates obtained from antenna stream.");
  		
  		getInstrument().earlyFITS = true;
  		header = data.getHeader();
  		float RA = ((float[]) data.getElement(0, data.findColumn("RA")))[0];
  		float DEC = ((float[]) data.getElement(0, data.findColumn("DEC")))[0];
  		
  		equatorial = new EquatorialCoordinates(RA * raUnit, DEC * decUnit, CoordinateEpoch.fromHeader(header));
  	}
  	
  	@Override
  	public void setSerial(int number) {
  		// Fix for early Nov 2002 scans, where RA/DEC are in incorrect units.
  		if(number < 7320) {
  			info(">>> Fix: RA/DEC column in unexpected units.");
  			raUnit = 1.0;
  			decUnit = 1.0;
  		}
  		else {
  			raUnit = Unit.hourAngle;
  			decUnit = Unit.deg;
  		}
  	
  		super.setSerial(number);
  	}
  
  	@Override
  	public void setSourceName(String name) {
  		// For early SHARC-2 data, the source name was sometimes lagging, and therefore not always correct. 
  		// (JSharc got the source name from the UIP at the beginning of the scan, rather than at the end)
  		// This happened only before scan 7320. For these early scans, we can recover the correct source name
  		// based on a correction list definition...
  		if(getSerial() < 7320) name = fixSourceName(name);
  		super.setSourceName(name);		
  	}
  	
  	private String fixSourceName(String name) {
  		int serial = getSerial();
  		String fileName = getInstrument().getConfigPath() + "sourcename-2002.fix";
  		try (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);
  				if(Integer.parseInt(tokens.nextToken()) == serial) {
  					name = tokens.nextToken();
  					info("Fix: source name changed to " + name);
  					in.close();
  					return name;
  				}
  			}
  			in.close();
  		}
  		catch(IOException e) { info(">>> Fix: WARNING! Cannot find name fix list specification file. No fix."); }
  		return name;
  	}
  	
  	protected void parseScanPrimaryHDU(BasicHDU<?> hdu) throws HeaderCardException {
  		Header header = hdu.getHeader();
  
  		// Load any options based on the FITS header...
  		getInstrument().setFitsHeaderOptions(header);
  		
  		// Scan Info
  		setSerial(header.getIntValue("SCANNO"));
  		
  		//site = new GeodesicCoordinates(header.getDoubleValue("TELLONGI") * Unit.deg, header.getDoubleValue("TELLATID") * Unit.deg);
  		//info("Location: " + site);
  		
  		creator = header.getStringValue("CREATOR");
  		observer = header.getStringValue("OBSERVER");
  		project = header.getStringValue("PROJECT");
  
  		if(creator == null) creator = "Unknown";
  		if(observer == null) observer = "Unknown";
  		if(project == null) project = "Unknown";
  
  		// Weather
  		if(hasOption("tau.225ghz")) tau225GHz = option("tau.225ghz").getDouble();
  		else {
  			tau225GHz = header.getDoubleValue("TAU225GH");
  			getInstrument().setOption("tau.225ghz=" + tau225GHz);
  		}
  
  		ambientT = header.getDoubleValue("TEMPERAT") * Unit.K + Constant.zeroCelsius;
  		pressure = header.getDoubleValue("PRESSURE") * Unit.mbar;
  		humidity = header.getDoubleValue("HUMIDITY");
  
  		// Source Information
  		String sourceName = header.getStringValue("OBJECT");
  		if(sourceName == null) sourceName = "Unknown";
  		setSourceName(sourceName);
  		sourceName = getSourceName(); // Confirm the source name setting...
  		
  		timeStamp = header.getStringValue("DATE-OBS");
  		
  		// increment month by 1 to correct JSharc FITS bug
  		if(creator.equalsIgnoreCase("JSharc")) {
  			info(">>> Fix: FITS libraries BUG in JSharc timestamp.");
  			String modDate = timeStamp.substring(0, 5);
  			modDate += Util.d2.format(Integer.parseInt(timeStamp.substring(5,7)) + 1 );
  			modDate += timeStamp.substring(7);
  			timeStamp = modDate;
  			//CRUSH.detail(this, modDate);
  		}
  		double epoch = header.getDoubleValue("EQUINOX");
  		
  		if(equatorial == null) equatorial = new EquatorialCoordinates(
  				header.getDoubleValue("RAEP") * raUnit,
  				header.getDoubleValue("DECEP") * decUnit,
  				(epoch < 1984.0 ? "B" : "J") + epoch);
  	
  		
  		if(header.containsKey("SCANCOORD")) scanSystem = getScanSystem(header.getIntValue("SCANCOORD", SCAN_UNDEFINED));
  		if(scanSystem == null) scanSystem = HorizontalCoordinates.class;
  		
  		// Print out some of the information...
  		StringTokenizer tokens = new StringTokenizer(timeStamp, ":T");
  		String dateString = tokens.nextToken();
  		String timeString = tokens.nextToken() + ":" + tokens.nextToken() + " UT";
  		
  		info("[" + sourceName + "] observed on " + dateString + " at " + timeString + " by " + observer);
  		if(equatorial != null) info("Equatorial: " + equatorial);	
  		
  		try { setMJD(AstroTime.forFitsTimeStamp(timeStamp).getMJD()); }
  		catch(ParseException e) { error(e); }
  		
  		iMJD = (int) Math.floor(getMJD());
  		
  		// iMJD does not exist in earlier scans
  		// convert DATE-OBS into MJD...
  		//if(header.containsKey("JUL_DAY")) iMJD = header.getIntValue("JUL_DAY");
  			
  		addStaticOffsets = hasOption("offsets.add");
  		
  		// Add on the various additional offsets
  		horizontalOffset = new Vector2D(
  				header.getDoubleValue("AZO") * Unit.arcsec,
  				-header.getDoubleValue("ZAO") * Unit.arcsec);
  		horizontalOffset.addX(header.getDoubleValue("AZO_MAP") * Unit.arcsec);
  		horizontalOffset.subtractY(header.getDoubleValue("ZAO_MAP") * Unit.arcsec);
  		horizontalOffset.addX(header.getDoubleValue("AZO_CHOP") * Unit.arcsec);		
  		horizontalOffset.subtractY(header.getDoubleValue("ZAO_CHOP") * Unit.arcsec);
  		horizontalOffset.addX(header.getDoubleValue("CHPOFFST") * Unit.arcsec);
  		
  		Vector2D eqOffset = new Vector2D( 
  				header.getDoubleValue("RAO") * Unit.arcsec,
  				header.getDoubleValue("DECO") * Unit.arcsec);		
  		eqOffset.addX(header.getDoubleValue("RAO_MAP") * Unit.arcsec);
  		eqOffset.addY(header.getDoubleValue("DECO_MAP") * Unit.arcsec);
  		eqOffset.addX(header.getDoubleValue("RAO_FLD") * Unit.arcsec);
  		eqOffset.addY(header.getDoubleValue("DECO_FLD") * Unit.arcsec);
  
  		fixedOffset = new Vector2D(header.getDoubleValue("FAZO") * Unit.arcsec, -header.getDoubleValue("FZAO") * Unit.arcsec);	
  		
  		DecimalFormat f3_1 = new DecimalFormat(" 0.0;-0.0");
  
  		info("  AZO =" + f3_1.format(horizontalOffset.x()/Unit.arcsec)
  				+ "\tELO =" + f3_1.format(horizontalOffset.y()/Unit.arcsec)
  				+ "\tRAO =" + f3_1.format(eqOffset.x()/Unit.arcsec)
  				+ "\tDECO=" + f3_1.format(eqOffset.y()/Unit.arcsec)
  				
  		);
  		
  		info("  FAZO=" + f3_1.format(fixedOffset.x()/Unit.arcsec)
  				+ "\tFZAO=" + f3_1.format(-fixedOffset.y()/Unit.arcsec)
  		);
  		
  		equatorial.addOffset(eqOffset);
  	
  		// Add pointing corrections...
  		if(hasOption("fazo")) {
  			double fazo = option("fazo").getDouble() * Unit.arcsec;
  			horizontalOffset.addX(fixedOffset.x() - fazo);
  			fixedOffset.setX(fazo);
  		}
  		if(hasOption("fzao")) {
  			double felo = -option("fzao").getDouble() * Unit.arcsec;
  			horizontalOffset.addY(fixedOffset.y() - felo);
  			fixedOffset.setY(felo);
  		}		
  	}
  
  }
  