/***
 * A program to generate the Mayan Periodic chart of the elements
 * @author Mitch Fincher, mitchfincher@yahoo.com
**/

import java.awt.* ;
import java.awt.geom.* ;
import java.awt.event.* ;
import java.awt.image.* ;
import java.io.* ;
import Acme.JPM.Encoders.*;
import com.sun.image.codec.jpeg.*;

class Mayan2 extends Frame implements AdjustmentListener {

    static int GAS = 0, LIQUID=1, SOLID=2;
    static Color colors[] = {new Color(255,200,200),new
	Color(200,255,200),new Color(200,200,255)};
    static Color titleColor = new Color(50,50,50);
    static Color elementColor = new Color(0,0,0);
    static Color ringColor = new Color(220,0,0);
    static Color spokeColor = new Color(0,255,255);
    static int CumulativeElectronsInShell[] = { 0,2,10,18,36,54,86,120};
    static int electronsInShell[] = { 0,2,8,8,18,18,32,34};
    Font titleFont,elementNameFont,elementSymbolFont,nobleElementNameFont,nobleElementSymbolFont;
    //   Cursor mycursor;
    ScrollPane scrollPane1 = null;
    Panel chartPanel = null;
    int frameWidth=900;
    int frameHeight=500;
    int lineWidth;
    int logicalWidth = 1152;
    int logicalHeight = 834; //864
    int fontsize,xcenter,ycenter,dr;
    BufferedImage img = null; 
    double twoPI = 2.0*Math.PI;
    double halfPI = Math.PI / 2.0;
    Graphics2D g2 = null;
    int numShells = 7;
    int numElements = 109;
    Element element[];
    String[]  elementSymbol = { "null","H", "He","Li", "Be", "B", "C", "N", "O", "F", "Ne","Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar","K", "Ca", "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge", "As", "Se", "Br", "Kr","Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc", "Ru", "Rh", "Pd", "Ag", "Cd", "In", "Sn", "Sb", "Te", "I", "Xe","Cs", "Ba", "La", "Ce", "Pr", "Nd", "Pm", "Sm", "Eu", "Gd", "Tb", "Dy", "Ho", "Er", "Tm", "Yb", "Lu", "Hf", "Ta", "W", "Re", "Os", "Ir", "Pt", "Au", "Hg", "Tl", "Pb", "Bi", "Po", "At", "Rn","Fr", "Ra", "Ac", "Th", "Pa", "U", "Np", "Pu", "Am", "Cm", "Bk", "Cf", "Es", "Fm", "Md", "No", "Lr", "Rf", "Db", "Sg", "Bh", "Hs", "Mt" };

    String[] elementFullname = { "null", "Hydrogen", "Helium", "Lithium","Berylium", "Boron", "Carbon", "Nitrogen", "Oxygen", "Fluorine", "Neon","Sodium", "Magnesium", "Aluminum", "Silicon", "Phosphorous", "Sulfur","Chlorine", "Argon", "Potassium", "Calcium", "Scandium", "Titanium","Vanadium", "Chromium", "Manganese", "Iron", "Cobalt", "Nickel", "Copper","Zinc", "Gallium", "Germanium", "Arsenic", "Selenium", "Bromine", "Krypton","Rubidium", "Strontium", "Yttrium", "Zirconium", "Niobium", "Molybdenum","Technetium", "Ruthenium", "Rhodium", "Palladium", "Silver", "Cadmium","Indium", "Tin", "Antimony", "Tellurium", "Iodine", "Xenon", "Cesium","Barium", "Lanthanum", "Cerium", "Praseodymium", "Neodymium", "Promethium","Samarium", "Europium", "Gadolinium", "Terbium", "Dysprosium", "Holmium","Erbium", "Thulium", "Ytterbium", "Lutetium", "Hafnium", "Tantalum","Tungsten", "Rhenium", "Osmium", "Iridium", "Platinum", "Gold", "Mercury","Thallium", "Lead", "Bismuth", "Polonium", "Astatine", "Radon", "Francium","Radium", "Actinium", "Thorium", "Protactinium", "Uranium", "Neptunium","Plutonium", "Americium", "Curium", "Berkelium", "Californium", "Einsteinium","Fermium", "Mendelevium", "Nobelium", "Lawrencium", "Rutherfordium", "Dubnium", "Seaborgium","Bohrium", "Hassium", "Meitnerium" };

 public Mayan2(){
   super("Mayan Periodic Chart of the Elements");
 }

public void init() {
    fontsize = logicalHeight/100;
    xcenter = logicalWidth/2;
    ycenter = logicalHeight/2+30; // 30 for main title
    dr = logicalHeight / 16; // deltaradius; the radius of the first circle
    lineWidth = 3+logicalHeight/500;
   setBounds( 0, 0, frameWidth,  frameHeight);
   setBackground( Color.white );
   setLayout(new BorderLayout(0,0));
   scrollPane1 = new ScrollPane();
   chartPanel = new Panel();
   if(false) {
       chartPanel.setBackground( Color.white ); /*Color.white*/
       scrollPane1.setBackground( Color.white  );// real color of backgroud
   } else {
       chartPanel.setBackground( new Color(78,120,171)); /*Color.white*/
       scrollPane1.setBackground( new Color(78,120,171));// real color of backgroud
   }
   add(BorderLayout.CENTER,scrollPane1);
   scrollPane1.setBounds(0,0,logicalWidth,logicalHeight);
   chartPanel.setLayout(null);
   scrollPane1.add(chartPanel);
   chartPanel.setForeground(java.awt.Color.cyan);
   chartPanel.setBounds(0,0,logicalWidth-4,logicalHeight-4);
   add(scrollPane1);
   String fontname = "Helvetica";
   titleFont = new Font(fontname, Font.BOLD, fontsize*3);
   elementSymbolFont = new Font(fontname, Font.PLAIN, fontsize*2);
   nobleElementSymbolFont = new Font(fontname, Font.BOLD, fontsize*2);
   elementNameFont = new Font(fontname, Font.PLAIN, fontsize);
   nobleElementNameFont = new Font(fontname, Font.BOLD, fontsize);

   element = new Element[numElements+1];
   for(int i=1;i<=numElements;i++)
      {
	 element[i] =  new Element(this,elementSymbol[i],
					   elementFullname[i],i);
      }

   addWindowListener( new WindowAdapter(){
	   public void windowClosing( WindowEvent e ){
	       System.exit(0);
	   }
	   public void windowActivated(WindowEvent e) {
	       paint( g2 /*e.getWindow().getGraphics()*/ );
	   }
       }
		      );
 


   Adjustable vadjust = scrollPane1.getVAdjustable();
   vadjust.addAdjustmentListener(this);
   Adjustable hadjust = scrollPane1.getHAdjustable();
   hadjust.addAdjustmentListener(this);
 }

public void adjustmentValueChanged(AdjustmentEvent e) {
       paint( g2 );
   }

synchronized void drawString(Color color, Font font, String text, int x,int y) {
    g2.setFont( font );
    g2.setColor(color);
    g2.drawString(text,x,y); 
}


synchronized void drawCenteredString(Graphics2D g, Color color, String mystring,int x,int y) 
   {
       g.setColor(color);
       x = x - g.getFontMetrics().stringWidth(mystring)/2;
       g.drawString(mystring,x,y); 
   }

public void drawRings() {
    g2.setColor(Color.red);
    for (int i=1;i <= numShells;i++) {
	int diameter = dr*i*2;
	g2.drawArc(xcenter-diameter/2,ycenter-diameter/2,
		   diameter,diameter, 0,360);
    }
}

public void drawSpokes() {
    for (int i=1;i <= numShells;i++) {
	int numberOfElementsInThisRing = CumulativeElectronsInShell[i]-CumulativeElectronsInShell[i-1];
	double deltatheta = (2.0*Math.PI)/((double)numberOfElementsInThisRing);
	double theta;
	if(i==2 || i==3 || i==6 ) {
	    theta = deltatheta/2.0;
	} else {
	    theta = deltatheta;
	}
	for(int j=0;j<numberOfElementsInThisRing;j++) {
	    double x1, y1, x2, y2;
	    g2.setColor(Color.red);
	    theta += deltatheta;
	    x1 = Math.cos(theta) * dr * (i-1) + xcenter;
	    x2 = Math.cos(theta) * dr * i + xcenter;
	    y1 = Math.sin(theta) * dr * (i-1) + ycenter;
	    y2 = Math.sin(theta) * dr * i + ycenter;
	    drawLine(ringColor,(int)x1,(int)y1,(int)x2,(int)y2);
	}

    }
}


public void paint(Graphics g ){
    if(g2 == null) { 
	g2 = (Graphics2D)chartPanel.getGraphics();
	g2.setStroke(new BasicStroke(lineWidth));
    }
    g2.setFont( titleFont );
    //Dimension d = getSize();
    drawCenteredString( g2, titleColor, "Mayan Periodic Chart of the Elements", xcenter,20+2*fontsize);
    g2.setFont( nobleElementNameFont );
    drawCenteredString( g2, titleColor, "by Mitch Fincher 2000/08/30", xcenter,20+4*fontsize);
    drawLegend();
    drawElementsThreaded();
    drawRings();
    //drawSpokes();
  }

synchronized public void drawLine( Color color, int x1, int y1, int x2, int y2) {
    g2.setColor( color );
    g2.drawLine(x1,y1,x2,y2);
}

public void drawLegend() {
    int x = xcenter+logicalWidth/3;
    int y = logicalHeight/10;
    int width = fontsize*4;
    Font font = nobleElementSymbolFont;
    drawString(titleColor,font,"Legend",x+width,y+width/2);
    y += fontsize*6;
    drawLegendLine( x, y, width,"Liquid" ,font, colors[LIQUID]);
    y += fontsize*6;
    drawLegendLine( x, y, width,"Solid" , font, colors[SOLID]);
    y += fontsize*6;
    drawLegendLine( x, y, width,"Gas" , font, colors[GAS]);
}
synchronized public void drawLegendLine(int x, int y, int width, String text,Font font, Color color) {
    g2.setColor(color);
    g2.fillRect(x+width,y,width,width);
    g2.setColor(Color.red);
    g2.drawRect(x+width,y,width,width);
    g2.setColor(Color.black);
    g2.setFont( font );
    g2.drawString(text,x+3*width,y+width/2);
}


public void drawElements() {
   g2.setColor(Color.black);
   for(int i=1;i<=numElements;i++)
      {
	  element[i].draw(g2);
      }
}
public void drawElementsThreaded() {
    Thread threads[] = new myThread[numElements+1];

   g2.setColor(Color.black);
   for(int i=1;i<=numElements;i++)
      {
	  threads[i] = new myThread(element[i]);
      }
   for(int i=1;i<=numElements;i++)
      {
	  threads[i].start();
      }
}


public void setLogicalWidth(int in) {
    logicalWidth = in;
}
public void setLogicalHeight(int in) {
    logicalHeight = in;
    //since fontsize is a dervived value we need to reset it
    fontsize = logicalHeight/100;
}

public void writeGIF() {
try {
    FileOutputStream fos = new FileOutputStream("Mayan2.gif"); 
    img = (BufferedImage) scrollPane1.createImage(logicalWidth,logicalHeight); 
System.out.println("img: " + img);
    System.out.print("painting... ");
    g2 = (Graphics2D)img.getGraphics();
    g2.setStroke(new BasicStroke(2.0f+logicalHeight/500));
    paint(g2);
    System.out.println("done.");
    GifEncoder ge = new GifEncoder(img,fos); 
    System.out.print("encoding... ");
    ge.encode(); 

    fos.flush(); 
    fos.close(); 
    System.out.println("done.");
} catch (Exception e) {
    System.out.println("e: " + e);
    e.printStackTrace();
    
}

}
public void writeJPEG() {
try {
    FileOutputStream fos = new FileOutputStream("Mayan2.jpg"); 
    img = (BufferedImage) scrollPane1.createImage(logicalWidth,logicalHeight); 
    System.out.println("img: " + img);
    System.out.print("painting... ");
    g2 = (Graphics2D)img.getGraphics();
    g2.setStroke(new BasicStroke(2.0f+logicalHeight/500));
    paint(g2);
    System.out.println("done.");
    JPEGImageEncoder jpegImageEncoder = 
	JPEGCodec.createJPEGEncoder(fos); 
    JPEGEncodeParam  jpegEncodeParam = 
	jpegImageEncoder.getDefaultJPEGEncodeParam(img);
    jpegEncodeParam.setQuality(0.95f,true);
    System.out.print("encoding... ");
    jpegImageEncoder.encode(img, jpegEncodeParam);

    fos.flush(); 
    fos.close(); 
    System.out.println("done.");
} catch (Exception e) {
    System.out.println("e: " + e);
    e.printStackTrace();
    
}
}
    
  public static void main(String[] argv ){
   Mayan2 mayan2 = new Mayan2();
   gnu.getopt.Getopt g = new gnu.getopt.Getopt("Mayan2", argv, "W:H:gjh");
   int c;
   String arg;
   while ((c = g.getopt()) != -1)
       {
	   switch(c)
	       {
	       case 'h':
		   System.out.println("Mayan2 help: valid argument: \n   -H n --- the Height in pixels\n   -W n --- the Width in pixels\n   -g --- write image to gif File named Mayan2.gif (this must the the last argument)\n   -j --- write image to jpg File named Mayan2.jpg (this must the the last argument)\n   -h --- help\n\n   Example:  java Mayan2 -W 1000 -L 1000\n");
		   System.exit(0);
		   break;
	       case 'W':
		   mayan2.setLogicalWidth(Integer.parseInt(g.getOptarg()));
		   break;
	       case 'H':
		   mayan2.setLogicalHeight(Integer.parseInt(g.getOptarg()));
		   break;
	       case 'g':
		   mayan2.init();
		   mayan2.show();
		   mayan2.writeGIF();
		   System.exit(0);
		   break;
	       case 'j':
		   mayan2.init();
		   mayan2.show();
		   mayan2.writeJPEG();
		   System.exit(0);
		   break;
	       default:
		   System.out.print("getopt() returned " + c + "\n");
		   System.exit(1);
	       }
       }
    mayan2.init();
    mayan2.show();
    mayan2.paint(null);
 }

class Element {

    String name,fullname;
    int atomicNumber;
    int shell;
    double angle,angleStart,angleEnd,deltaAngle;
    double x,y;
    Mayan2 m;
    boolean noble = false;
    int state;

Element(Mayan2 m, String Name, String Fullname, int number)
{
    this.name = Name;
    this.fullname = Fullname;
    this.atomicNumber = number;
    this.m = m;
    shell = whichShell(atomicNumber); //in rads
    angle = whichAngle(atomicNumber);
    deltaAngle = (twoPI/(double)m.electronsInShell[shell]); //in rads
    angleStart = angle - deltaAngle/2.0; //in rads
    angleEnd = angle + deltaAngle/2.0; //in rads

    noble = (angle < -1.56 && angle > -1.58);
    state = getState();

    x = m.xcenter + Math.cos(angle)*(double)m.dr*((double)shell - 0.5);
    y = m.ycenter + Math.sin(angle)*(double)m.dr*((double)shell-0.5);
}
public int getState() {
    int state;
    if( noble || ((atomicNumber >= 7) && (atomicNumber < 11)) || atomicNumber == 1 || atomicNumber == 17) {
	state = Mayan2.GAS;
    } else if (atomicNumber == 80 || atomicNumber == 35 || atomicNumber == 31 || atomicNumber == 55 || atomicNumber == 87 ) {
	state = Mayan2.LIQUID;
    } else {
	state = Mayan2.SOLID;
    }

    return state;
}

    //synthetic 43,61,93-109
    //radioactive 43, 84-88, 61,89-109


void  draw(Graphics2D g2) {
    //draw the background color
    double x1=0, y1=0, x2, y2;
    int intervals = 10*(numShells - shell + 4);
    int radius1 = ((dr * (shell)) - lineWidth/2);
    int radius2 = ((dr * (shell-1)) + lineWidth/2);
    double deltatheta = (2.0*Math.PI)/((double)electronsInShell[shell]);
    double theta = angle - deltatheta/2.0;


    for(int k=0;k<intervals;k++) {
	theta += (deltatheta/((double)intervals));
	x1 = Math.cos(theta) * radius2 + xcenter;
	x2 = Math.cos(theta) * radius1 + xcenter;
	y1 = Math.sin(theta) * radius2 + ycenter;
	y2 = Math.sin(theta) * radius1 + ycenter;
	
	drawLine(m.colors[state],(int)x1,(int)y1,(int)x2,(int)y2);
    }

    //draw the spokes
    x1 = Math.cos(angleStart) * radius2 + m.xcenter;
    x2 = Math.cos(angleStart) * radius1 + m.xcenter;
    y1 = Math.sin(angleStart) * radius2 + m.ycenter;
    y2 = Math.sin(angleStart) * radius1 + m.ycenter;

    drawLine(spokeColor,(int)x1,(int)y1,(int)x2,(int)y2);

    if(atomicNumber == numElements || atomicNumber < 3) { // its the last one
	x1 = Math.cos(angleEnd) * radius2 + m.xcenter;
	x2 = Math.cos(angleEnd) * radius1 + m.xcenter;
	y1 = Math.sin(angleEnd) * radius2 + m.ycenter;
	y2 = Math.sin(angleEnd) * radius1 + m.ycenter;
	drawLine(spokeColor,(int)x1,(int)y1,(int)x2,(int)y2);
    }
    if(noble) /* its a noble gas */
	g2.setFont(m.nobleElementSymbolFont);
    else
	g2.setFont(m.elementSymbolFont);
    m.drawCenteredString(g2,elementColor,name+" "+atomicNumber,(int)(x+0.5),(int)(y+0.5));
    if(noble) /* its a noble gas */
	g2.setFont(m.nobleElementNameFont);
    else
	g2.setFont(m.elementNameFont);
    m.drawCenteredString(g2,elementColor,fullname,(int)(x+0.5),(int)(y+(dr/5)));

}

int whichShell (int atomicNumber) {
    for(int i=6;i>=0;i--)
	{
	    if(atomicNumber > m.CumulativeElectronsInShell[i])
		return(i+1);
	}
    return(-1);
}

double whichAngle (int atomic_number) {
    double angle=0.0;
    for(int i=6;i>=0;i--)
	{
	    if (atomic_number >= m.CumulativeElectronsInShell[i])
		{
		    angle =
			((double)(atomic_number - m.CumulativeElectronsInShell[i]))/
			(double)(m.CumulativeElectronsInShell[i+1]-m.CumulativeElectronsInShell[i]);
		    return(angle*twoPI - halfPI);
		}
	}
    return(0.0);
}



}
 class myThread extends Thread {
     Element element;
    public myThread(Element element)
    {
	this.element = element;
    }
    public void run()
    {
	System.out.println("drawing element: " + element.atomicNumber);
	element.draw(g2);
    }
 }

} // Mayan2
