/* BEGIN_HEADER                                              Java TreeView
 *
 * $Author: rqluk $
 * $RCSfile: PostscriptExportPanel.java,v $
 * $Revision: 1.1 $
 * $Date: 2006-08-16 19:13:45 $
 * $Name:  $
 *
 * This file is part of Java TreeView
 * Copyright (C) 2001-2014 Alok Saldanha, All Rights Reserved. Modifications Copyright (C) Lawrence Berkeley Lab.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * END_HEADER 
 */
package edu.stanford.genetics.treeview.plugin.dendroview;

import java.awt.Color;
import java.awt.Font;
import java.io.*;
import java.util.Stack;

import edu.stanford.genetics.treeview.*;
/**
*  Subclass of ExportPanel which outputs a postscript version of a DendroView.
*
*/
public class PostscriptExportPanel extends ExportPanel implements SettingsPanel {

  // I wish I could just inherit this...
    public PostscriptExportPanel(HeaderInfo arrayHeaderInfo, HeaderInfo geneHeaderInfo, 
		TreeSelectionI geneSelection, 
		TreeSelectionI arraySelection, 
		InvertedTreeDrawer arrayTreeDrawer, LeftTreeDrawer geneTreeDrawer, ArrayDrawer arrayDrawer, MapContainer arrayMap,MapContainer geneMap) {
	  super(arrayHeaderInfo, geneHeaderInfo, 
		geneSelection, 
		arraySelection, 
	  arrayTreeDrawer, geneTreeDrawer, 
	  arrayDrawer, arrayMap,
	  geneMap, false);
	}

  protected Font getGeneFont() {
	return new Font("Courier", 0, 8);
  }
  protected Font getArrayFont() {
	return new Font("Courier", 0, 8);
  }
	
	public void synchronizeTo() {
	  save();
	}
  
  public void synchronizeFrom() {
	// do nothing...
  }

  public void save() {
	try {
	  PrintStream output = new PrintStream(new BufferedOutputStream
	  (new FileOutputStream(getFile())));
	  
	  DendroPSWriter psw = new DendroPSWriter();
	  psw.write(output);
	  
	  
	  output.close();
	} catch (Exception e) {
	  LogBuffer.println("PostscriptExportPanel.save() caught exception " + e);
	  e.printStackTrace();
	}
  }
  
  
  
  /**
  *  Inner class which outputs a postscript version of Dendroview like things
  *
  *	It is loosely coupled in that it only calls protected methods in the ExporPanel superclass.
  */
  class DendroPSWriter {
	
	/** 
	* Writes out postscript header, much of it stolen directly from eisen.
	*/
	private void writeHeader(PrintStream ps) {
	  int totalWidth = estimateWidth();;
	  int totalHeight = estimateHeight(); 
	  
	  ps.println("%!PS-Adobe-3.0");
	  if (includeBbox()) {
		ps.println("%%BoundingBox: 0 0 " + totalWidth + " " + totalHeight);
	  }
	  ps.println("%%Creator: DendroPSWriter (a Java TreeView Component)");
	  ps.println("%%CreationDate: " + (new java.util.Date()).toString());
	  ps.println("%%Pages: (atend)");
	  ps.println("%%EndComments");
	  ps.println("%%BeginSetup");
	  
	  ps.println("/ln { newpath moveto lineto stroke closepath } bind def");
	  ps.println("/tx { newpath moveto show closepath } bind def");
	  ps.println("/sl { setlinewidth } def");
	  ps.println("/sc { setlinecap } def");
	  ps.println("/sr { setrgbcolor } def");
	  ps.println("/sf { exch findfont exch scalefont setfont } def");
	  ps.println("/tr { translate } def");
	  ps.println("/sp { 1 sc 1 sl 0.0 0.0 0.0 sr 18.00000 13.00000 tr 0.96000 0.98205 scale tr } def");
	  ps.println("/fb {exch dup 0 rlineto exch 0 exch rlineto neg 0 rlineto closepath fill } bind def");
	  // consLineTo duplicates the point on the stack, lineto and strokes, and then moves to it.
	  ps.println("/consLineTo {1 index 1 index lineto stroke moveto} bind def");
	  // the following expects rx, ry, tx, ly, lx as arguments, and draws a line connecting, for GTR
	  ps.println("/snGTR {1 index moveto 1 index exch consLineTo 1 index consLineTo lineto stroke} bind def");
	  ps.println("/snATR {1 index exch moveto 1 index consLineTo 1 index exch consLineTo exch lineto stroke } bind def");
	  
	  // old eisen fillbox: '/fillbox {newpath moveto 8 0 rlineto 0 8 rlineto -8 0 rlineto closepath fill} def
	  ps.println("%%EndSetup");
	  ps.println("%%Page: tree 1");
	  ps.println("%%PageResources: (atend)");
	  ps.println("%%BeginPageSetup");
	  ps.println("/pgsave save def");
	  ps.println("%%EndPageSetup");
	  
	  
	}
	
	
	/**
	* draws boxes using maps with the lower left corner at the current origin.
	*
	*/
	private void writeBoxes(PrintStream ps) {
	  int height = (int) getYmapHeight();
	  
	  if (includeGtr()) { // make room for Gtr...
		ps.println("% make room for gtrview");
		ps.println(getGtrWidth() + " 0 translate");		
	  }
	  
	  int yoff = getYmapPixel(minGene() - 0.5);
	  int xoff = -getXmapPixel(minArray() - 0.5);
	  ps.println("% account for offset into data matrix");
	  ps.println(xoff + " " + yoff + " translate");
	  // HACK doesn't account for discontinuous selection...
	  // for each row...
	  for (int i = minGene(); i <= maxGene(); i++) {
		int maxArray = maxArray(); // for efficiency...
		for (int j = minArray(); j <= maxArray; j++) {
		  Color color =  getArrayDrawer().getColor(j, i);
		  // setcolor
		  ps.println(convertColor(color)+ " sr");
		  // move to lower left corner...
		  int lx = getXmapPixel(j - 0.5);
		  int ly = getYmapPixel(i - 0.5);
		  int ux = getXmapPixel(j+0.5);
		  int uy = getYmapPixel(i+0.5);
		  
		  ps.println((lx) + " " + (height - uy) + " moveto");
		  // draw filled box
		  int w = ux - lx;
		  int h = uy - ly;
		  ps.println(w + " " + h + " fb");
		}
	  }
	  ps.println((-xoff) + " " + (-yoff) + " translate");
	  
	  if (includeGtr()) {
		ps.println((-getGtrWidth()) + " 0 translate");		
	  }
	}
		
	private void writeGeneNames(PrintStream ps) {
		// translate over
		if (getGeneAnnoLength() <= 0) return;
		if (includeArrayMap()) ps.println(getXmapWidth() + " 0 translate");
		if (includeGtr()) ps.println(getGtrWidth() + " 0 translate");		
		//	  if (includeAtr()) ps.println("0 " + getAtrHeight() + " translate");
		
		ps.println(" /Courier findfont");
		ps.println("8 scalefont");
		ps.println("setfont");
		
		int yoff = getYmapPixel(minGene() - 0.5);
		int xoff = 0;
		ps.println("% account for offset into data matrix");
		ps.println(xoff + " " + yoff + " translate");
		int height = (int)getYmapHeight();
		int maxGene = maxGene();

		for (int j = minGene(); j <= maxGene; j++) {
			Color bgColor = getGeneBgColor(j);
			if (bgColor != null) {
				int lx = 0;
				int ly = getYmapPixel(j - 0.5);
				int ux = getGeneAnnoLength();
				int uy = getYmapPixel(j+0.5);
				ps.println(convertColor(bgColor) + " sr");
//				ps.println("0 " + (height - uy) + " moveto");
				
				ps.println((lx) + " " + (height - uy) + " moveto");
				// draw filled box
				int w = ux - lx;
				int h = uy - ly;
				ps.println(w + " " + h + " fb");
			}
		}
		
		for (int j = minGene(); j <= maxGene; j++) {
			int uy = getYmapPixel(j+0.25);
			String out = getGeneAnno(j);
			Color fgColor = getGeneFgColor(j);
			if (out != null) {
				if (fgColor != null) {
					ps.println(convertColor(fgColor) + " sr");
				}
				ps.println("0 " + (height - uy) + " moveto");
				ps.println("( " + psEscape(out) + " ) show");
			}
		}
		ps.println((-xoff) + " " + (-yoff) + " translate");
		
		// translate back
		//	  if (includeAtr()) ps.println("0 " + - getAtrHeight() + " translate");
		if (includeGtr())      ps.println( - getGtrWidth() + " 0 translate");		
		if (includeArrayMap()) ps.println(- getXmapWidth() + " 0 translate");
	}
	
	private void writeArrayNames(PrintStream ps) {
	  if (getArrayAnnoLength() <= 0) return;
	  int tHeight = 0;
	  int tWidth  = 0;
	  if (includeGeneMap()) tHeight += getYmapHeight();
	  if (includeAtr() && (getArrayAnnoInside() == false)) {
		tHeight += getAtrHeight();
	  }
	  if (includeGtr()) tWidth += getGtrWidth();		

	  ps.println(tWidth + " " + tHeight + " translate");

	  int xoff = -getXmapPixel(minArray() - 0.5);
	  int yoff = 0;
	  ps.println("% account for offset into data matrix");
	  ps.println(xoff + " " + yoff + " translate");

	  ps.println("0 0 0 sr");
	  ps.println(" /Courier findfont");
	  ps.println("8 scalefont");
	  ps.println("setfont");
	  ps.println("90 rotate");
	  int max = maxArray();

		for (int j = minArray(); j <= max; j++) {
			Color bgColor = getArrayBgColor(j);
			if (bgColor != null) {
				int lx = 0;
				int ly = getXmapPixel(j - 0.5);
				int ux = getArrayAnnoLength();
				int uy = getXmapPixel(j+0.5);
				ps.println(convertColor(bgColor) + " sr");
//				ps.println("0 " + (-uy) + " moveto");
				
				ps.println((lx) + " " + (-uy) + " moveto");
				// draw filled box
				int w = ux - lx;
				int h = uy - ly;
				ps.println(w + " " + h + " fb");
			}
		}


	  for (int i = minArray(); i <= max; i++) {
		int ux = getXmapPixel(i+0.25);
		String out = getArrayAnno(i);
		Color color = getArrayFgColor(i);
		if (out != null) {
		  if (color != null) {
			ps.println(convertColor(color) + " sr");
		  }
		  ps.println("0 " + (-ux) + " moveto");
		  ps.println("( " + psEscape(out) + " ) show");
		}
		
	  }

	  ps.println("-90 rotate");

	  ps.println((-xoff) + " " + (-yoff) + " translate");
	  ps.println(-tWidth + " " + (-tHeight) + " translate");

	}
	private String psEscape(String inString) {
	  String convicts = "()"; // escape the convicts!!!
	  StringBuffer outString = new StringBuffer(inString.length());
	  for (int i =0 ; i < inString.length(); i++) {
		char thisChar = inString.charAt(i);
		if (convicts.indexOf(thisChar) >= 0) {
		  outString.append('\\');
		}
		outString.append(thisChar);
	  }
	  return outString.toString();
	}
	
	private String convertColor(Color c) {
	  // God Damn java 1.0!!!
	  //	float comp[] = new float [3];
	  //	c.getRGBColorComponents(comp);
	  return convertRGB(c.getRed()) + " " + 
	  convertRGB(c.getGreen()) + " " + 
	  convertRGB(c.getBlue());
	}
	private float convertRGB(int r) {
	  return ((float) r) / 255;
	}
	
	private void writeFooter(PrintStream ps) {
	  ps.println("showpage");
	}
	
	private double scaleGTR, corrGTR;
	private int offsetGTR = 5;
	private void writeGTR(PrintStream ps) {
	  if (includeGtr() == false) return;
	  corrGTR = getMinGeneCorr();
	  scaleGTR = (getGtrWidth() - offsetGTR) / (1.0 - corrGTR);

	  ps.println((offsetGTR/2) + " 0 translate");

	  int yoff = getYmapPixel(minGene() - 0.5);
	  int xoff = 0;
	  ps.println("% account for offset into data matrix");
	  ps.println(xoff + " " + yoff + " translate");
	  interateGTR(ps, getGeneNode());
		ps.println(convertColor(Color.black)+ " sr");
	  ps.println((-xoff) + " " + (-yoff) + " translate");

	  ps.println((-offsetGTR/2) + " 0 translate");

	}

	private double scaleATR, corrATR;
	private int offsetATR = 5;
	private void writeATR(PrintStream ps) {
	  if (includeAtr() == false) return;
	  corrATR = getMinArrayCorr();
	  scaleATR = (getAtrHeight() - offsetATR) / (1.0 - corrATR);
	  int widthOffset = 0;
	  int heightOffset = 0;
	  if (includeGtr()) widthOffset += getGtrWidth();
	  if (includeGeneMap()) heightOffset += getYmapHeight();
	  if (getArrayAnnoInside()) heightOffset += getArrayAnnoLength();

	  ps.println(widthOffset + " " + (heightOffset - offsetATR /2 ) + " translate");

	  int xoff = -getXmapPixel(minArray() - 0.5);
	  int yoff = 0;
	  ps.println("% account for offset into data matrix");
	  ps.println(xoff + " " + yoff + " translate");
	  recurseATR(ps, getArrayNode());
		ps.println(convertColor(Color.black)+ " sr");

	  ps.println((-xoff) + " " + (-yoff) + " translate");

	  ps.println((-widthOffset) + " " + (-heightOffset + offsetATR/2) + " translate");

	}

	private void interateGTR(PrintStream ps, TreeDrawerNode startNode) {
	  int height = (int)getYmapHeight();
	  Stack remaining = new Stack();
	  remaining.push(startNode);
	  while (remaining.empty() == false) {
	  	TreeDrawerNode node = (TreeDrawerNode) remaining.pop();
	  	TreeDrawerNode left = node.getLeft();
	  	TreeDrawerNode right = node.getRight();
	  	
	  	int rx = (int) (scaleGTR * (right.getCorr() - corrGTR));
	  	int lx = (int) (scaleGTR * (left.getCorr() - corrGTR));
	  	int tx = (int) (scaleGTR * (node.getCorr() - corrGTR));
	  	
	  	int ry = getYmapPixel(right.getIndex());
	  	int ly = getYmapPixel(left.getIndex());
	  	Color color =  node.getColor();
	  	// setcolor
	  	ps.println(convertColor(color)+ " sr");
	  	
	  	ps.println(rx + " " + (height - ry) + " " + tx + " " + (height - ly) + " " + lx + " snGTR");
	  	
	  	if (left.isLeaf() == false) remaining.push(left);
	  	if (right.isLeaf() == false) remaining.push(right);
	  }
	}
	
	private void recurseATR(PrintStream ps, TreeDrawerNode node) {
	  int height = (int)getAtrHeight();
	  
	  TreeDrawerNode left = node.getLeft();
	  TreeDrawerNode right = node.getRight();
	  
	  
	  int ry = (int) (scaleATR * (right.getCorr() - corrATR));
	  int ly = (int) (scaleATR * (left.getCorr() - corrATR));
	  int ty = (int) (scaleATR * (node.getCorr() - corrATR));
	  
	  int rx = getXmapPixel(right.getIndex());
	  int lx = getXmapPixel(left.getIndex());
		Color color =  node.getColor();
		// setcolor
		ps.println(convertColor(color)+ " sr");
	  ps.println((height - ry) + " " + rx + " " + (height - ty) + " " + lx + " " + (height - ly) + " snATR");
	  
	  if (left.isLeaf() == false) recurseATR(ps, left);
	  if (right.isLeaf() == false) recurseATR(ps, right);
	}
	
	public void write(PrintStream ps) {
	  
	  // calculateDimensions();
	  writeHeader(ps);
	  
	  //write gtr?
	  if (includeGtr()) {
		writeGTR(ps);
	  }
	  if (includeAtr()) {
		writeATR(ps);
	  }
	  
	  writeArrayNames(ps);
	  
	  writeGeneNames(ps);

	  if (includeData()) {
		writeBoxes(ps);
	  }
	  writeFooter(ps);
	  
	  if (ps.checkError()) {
		LogBuffer.println("Some error occured during PostScript export");
	  }
	}
  }
}
