code: /*******************************************************************************
   * Copyright (c) 2016 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.gismo;
  
  import java.io.IOException;
  import java.io.PrintStream;
  import java.text.ParseException;
  import java.util.ArrayList;
  import java.util.Hashtable;
  import java.util.StringTokenizer;
  
  import crush.CRUSH;
  import jnum.Unit;
  import jnum.Util;
  import jnum.io.LineParser;
  import jnum.math.Vector2D;
  import jnum.text.SmartTokenizer;
  
  
  public class SyntheticRCP {
  	ArrayList<Coefficient> xcoeffs = new ArrayList<>();
  	ArrayList<Coefficient> ycoeffs = new ArrayList<>();
  	
  	Hashtable<Integer,Double> sourceGains;
  	Hashtable<Integer,Double> skyGains;
  	
  	double rotate = 0.0;
  	
  	public static void main(String[] args) {
  		SyntheticRCP distortion = new SyntheticRCP();
  		
  		try {
  			if(args.length == 0) usage();
  			distortion.parse(args[0]);
  			if(args.length > 1) distortion.gainsFrom(args[1]);
  			if(args.length > 2) distortion.rotate = Double.parseDouble(args[2]) * Unit.deg;
  			distortion.print(System.out);
  		}
  		catch(Exception e) { CRUSH.error(SyntheticRCP.class, e); }
  		
  	}
  	
  	public void parse(String fileName) throws IOException {	
  	    new LineParser() {
              @Override
              protected boolean parse(String line) throws Exception {
                  StringTokenizer tokens = new StringTokenizer(line);
                  String spec = tokens.nextToken();
                
                  xcoeffs.add(new Coefficient(spec, tokens.nextToken())); 
                  ycoeffs.add(new Coefficient(spec, tokens.nextToken())); 
                  return true;
              }
  	    }.read(fileName);
  		
  		CRUSH.info(this, (xcoeffs.size() + ycoeffs.size()) + " coefficients parsed.");
  	}
  
  	public void gainsFrom(String rcpFile) throws IOException {		
  		CRUSH.info(this, "Loading gains from " + rcpFile);
  		
  		sourceGains = new Hashtable<>();
  		skyGains = new Hashtable<>();
  	
  		new LineParser() {
              @Override
              protected boolean parse(String line) throws Exception {
                  SmartTokenizer tokens = new SmartTokenizer(line);
                  int channel = tokens.nextInt();
                  sourceGains.put(channel, tokens.nextDouble());
                  if(tokens.hasMoreTokens()) skyGains.put(channel, tokens.nextDouble());
                  return true;
              }
  		}.read(rcpFile);
  	}
  	
  	public double getValue(ArrayList<Coefficient> coeffs, double x, double y) {
  		double value = 0.0;
  		for(Coefficient c : coeffs) value += c.valueAt(x, y);
  		return value;		
  	}
  	
  	public void print(PrintStream out) {
  		out.println("# Synthetic RCP ");
  		for(int i=0; i<128; i++) {
  			double dc = i % 8 - 3.5;
  			double dr = i / 8 - 7.5;
  			
  			int channel = (i+1);
  			double sourceGain = 1.0;
  			double skyGain = 1.0;
  			
  			if(sourceGains != null) if(sourceGains.containsKey(channel)) sourceGain = sourceGains.get(channel);
  			if(skyGains != null) if(skyGains.containsKey(channel)) skyGain = skyGains.get(channel);
  			
  			Vector2D pos = new Vector2D(
  						getValue(xcoeffs, dc, dr),
  						getValue(ycoeffs, dc, dr)
  					);
  			
  			pos.rotate(rotate);
  			
  			out.print(channel + "\t" + sourceGain + "\t" + skyGain + "\t");
  			out.print(Util.f1.format(pos.x()) + "\t");
  			out.print(Util.f1.format(pos.y()) + "\n");
  		}
  	}
  	
  	class Coefficient {
  		int xOrder, yOrder;
  		double value;		
  	
  		public Coefficient(String spec, String value) throws NumberFormatException, ParseException {
  			this.value = Double.parseDouble(value);
  			
  			xOrder = yOrder = 0;
  			
  			spec = spec.toLowerCase();
  			
  			int from = 0;
  			while(from < spec.length()) from = parse(spec, from);
  		}
  		
  		public double valueAt(double x, double y) {
  			return value * Math.pow(x, xOrder) * Math.pow(y, yOrder);
  		}
  		
  		private int parse(String spec, int from) throws NumberFormatException {
  			char c = spec.charAt(from);
  			
  			switch(c) {
  			case '0' : break;
  			case 'x' :
  				from++;
  				c = from < spec.length() ? spec.charAt(from) : '1';
  				if(c == 'y') xOrder++;
  				else xOrder += Integer.parseInt(c + "");
  				break;
  			case 'y' :
  				from++;
  				c = from < spec.length() ? spec.charAt(from) : '1';
  				if(c == 'x') yOrder++;
  				else yOrder += Integer.parseInt(c + "");
  			}
  			
  			CRUSH.detail(this, "> " + xOrder + "," + yOrder + " : " + value);
  			
  			return from+1;
  		}
  		
  	}
  	
  	public static void usage() {
  		System.out.println( 
  			"  -----------------------------------------------------------------------------\n" +
  			"  SyntheticRCP -- RCP generation tool.\n" +
  			"                  Copyright (C)2011 Attila Kovacs <attila[AT]sigmyne.com>\n" +
  			"  -----------------------------------------------------------------------------\n" +	
  			"\n" +
  			"  Usage: java crush.array.SyntheticRCP <distortion> [rcp [rotation]]\n" +
  			"\n" +
  			"    <distortion>  A list of distortion parameters. See e.g. 'gismo/distortion.dat'.\n" +
  			"    [rcp]         (optional) An observed RCP file for source gains.\n" +
  			"    [rotation]	   (optional) Presume a rotation in degrees.\n");
  		
  		System.exit(0);
  	}
  }
  