/*
 *	(C) James T. Hoffman and Sun Synchrony
 *
 *
 *	SUMMARY:
 *
 *	Generate, for given shape:
 *	  * profile images
 *	  * spin images
 *	  * tiling array images
 *	  * array aperture images, given incidence angle
 *	  * histograms of images
 *	Generates input for Polar.java, which:
 *	  * graphs of light-capture efficiency in disk-normalized sky coordinates
 *
 * 	USAGE:
 *
 *	java jt.aperture.Aperture <args>
 *	<args>:
 *		<shape-specifier>  <args> <actions>
 *
 *	<shape-specifier>:
 *		synchro <elong> <trunc>
 *		square
 *		diamond
 *		equihex
 *		althex
 *		shield
 *
 *	<args>:
 *		R <portrad>		portrad
 *		M <insetmarg>		inset margin
 *
 *	<action>:
 *		O			offset to accomodate excursions
 *		H			histogram
 *		P			draw profile
 *		S			draw spin outline
 *		T			draw tile
 *		A <tiltx> <tilty>	draw array
 *
 */


package jt.aperture;

import java.util.*;
import java.text.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.image.renderable.*;
import javax.swing.*;
import javax.imageio.*;
import javax.media.jai.*;
import java.io.*;

import jt.util.*;
import jt.geom.*;
import jt.vewg.*;


class Aperture extends JFrame implements Runnable {

  double portRad = 1.6;
  int imgWide = 1600;
  int imgHigh = 1600;
  boolean colorq = false;
  int extendReps = 6;

  double insetMarg = 0.0;
				// mode flags
  boolean profileQ = false;
  boolean histoQ = false;
  boolean spinQ = false;	// can combine spin with tile if not aperture
  boolean tileQ = false;
  boolean arrayQ = false;
  boolean offsetQ = false;

  double[][] sCurve(double elong,double truncWid) {
    int n = 40;
    int num = n*2 + 1;
    double[][] data = new double[num][];
    double rad = Maux.hypot(1.0,elong*2)/2;
    double ang1 = Math.acos(1.0/rad);		// edge intercept
    double ang2 = Math.acos(0.5/rad);		// inflection
    double angd = ang2 - ang1;
    int j = 0;
    for (int i = 0; i <= n; i++) {
      double a = ang1 + (ang2 - ang1)*i/n;
      data[j++] = new double[] {
	rad*Math.cos(a),
	rad*Math.sin(a)
      };
    }
    double ang3 = ang2 + Math.PI;
    double ang4 = Math.acos((1.0 - truncWid)/rad) + Math.PI;
    for (int i = 1; i <= n; i++) {
      double a = ang3 + (ang4 - ang3)*i/n;
      data[j++] = new double[] {
	1.0     + rad*Math.cos(a),
	elong*2 + rad*Math.sin(a)
      };
    }
    return data;
  }
  class Poly {
    double[][] data;			// profile quadrant
    Vec uDir,vDir;			// tiling vectors
    Vec offset;				// tiling offset
    Vec[] verts;			// complete profile

    class Elem implements Comparable {
      Vec center;
      Elem(Vec v) {
	center = v;
      }
      public int compareTo(Object o) {
	Elem elem = (Elem)o;
	return elem.center.z() > center.z() ? 1 : -1;
      }
    }

    Poly(double[][] d, Vec ud, Vec vd) {
      data = d;
      uDir = ud;
      vDir = vd;
    }
    Poly(double[][] d, Vec ud, Vec vd, Vec off) {
      this(d,ud,vd);
      offset = off;
    }
    void build(Vec center) {
      int len = data.length;
      verts = new Vec3[len*4];
      for (int k = 0; k < 4; k++) {	// make symmetric
	double xsn = k/2*2 - 1;
	double ysn = (k + 1)%4/2*2 - 1;
	int ksn = k%2;
	for (int i = 0; i < len; i++) {
	  int l = ksn > 0 ? i : len - 1 - i;
	  verts[k*len + l] = new Vec3(data[i][0]*xsn,data[i][1]*ysn,0d);
	}
      }
      cull();				// eliminate dup verts
      if (insetMarg > 0d) inset();
      displace(center);
    }
    void cull() {
      int len = verts.length;
      int i,ic,j = 0;
      for (i = 0; i < len; i++) {
	ic = (i + 1)%len;
	if (verts[i].dist(verts[ic]) == 0d) continue;
	verts[j++] = verts[i];
      }
      Vec[] vsn = new Vec3[j];
      for (i = 0; i < j; i++)
	vsn[i] = verts[i];
      verts = vsn;
    }
    void inset() {
      int len = verts.length;
      Vec[] vsn = new Vec3[len];
      double ymn = 0d,ymx = 0d;
      for (int i = 0; i < len; i++) {
	Vec v1 = verts[i];
	Vec v2 = verts[(i + 1)%len];
	Vec v3 = verts[(i + 2)%len];
	double y = v1.y();
	if      (y < ymn) ymn = y;
	else if (y > ymx) ymx = y;
	Vec vd1 = v1.dif(v2);
	Vec vd3 = v3.dif(v2);
	vd1.rotate2(-Math.PI/2).norm();
	vd3.rotate2( Math.PI/2).norm();
	double dot = vd1.normDotProd(vd3);
	if (dot < 0d) dot = -dot;
 	if (dot < 1.0e-8) dot = 0.707;
	Vec vd = vd1.sum(vd3).norm().prod(-insetMarg/dot/2);
	vsn[(i + 1)%len] = v2.sum(vd);
      }
      ymx -= insetMarg; ymn += insetMarg;
      for (int i = 0; i < len; i++) {	// trim overshoot from sharp corners
	Vec v = vsn[i];
	if (v.y() > ymx) v.set(1,ymx);
	if (v.y() < ymn) v.set(1,ymn);
      }
      verts = vsn;
    }
    void spin(double aX,double aY) {
      for (int i = 0; i < verts.length; i++) {
	Vec v = verts[i];
	v.rotate(aX,0,2);
	v.rotate(aY,1,2);
      }
    }
    void displace(Vec duv) {
      if (duv == null) return;
      for (int i = 0; i < verts.length; i++) {
	Vec v = verts[i];
	v.add(duv);
      }
    }
    void setStrokeThick(float thick) {
	gr.setStroke(new BasicStroke(
		thick,
		BasicStroke.CAP_SQUARE,BasicStroke.CAP_SQUARE,2f,null,0f));
    }
    void draw(
	float thick,
	boolean fill,boolean outline,
	Color fillCol,Color outlineCol) {
      Polygon p = new Polygon();
      for (int i = 0; i < verts.length; i++) {
	Vec v = verts[i];
	p.addPoint(
	  (int)((v.x()/portRad + 1)*imgRad),
	  (int)((v.y()/portRad + 1)*imgRad));
      }
      if (fill) {
	gr.setColor(fillCol);
	gr.fillPolygon(p);
      }
      if (outline) {
	setStrokeThick(thick);
	gr.setColor(outlineCol);
	gr.drawPolygon(p);
      }
    }				// draw
    void drawSpinSet(Vec center,float thick,
	double angXMax, double angYMax,
	double angXInc, double angYInc) {
      for (double angX = 0; angX <= angXMax; angX += angXInc)
        for (double angY = -angYMax; angY <= angYMax; angY += angYInc) {
	  build(null);
	  double aX = angX*Math.PI/180;
	  double aY = angY*Math.PI/180;
	  spin(aX,aY);
	  displace(center);
	  draw(thick,
		colorq,true,
		Color.GRAY,Color.BLACK);
        }
    }				// drawSpinSet
    void drawArray(
	int uL, int vL,
	double angX, double angY) {
      double aX = angX*Math.PI/180;
      double aY = angY*Math.PI/180;
      TreeSet list = new TreeSet();
      Vec ud = new Vec2(uDir);
      Vec vd = new Vec2(vDir);
      if (offsetQ && offset != null) {
	ud.add(0,offset.x());
	vd.add(1,offset.y());
      }
      for (int iu = -uL; iu <= uL; iu++) {
	Vec u = new Vec3(ud).prod((double)iu);
	for (int iv = -vL; iv <= vL; iv++) {
	  Vec v = new Vec3(vd).prod((double)iv);
	  Vec w = v.sum(u);
	  w.rotate(aY,1,2);
	  w.rotate(aX,0,2);
	  list.add(new Elem(w));
	}
      }
      for (Iterator it = list.iterator(); it.hasNext();) {
	Elem elem = (Elem)it.next();
	if (spinQ)
	  drawSpinSet(elem.center,1.5f,60d,60d,5d,5d);
	build(elem.center);
	draw(histoQ ? 0.1f : 2f,
		true,true,
		Color.WHITE,Color.BLACK);
      }
    }				// drawArray
  }
  class Square extends Poly {
    Square() { 
      super(
	new double[][] { { 1.0,1.0 } },
	new Vec2(2.0,0.0),
	new Vec2(0.0,2.0),
	new Vec2(0.0,0.60));
    }
  }
  class Equihex extends Poly {
    Equihex() { 
      super(
	new double[][] { { 1.0,0.5773 }, { 0.0,1.1542 } },
	new Vec2(2.0,0.0),
	new Vec2(1.0,1.7319),
	new Vec2(0.0,0.27));
    }
  }
  class Althex extends Poly {
    Althex() { 
      super(
	new double[][] { { 1.0,0.0 }, { 0.5,1.2 } },
	new Vec2(3.0,0.0),
	new Vec2(1.5,1.2),
	new Vec2(0.0,0.10));
    }
  }
  class Diamond extends Poly {
    Diamond() { 
      super(
	new double[][] { { 1.3,0.0 }, { 0.0,1.3 } },
	new Vec2(2.6,0.0),
	new Vec2(1.3,1.3),
	new Vec2(0.0,0.60));
    }
  }
  class Shield extends Poly {
    Shield() { 
      super(
	new double[][] { { 1.0,0.4 }, { 0.8,0.7 }, { 0.2,1.1 }, { 0.0,1.4 } },
	new Vec2(2.0,0.0),
	new Vec2(1.0,1.8),
	new Vec2(0.0,0.07));
    }
  }
  class Hex2 extends Poly {
    Hex2() { 
      super(
	new double[][] { { 1.0,0.5 }, { 0.0,1.3 } },
	new Vec2(2.0,0.0),
	new Vec2(0.0,1.8));
    }
  }
  class Shield2 extends Poly {
    Shield2() { 
      super(
	new double[][] { { 1.4,0.0 }, { 1.0,0.7 }, { 0.4,0.7 }, { 0.0,1.4 } },
	new Vec2(2.8,0.0),
	new Vec2(0.0,1.4));
    }
  }
  class Synchro extends Poly {
    Synchro(double elong,double truncWid) { 
      super(
	sCurve(elong,truncWid),
	new Vec2(2.0,0.0),
	new Vec2(1.0,elong*2));
    }
    Synchro(String elo,String trun) {
      this(Double.parseDouble(elo),(double)(Double.parseDouble(trun)));
    }
  }
  /*
   *
   */
  Image image;
  int imgRad;
  Component pane;
  Graphics2D gr;
  static Color lightGray = new Color(0.9f,0.9f,0.9f);
  static Color gapRed = new Color(0.8f,0.2f,0.0f);

  private BufferedImage getBufferedImageFromImage(Image img)
  {
    // this makes sure that the image is loaded fully
    //
    img = new ImageIcon(img).getImage();
    BufferedImage bufferedImage = new BufferedImage(
                img.getWidth(null),
                img.getHeight(null),
                BufferedImage.TYPE_INT_RGB);
    Graphics gr = bufferedImage.createGraphics();
    gr.drawImage(img, 0,0, null);
    gr.dispose();
    return bufferedImage;
  }
  void saveAsPNG(String nam) {
    BufferedImage bufferedImage = getBufferedImageFromImage(image);
    try {
      File f = new File("img/"+ nam +".png");
      ImageIO.write(bufferedImage,"png",f);
      System.out.println("Image created Successfully");
    } catch(IOException io) {
      SO.coml("error in ImageIO.write");
    }
  }
  void writeTo(String fname,String s) {
    try {
      BufferedWriter out = new BufferedWriter(new FileWriter(fname));
      out.write(s);
      out.close(); 
    } catch (IOException e) { } 
  }
  double compLossRatio() {
    int[] bins = { 20,20,20 };	// The number of bins.
    double[] low = { 0.0,0.0,0.0 };
    double[] high = { 256.0,256.D,256.D };

    ParameterBlock pb = new ParameterBlock();
    pb.addSource(image);	// Specify the source image
    pb.add(null);		// No ROI
    pb.add(1);			// Sampling
    pb.add(1);			// periods
    pb.add(bins);
    pb.add(low);
    pb.add(high);
    RenderedOp op = JAI.create("histogram",pb,null);
    Histogram hist = (Histogram)op.getProperty("histogram"); 

    int nBin = hist.getNumBins(0);
    // int[] bar = new int[nBin];
    int[] siza = new int[3];
    int total = 0;
    int j = 0;
    for (int i = 0; i < nBin; i++) {
	int bar = hist.getBinSize(0, i);
	if (bar > 0) total += siza[j++] = bar;
    }
    double ratio = (double)siza[1]/(double)total;
    // SO.coml("ratio: "+ ratio);
    if (ratio < 0.0001 || ratio > 0.99) ratio = 0;
    String s = new DecimalFormat("#0.0000").format(ratio);
    writeTo("_lossratio",s);
    return ratio;
  }

  void setup() {
    pack();
    setVisible(false);
    pane = getContentPane();
    imgRad = imgWide/2;
    image = pane.createImage(imgWide,imgHigh);
    gr = (Graphics2D)image.getGraphics();
    gr.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
	 histoQ ? RenderingHints.VALUE_ANTIALIAS_OFF
		: RenderingHints.VALUE_ANTIALIAS_ON);
  }
  void clear(Color col) {
    gr.clearRect(0,0,imgWide,imgHigh);
    gr.setColor(col);
    gr.fillRect(0,0,imgWide,imgHigh);
  }

  static Poly shape;
  static String shapeName;

  void drawProfile(String name) {
    setup();
    clear(Color.WHITE);
    shape.build(null);
    shape.draw(3f,
	true,true,
	lightGray,Color.BLACK);
    saveAsPNG(name);
  }
  void drawSpinOutline(String name) {
    setup();
    clear(Color.WHITE);
    shape.drawSpinSet(null,1.5f,60.0,60.0,5.0,5.0);
    shape.build(null);
    shape.draw(3f,
	true,true,
	lightGray,Color.BLACK);
    shape.drawSpinSet(null,0.1f,60.0,60.0,5.0,5.0);
    shape.draw(5f,
	false,true,
	null,Color.YELLOW);
    shape.build(null);
    shape.draw(5f,
	false,true,
	null,Color.GREEN);
    saveAsPNG(name);
  }
  void drawArray(String name,int angX,int angY) {
    setup();
    clear(gapRed);
    shape.drawArray(extendReps,extendReps,(double)angX,(double)angY);
    if (histoQ) compLossRatio();
    else saveAsPNG(name);
    writeTo("_imagename",name);		// record image name for rb script
  }

  void parseOpts(String[] argv) {
    String sax = null,say = null;
    int iarg = 0;
    while (iarg < argv.length) {
      String s = argv[iarg++];
      switch (s.charAt(0)) {
	case 'a':
	case 's':
	case 'd':
	case 'e':
	  String nameAux = "";
	  if (s.equals("synchro")) {
		String elo = argv[iarg++];
		String tru = argv[iarg++];
		shape = new Synchro(elo,tru);
		nameAux = "_"+ elo;
	  }
	  else if (s.equals("square"))  shape = new Square();
	  else if (s.equals("diamond")) shape = new Diamond();
	  else if (s.equals("equihex")) shape = new Equihex();
	  else if (s.equals("althex"))  shape = new Althex();
	  else if (s.equals("shield"))  shape = new Shield();
	  shapeName = s + nameAux;
	break;
	case 'R':
	  portRad = Double.parseDouble(argv[iarg++]);
	break;
	case 'M':
	  insetMarg = Double.parseDouble(argv[iarg++]);
	break;
					// mode flags
	case 'O':
	  offsetQ = true;
	break;
	case 'H': histoQ = true;
	break;
	case 'P': profileQ = true;
	break;
	case 'S': spinQ = true;
	break;
	case 'T': tileQ = true;
	break;
	case 'A': arrayQ = true;
	  sax = argv[iarg++];
	  say = argv[iarg++];
	break;
      }
    }			// while
    if (histoQ) {
	imgWide = 2000;
	imgHigh = 2000;
	extendReps = 8;
    }
    if (profileQ)
	drawProfile(shapeName + "_prof");
    if (spinQ && !tileQ)
	drawSpinOutline(shapeName + "_spin");
    if (tileQ)
	drawArray(shapeName + (spinQ ? "_spintile" : "_tile"),0,0);
    if (arrayQ)
	drawArray(shapeName +"_aperture_"+ sax +"_"+ say,
		Integer.parseInt(sax),
		Integer.parseInt(say));
    System.exit(0);
  }
  public void run() {
	// todo
  }
  public static void main(String[] argv) {
    Aperture that = new Aperture();
    that.parseOpts(argv);
  }
}
