/*
 VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases.
 Copyright (C) 2008  Kevin Darty, Alain Denise and Yann Ponty.
 electronic mail : Yann.Ponty@lri.fr
 paper mail : LRI, bat 490 Universit� Paris-Sud 91405 Orsay Cedex France

 This file is part of VARNA version 3.1.
 VARNA version 3.1 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.

 VARNA version 3.1 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 VARNA version 3.1.
 If not, see http://www.gnu.org/licenses.
 */
package fr.orsay.lri.varna.applications;


import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.Vector;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.FileImageOutputStream;
import javax.swing.JFrame;

import fr.orsay.lri.varna.VARNAPanel;
import fr.orsay.lri.varna.exceptions.ExceptionExportFailed;
import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
import fr.orsay.lri.varna.exceptions.ExceptionJPEGEncoding;
import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
import fr.orsay.lri.varna.exceptions.ExceptionModeleStyleBaseSyntaxError;
import fr.orsay.lri.varna.exceptions.ExceptionNonEqualLength;
import fr.orsay.lri.varna.exceptions.ExceptionParameterError;
import fr.orsay.lri.varna.exceptions.ExceptionPermissionDenied;
import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
import fr.orsay.lri.varna.exceptions.ExceptionWritingForbidden;
import fr.orsay.lri.varna.factories.RNAFactory;
import fr.orsay.lri.varna.interfaces.InterfaceParameterLoader;
import fr.orsay.lri.varna.models.FullBackup;
import fr.orsay.lri.varna.models.VARNAConfig;
import fr.orsay.lri.varna.models.VARNAConfigLoader;
import fr.orsay.lri.varna.models.rna.RNA;

public class VARNAcmd implements InterfaceParameterLoader {
	
	public class ExitCode extends Exception{
		/**
		 * 
		 */
		private static final long serialVersionUID = -3011196062868355584L;
		private int _c;
		private String _msg;
		public ExitCode(int c,String msg){
			_c = c;
			_msg = msg;
		}
		public int getExitCode(){
			return _c;
		}
		public String getExitMessage(){
			return _msg;
		}
	}
	
	
	private Hashtable<String, String> _optsValues = new Hashtable<String, String>();
	private Hashtable<String, String> _basicOptsInv = new Hashtable<String, String>();
	private String _inFile = "";
	private String _outFile = "";
	int _baseWidth = 400;
	double _scale = 1.0;
	float _quality = 0.9f;

	private String[] _basicOptions = { VARNAConfigLoader.algoOpt,
			VARNAConfigLoader.bpStyleOpt, VARNAConfigLoader.bondColorOpt,
			VARNAConfigLoader.backboneColorOpt, VARNAConfigLoader.periodNumOpt,
			VARNAConfigLoader.baseInnerColorOpt,
			VARNAConfigLoader.baseOutlineColorOpt,

	};

	public VARNAcmd(Vector<String> args) throws ExitCode {
		for (int j = 0; j < _basicOptions.length; j++) {
			_basicOptsInv.put(_basicOptions[j], _basicOptions[j]);
		}
		int i = 0;
		while (i < args.size()) {
			String opt = args.elementAt(i);
			if (opt.charAt(0) != '-') {
				errorExit("Missing or unknown option \"" + opt + "\"");
			}
			if (opt.equals("-h")) {
				displayLightHelpExit();
			}
			if (opt.equals("-x")) {
				displayDetailledHelpExit();
			} else {
				if (i + 1 >= args.size()) {
					errorExit("Missing argument for option \"" + opt + "\"");
				}
				String val = args.get(i + 1);
				if (opt.equals("-i")) {
					_inFile = val;
				} else if (opt.equals("-o")) {
					_outFile = val;
				} else if (opt.equals("-quality")) {
					_quality = Float.parseFloat(val);
				} else if (opt.equals("-resolution")) {
					_scale = Float.parseFloat(val);
				} else {
					addOption(opt, val);
				}
			}
			i += 2;
		}
	}

	public void addOption(String key, String value) {
		if (key.equals("-i")) {
			_inFile = value;
		} else if (key.equals("-o")) {
			_outFile = value;
		} else {
			_optsValues.put(key.substring(1), value);
		}
	}

	private String getDescription() {
		return "VARNA v"
				+ VARNAConfig.MAJOR_VERSION
				+ "."
				+ VARNAConfig.MINOR_VERSION
				+ " Assisted drawing of RNA secondary structure (Command Line version)";
	}

	private String indent(int k) {
		String result = "";
		for (int i = 0; i < k; i++) {
			result += "  ";
		}
		return result;
	}

	private String complete(String s, int k) {
		String result = s;
		while (result.length() < k) {
			result += " ";
		}
		return result;
	}

	Vector<String[]> matrix = new Vector<String[]>();

	private void addLine(String opt, String val) {
		String[] line = { opt, val };
		matrix.add(line);
	}

	private static int MAX_WIDTH = 100;

	@SuppressWarnings("unchecked")
	private void printMatrix(int ind) {
		String[][] values = new String[matrix.size()][];
		matrix.toArray(values);
		Arrays.sort(values, new Comparator() {
			public int compare(Object o1, Object o2) {
				String[] tab1 = (String[]) o1;
				String[] tab2 = (String[]) o2;
				return tab1[0].compareTo(tab2[0]);
			}
		});

		int maxSize = 0;
		for (int i = 0; i < values.length; i++) {
			String[] elem = values[i];
			maxSize = Math.max(maxSize, elem[0].length());
		}
		maxSize += ind + 2;
		for (int i = 0; i < values.length; i++) {
			String[] elem = values[i];
			String opt = elem[0];
			String msg = elem[1];
			opt = complete("", ind) + "-" + complete(opt, maxSize - ind);
			System.out.println(opt	+ msg.substring(0, Math.min(MAX_WIDTH - opt.length(), msg.length())));
			if (opt.length() + msg.length() >= MAX_WIDTH) {
				int off = MAX_WIDTH - opt.length();
				while (off < msg.length()) {
					String nmsg = msg.substring(off, Math.min(off + MAX_WIDTH
							- opt.length(), msg.length()));
					System.out.println(complete("", opt.length())+nmsg);
					off += MAX_WIDTH - opt.length();
				}
			}
		} 
		matrix = new Vector<String[]>();
	}

	private void printUsage() {
		System.out
				.println("Usage: java -cp . [-i InFile|-sequenceDBN XXX -structureDBN YYY] -o OutFile [Options]");
		System.out.println("Where:");
		System.out.println(indent(1)
				+ "OutFile\tSupported formats: {JPEG,PNG,EPS,XFIG,SVG}");
		System.out
				.println(indent(1)
						+ "InFile\tSecondary structure file: Supported formats: {BPSEQ,CT,RNAML,DBN}");

	}

	private void printHelpOptions() {
		System.out.println("\nMain options:");
		addLine("h", "Displays a short description of main options and exits");
		addLine("x", "Displays a detailled description of all options");
		printMatrix(2);
	}

	private void printMainOptions(String[][] info) {
		System.out.println("\nMain options:");
		addLine("h", "Displays a short description of main options and exits");
		addLine("x", "Displays a detailled description of all options");
		for (int i = 0; i < info.length; i++) {
			String key = info[i][0];
			if (_basicOptsInv.containsKey(key)) {
				addLine(key, info[i][2]);
			}
		}
		printMatrix(2);
	}

	private void printAdvancedOptions(String[][] info) {
		System.out.println("\nAdvanced options:");
		for (int i = 0; i < info.length; i++) {
			String key = info[i][0];
			if (!_basicOptsInv.containsKey(key)) {
				addLine(key, info[i][2]);
			}
		}
		addLine("quality", "Sets quality (non-vector file formats only)");
		addLine("resolution", "Sets resolution (non-vector file formats only)");
		printMatrix(2);
	}

	private void displayLightHelpExit() throws ExitCode {
		String[][] info = VARNAConfigLoader.getParameterInfo();
		System.out.println(getDescription());
		printUsage();
		printMainOptions(info);
		throw(new ExitCode(1,""));
	}

	private void displayDetailledHelpExit() throws ExitCode {
		String[][] info = VARNAConfigLoader.getParameterInfo();
		System.out.println(getDescription());
		printUsage();
		printMainOptions(info);
		printAdvancedOptions(info);
		throw(new ExitCode(1,""));
	}

	private void errorExit(String msg) throws ExitCode {
		System.out.println(getDescription());
		System.out.println("Error: " + msg + "\n");
		printUsage();
		printHelpOptions();
		throw(new ExitCode(1,""));
	}

	public String getParameterValue(String key, String def) {
		if (_optsValues.containsKey(key)) {
			return _optsValues.get(key);
		}
		return def;
	}

	public String formatOutputPath(String base,int index, int total)
	{
		String result = base;
		
		if (total>1)
		{
			int indexDot = base.lastIndexOf('.');
			String pref;
			String ext;
			if (indexDot!=-1)
			{
			  pref = base.substring(0,indexDot);
			  ext = base.substring(indexDot);
			}
			else{
				pref=base;
				ext="";
			}
			result = pref+"-"+index+ext;
		}
		System.err.println("Output file: "+result);
		return result;
	}
	
	public void run() throws IOException, ExitCode {
		VARNAConfigLoader VARNAcfg = new VARNAConfigLoader(this);
		ArrayList<VARNAPanel> vpl;
		ArrayList<FullBackup> confs = new ArrayList<FullBackup>();
		try {
			if (!_inFile.equals("")) {
				if (!_inFile.toLowerCase().endsWith(".varna")) {
					Collection<RNA> rnas = RNAFactory.loadSecStr(_inFile);
					 if (rnas.isEmpty())
					 {
						 FullBackup f = null;
								try{
								f = VARNAPanel.importSession(new FileInputStream(_inFile), _inFile);
								confs.add(f);
								}
								catch(Exception e)
								{
									e.printStackTrace();
								}
						if (f==null)
						{
						 throw new ExceptionFileFormatOrSyntax("No RNA could be parsed from file '"+_inFile+"'.");
						}
					 }
					 else{
						 for (RNA r: rnas)
						 {
							 confs.add(new FullBackup(r,_inFile));
						 }
					 }
				}
				else{
					confs.add(VARNAPanel.importSession(_inFile));
				}
			} else {
				RNA r = new RNA();
				r.setRNA(this.getParameterValue("sequenceDBN",
						""), this.getParameterValue(
						"structureDBN", ""));
				confs.add(new FullBackup(r,"From Params"));
			}
			if (!_outFile.equals(""))
			{
			int index = 1;
			for (FullBackup r: confs)
			{
				VARNAcfg.setRNA(r.rna);
				vpl = VARNAcfg.createVARNAPanels();
				if (vpl.size() > 0) {
				VARNAPanel _vp = vpl.get(0);
				if (r.hasConfig())
				{
					_vp.setConfig(r.config);
				}
				RNA _rna = _vp.getRNA();
				Rectangle2D.Double bbox = _vp.getRNA().getBBox();
				//System.out.println(_vp.getRNA().getBBox());
				
				if (_outFile.toLowerCase().endsWith(".jpeg")
						|| _outFile.toLowerCase().endsWith(".jpg")
						|| _outFile.toLowerCase().endsWith(".png"))
				{ 
					_vp.setTitleFontSize((int)(_scale*_vp.getTitleFont().getSize())); 
				    _vp.setSize((int)(_baseWidth*_scale), (int)((_scale*_baseWidth*bbox.height)/((double)bbox.width)));
				}
				
				if (_outFile.toLowerCase().endsWith(".eps")) {
					_rna.saveRNAEPS(formatOutputPath(_outFile,index, confs.size()), _vp.getConfig());
				} else if (_outFile.toLowerCase().endsWith(".xfig")
						|| _outFile.toLowerCase().endsWith(".fig")) {
					_rna.saveRNAXFIG(formatOutputPath(_outFile,index, confs.size()), _vp.getConfig());
				} else if (_outFile.toLowerCase().endsWith(".svg")) {
					_rna.saveRNASVG(formatOutputPath(_outFile,index, confs.size()), _vp.getConfig());
				} else if (_outFile.toLowerCase().endsWith(".jpeg")
						|| _outFile.toLowerCase().endsWith(".jpg")) {
					this.saveToJPEG(formatOutputPath(_outFile,index, confs.size()), _vp);
				} else if (_outFile.toLowerCase().endsWith(".png")) {
					this.saveToPNG(formatOutputPath(_outFile,index, confs.size()), _vp);
				} else if (_outFile.toLowerCase().endsWith(".varna")) {
					_vp.saveSession(formatOutputPath(_outFile,index, confs.size()));
				} else {
					errorExit("Unknown extension for output file \"" + _outFile
							+ "\"");
				}
			}
			index++;
			}
			}
			// No output file => Open GUI
			else
			{
				VARNAGUI d = new VARNAGUI();
				d.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				d.pack();
				d.setVisible(true);
				for (FullBackup b: confs)
				{
					RNA r = b.rna;
					VARNAcfg.setRNA(r);
					vpl = VARNAcfg.createVARNAPanels();
					if (vpl.size() > 0) {
						VARNAPanel _vp = vpl.get(0);
						VARNAConfig cfg = _vp.getConfig();
						if (b.hasConfig())
						{
							cfg = b.config;
						}
						RNA rna = _vp.getRNA();
						d.addRNA(rna, cfg);
						
					}
				}
			}
		} catch (ExceptionWritingForbidden e) {
			e.printStackTrace();
			throw(new ExitCode(1,""));
		} catch (ExceptionJPEGEncoding e) {
			e.printStackTrace();
			throw(new ExitCode(1,""));
		} catch (ExceptionParameterError e) {
			e.printStackTrace();
			throw(new ExitCode(1,""));
		} catch (ExceptionModeleStyleBaseSyntaxError e) {
			e.printStackTrace();
			throw(new ExitCode(1,""));
		} catch (ExceptionNonEqualLength e) {
			e.printStackTrace();
			throw(new ExitCode(1,""));
		} catch (ExceptionUnmatchedClosingParentheses e) {
			e.printStackTrace();
			System.exit(1);
		} catch (ExceptionExportFailed e) {
			e.printStackTrace();
			throw(new ExitCode(1,""));
		} catch (ExceptionPermissionDenied e) {
			e.printStackTrace();
			throw(new ExitCode(1,""));
		} catch (ExceptionLoadingFailed e) {
			e.printStackTrace();
			throw(new ExitCode(1,""));
		} catch (ExceptionFileFormatOrSyntax e) {
			e.setPath(_inFile);
			e.printStackTrace();
			throw(new ExitCode(1,""));
		} catch (FileNotFoundException e) {
			throw(new ExitCode(1,"Error: Missing input file \""+_inFile+"\"."));
		}
		
		if (!_outFile.equals(""))
			throw(new ExitCode(0,""));
		
		
	
	}

	public void saveToJPEG(String filename, VARNAPanel vp)
			throws ExceptionJPEGEncoding, ExceptionExportFailed {
		
		BufferedImage myImage = new BufferedImage((int) Math.round(vp
				.getWidth()
				), (int) Math.round(vp.getHeight() ),
				BufferedImage.TYPE_INT_RGB);
		Graphics2D g2 = myImage.createGraphics();
		vp.paintComponent(g2);
		try {
			FileImageOutputStream out = new FileImageOutputStream(new File(filename));
			ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
			ImageWriteParam params = writer.getDefaultWriteParam();
			params.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
			params.setCompressionQuality(_quality);
			writer.setOutput(out);
			IIOImage myIIOImage = new IIOImage(myImage, null, null);
			writer.write(null, myIIOImage, params);
			out.close();
		} catch (IOException e) {
			throw new ExceptionExportFailed(e.getMessage(), filename);
		}

	}

	public void saveToPNG(String filename, VARNAPanel vp)
			throws ExceptionExportFailed {
		BufferedImage myImage = new BufferedImage((int) Math.round(vp
				.getWidth()), (int) Math.round(vp.getHeight() ),
				BufferedImage.TYPE_INT_RGB);
		Graphics2D g2 = myImage.createGraphics();
		vp.paintComponent(g2);
		g2.dispose();
		try {
			ImageIO.write(myImage, "PNG", new File(filename));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] argv) {
		Vector<String> opts = new Vector<String>();
		for (int i = 0; i < argv.length; i++) {
			opts.add(argv[i]);
		}
		try {
			VARNAcmd app = new VARNAcmd(opts);
			app.run();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ExitCode e) {
			System.err.println(e.getExitMessage());
			System.exit(e.getExitCode());
		}
	}

}
