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

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

import java.text.*;

public class Crush {
    static String version = "1.44";
    
    static String[] options;
    
    static String workPath = "./";
    static String reducedName;
    static String tauServer = "fangorn.submm.caltech.edu";
    static String logName = null;
    static double reducedEpoch = 2000.0;
    static boolean point = false;
    static boolean models = true;
    static boolean printInfo = false;
    static boolean writeScanMaps = false;
    static int reserveMemory = 25 * 1024 * 1024; // 25 MB;
    
    static String defaultConfig = "crush.cfg";
    static String currentConfig;
    
    static ReductionTool cleaner = new ReductionTool();
    static SharcScan scanTemplate = new SharcScan();
    
    static String spectrumDirective = "off"; 
    static int skySpectrumT = -1;

    static {
	parseConfig(defaultConfig);
    }
    
    public static void main(String[] args) {
	versionInfo();
	
	ReducedData reduced;
	options = args;
	
	if(args.length == 0) quickHelp();
	
	// the list of scans to process
	for(int i=0,k=0; i<args.length; i++) {
	    if(isOption(args[i])) option(args[i]);
	    else {
		StringTokenizer tokens = new StringTokenizer(args[i],"-");
		String[] scan = new String[] { args[i] };
		
		// If file specifier appears to be a range of scans, parse accordingly...
		if(tokens.countTokens() == 2) {
		    try {
			int firstScanNo = Integer.parseInt(tokens.nextToken());
			int lastScanNo = Integer.parseInt(tokens.nextToken());
			if(lastScanNo < firstScanNo) throw new IllegalStateException("Invalid Scan Range.");
			scan = new String[lastScanNo - firstScanNo + 1];
			for(int scanNo = firstScanNo, m=0; scanNo <= lastScanNo; scanNo++, m++) 
			    scan[m] = scanNo + "";
		    }
		    catch(NumberFormatException e) {}
		    catch(IllegalStateException e) {}
		}
		
		for(int m=0; m<scan.length; m++) {
		    try { 
			if(printInfo || logName != null) {
			    SharcScan currentScan = getScanInfo(scan[m]);
			    if(printInfo) currentScan.printInfo();
			    if(logName != null) currentScan.logEntry(Crush.workPath + logName);
			}
			else {
			    System.out.print(" Scan " + (cleaner.scanBuffer.size()+1) + ": ");
			    cleaner.add(getScan(scan[m]));
			    System.out.println();
			}
		    }
		    catch(IllegalStateException e) {
			System.err.println(" ERROR! " + e.getMessage());
		    }
		    catch(Exception e) {
			System.err.println(" ERROR! " + e.getMessage());
			e.printStackTrace();
			System.err.println(" ERROR! Cannot read scan " + scan[m] + ". Skipping...");
		    }
		}
	    }
	}
	
	if(printInfo || logName != null) System.exit(0);
	
	try { 
	    cleaner.init();
	    
	    if(cleaner.rounds >= 0) {
		reduced = cleaner.reduce();
		if(reducedName != null) reduced.fileName = reducedName;
		
		cleaner.write();

		if(writeScanMaps) cleaner.writeScanMaps();

		if(!spectrumDirective.equalsIgnoreCase("off")) {
		    StringTokenizer tokens = new StringTokenizer(spectrumDirective, ":,");
		    String modelName = tokens.nextToken();
		    int i = tokens.countTokens() > 2 ? Integer.parseInt(tokens.nextToken()) : 0;
		    int windowSize = tokens.hasMoreTokens() ? Integer.parseInt(tokens.nextToken()) : 1024;
		    cleaner.writeSpectra(modelName, i, windowSize);
		}
			
		if(point) {
		    reduced.show();
		    reduced.point();
		    reduced.displayUnit = reduced.janskyPerBeam;
		    reduced.display.repaint();
		}	
		
		if(skySpectrumT > 1) cleaner.writeSkySpectrum(skySpectrumT);
    
	    }
	} 
	catch(Exception e) { e.printStackTrace(); }
    }

    public static void parseConfig(String fileName) {
	File file = new File(fileName);
	if(!file.exists()) {
	    System.out.println("ERROR! Could not find configuration file " + fileName + ". Exiting.");
	    System.exit(1);
	}

	currentConfig = fileName;
	try {
	    RandomAccessFile cfgFile = new RandomAccessFile(file, "r");
	    
	    while(cfgFile.getFilePointer() < cfgFile.length()) {
	   
		StringTokenizer tokens = new StringTokenizer(cfgFile.readLine(), " \n\t=");
		
		if(tokens.countTokens() > 1) {
		    String key = tokens.nextToken();
		    String value = tokens.hasMoreTokens() ? tokens.nextToken() : null;
    
		    // Skip comment lines marked either by # or !
		    if(key.charAt(0) == '#' || key.charAt(0) == '!');		    
		    else if(key.equalsIgnoreCase("REDUCED_MAP_PATH")) workPath = value + "/";
		    else if(key.equalsIgnoreCase("REDUCED_MAP_EPOCH")) reducedEpoch = Double.parseDouble(value);
		    else if(key.equalsIgnoreCase("MAI_TAU")) tauServer = value;
		    else if(key.equalsIgnoreCase("LOAD_CONFIG")) parseConfig(value);
		    else if(key.equalsIgnoreCase("WRITE_SCAN_MAPS")) writeScanMaps = Util.parseBoolean(value); 
		    else if(scanTemplate.key(key, value));
		    else if(cleaner.key(key, value));		    
		    else System.out.println("WARNING! Unrecognized Key: " + key);
		}
	    }
	}
	catch(IOException e) { System.out.println("WARNING! Could not parse configuration file " + fileName + "."); }
    }
    
    public static void versionInfo() {
	System.out.println();
	System.out.println("crush -- Comprehensive Reduction Utility for SHARC2");
	System.out.println("         Author: Attila Kovacs <attila@submm.caltech.edu>");
	System.out.println("         Info: http://www.submm.caltech.edu/~sharc/crush");
	System.out.println("         Version: " + version); 
	System.out.println();
    }

    public static SharcScan getScanInfo(String scanDescriptor) throws IOException, HeaderCardException, FitsException {
	SharcScan scan = scanTemplate.duplicate();
	scan.readScanInfo(scanDescriptor);
	return scan;
    }

    public static SharcScan getScan(String scanDescriptor) throws HeaderCardException, FitsException {
	SharcScan scan = scanTemplate.duplicate();
	scan.read(scanDescriptor);
	return scan;
    }

    public static void option(String optionString) {
	StringTokenizer tokens = new StringTokenizer(optionString, "=");

	String key = tokens.nextToken();
	String value = tokens.hasMoreTokens() ? tokens.nextToken() : null;

	if(key.equalsIgnoreCase("-outpath")) workPath = value + "/";
	else if(key.equalsIgnoreCase("-name")) {
	    if(value.equalsIgnoreCase("auto")) reducedName = null;
	    else reducedName = Crush.workPath + value;
	}
	else if(key.equalsIgnoreCase("-out")) {
	    if(value.equalsIgnoreCase("auto")) reducedName = null;
	    else reducedName = value;
	}
	else if(key.equalsIgnoreCase("-MaiTau")) tauServer = value;
	else if(key.equalsIgnoreCase("-precess")) reducedEpoch = Double.parseDouble(value);
	else if(key.equalsIgnoreCase("-default")) {
	    parseConfig(defaultConfig);
	    cleaner.defaults();
	}	
	else if(key.equalsIgnoreCase("-bright")) {
	    parseConfig("bright.cfg");
	}
	else if(key.equalsIgnoreCase("-faint") || key.equalsIgnoreCase("-weak")) {
	    parseConfig("faint.cfg");
	}	
	else if(key.equalsIgnoreCase("-deep")) {
	    parseConfig("deep.cfg");
	}	
	else if(key.equalsIgnoreCase("-config")) parseConfig(value);
	else if(key.equalsIgnoreCase("-precess")) {
	    reducedEpoch = Double.parseDouble(value);
	}
	else if(key.equalsIgnoreCase("-point")) {
	    point = true;
	    parseConfig("point.cfg");
	}
	else if(key.equalsIgnoreCase("-info")) { printInfo = true; }
	else if(key.equalsIgnoreCase("-log")) {
	    if(value.equalsIgnoreCase("off") || value.equalsIgnoreCase("false")) logName = null;
	    else logName = value;
	}	    
	else if(key.equalsIgnoreCase("-spectrum")) { spectrumDirective = value; }
	else if(key.equalsIgnoreCase("-skySpectrum")) { 
	    if(value.equalsIgnoreCase("off")) skySpectrumT = -1;
	    else skySpectrumT = Integer.parseInt(value); 
	}
	else if(key.equalsIgnoreCase("-scanMaps")) { writeScanMaps = true; }
	else if(key.equalsIgnoreCase("-help")) usage();
	else if(key.equalsIgnoreCase("-version")) { 
	    //versionInfo(); 
	    System.exit(0); 
	}
	else if(scanTemplate.option(key, value)) return;
	else if(cleaner.option(key, value)) return;
	else {
	    System.out.println(" WARNING! Unrecognized option: " + key);
	}
    }    

    public static void quickHelp() {
	String info= "\n"
	    + "For information on syntax and options type \'crush -help\'.\n";
	System.out.println(info);
	System.exit(0);
    }

    public static void usage() {
	String info = "\n"
	    + "Syntax: crush [[-option]...] scan-spec [[[-option]...] scan-spec] [...]" + "\n"
	    + "\n"
	    + " Scan Specifications: " + "\n"
	    + "  #             Scan number. E.g. 8293." + "\n"
	    + "  #-#           Range of scan numbers. E.g. 8287-8291." + "\n"
	    + "  <name>        Scan file name with or without path." + "\n"
	    + "\n"
	    + " Options to Crush: " + "\n"
	    + "\n"
	    + "  -outpath=     Set the default output path. (" + workPath + ")\n"
	    + "  -name=        Specify the name of the output file or 'auto'.\n"
	    + "  -out=         Full path and name of the output or 'auto'. (" + (reducedName == null ? "auto" : reducedName) + ")\n"
	    + "  -MaiTau=      The Mai-Tau server address.. (" + tauServer + ")\n"
	    + "  -default      Set the default values from \'" + defaultConfig + "\'. " +
	    ( currentConfig.equals(defaultConfig) ? "(*)" : "" ) + "\n"
	    + "  -bright       Set Bright Source overriders (bright.cfg). E.g. Jupiter." + 
	    ( currentConfig.equals("bright.cfg") ? "(*)" : "" ) + "\n"
	    + "  -faint        Set Faint Source overriders (faint.cfg)." + 
	    ( currentConfig.equals("faint.cfg") ? "(*)" : "" ) + "\n"
	    + "  -deep         Set Deep Field Source overriders (deep.cfg)." + 
	    ( currentConfig.equals("deep.cfg") ? "(*)" : "" ) + "\n"
	    + "  -point        Reduce in pointing mode (point.cfg). " + (point ? "(*)" : "") + "\n" 
	    + "  -config=      Parse the specified configuration file. (" + currentConfig + ")\n" 
	    + "  -precess=     Precess coordinates to the epoch. (" + reducedEpoch + ")\n"
	    + "  -info         Provide scan info on the console and exit. (" + (printInfo ? "*" : "") + ")\n"
	    + "  -log=         Log scan info to the specified file and exit. (" + (logName == null ? "off" : logName) + ")\n"
	    + "  -spectrum=    Write spectral info for specified model. (" + spectrumDirective + ")\n"
	    + "  -skySpectrum= Write 2-D sky spectrum. (" + (skySpectrumT > 1 ? skySpectrumT  + "" : "off") + ")\n"
	    + "  -scanMaps     Write maps for each scan too. " + (writeScanMaps ? "(*)" : "") + "\n"
	    + "  -help         Show this help screen.\n"
	    + "  -version      Print version information.\n";
	
	System.out.println(info);

	scanTemplate.usage();
	cleaner.usage();
	
	System.exit(0);
    }
	    

    public static void editHeader(Cursor cursor) throws FitsException, HeaderCardException, IOException {
	cursor.add(new HeaderCard("MAPPATH", workPath, "The path where output maps are placed."));
	cursor.add(new HeaderCard("PRECESS", reducedEpoch, "The desired coordinate epoch for output files."));
	cursor.add(new HeaderCard("DEFAULT", defaultConfig, "The default config file which is parsed on startup."));
	cursor.add(new HeaderCard("POINTING", point, "Whether pointing was performed post reduction."));
	cursor.add(new HeaderCard("MAI-TAU", tauServer, "The inet address of the Mai-Tau server."));
    }    
   
    public static boolean isOption(String argument) {
	return argument.charAt(0) == '-';
    }
    
    public static void statusMessage(String message) {
	System.out.print(message);
	System.out.flush();
    }
}


