/* 
 * LibGlade support for libglade for Java-Gnome
 *
 * The Java-Gnome bindings library is free software distributed under
 * the terms of the GNU Library General Public License version 2.
 */

package org.gnu.glade;

import java.io.BufferedReader;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;

/**
 * LibGladeStubs: a utility that reads a Glade definition file
 * and parses it for signal handler references.  It then generates
 * a Java file which loads the Glade file and defines stubs for
 * the signal handlers.
 *
 * @author Tom Ball
 */
public class LibGladeStubs {

	String gladeFile;
	String className;
	SignalDesc[] handlers;

	public static void execute(String path) {
		if (!(new File(path).exists()))
			error("cannot access " + path);

		new LibGladeStubs(path).generate();
	}

	public LibGladeStubs(String path) {
		gladeFile = path;
	}

	public void generate() {
		try {
			parseGladeFile();
			writeJavaFile();
		} catch (Exception e) {
			error(e.toString());
		}
	}

	private void writeJavaFile() throws Exception {
		String directory;
		String filename;
		int i = gladeFile.lastIndexOf(File.separatorChar);
		if (i > -1) {
			directory = gladeFile.substring(0, i);
			filename = gladeFile.substring(i + 1);
		} else {
			directory = ".";
			filename = gladeFile;
		}
		i = filename.indexOf(".glade");
		String basename = (i > -1) ? filename.substring(0, i) : filename;

		// Create capitalized class name and class filename.
		StringBuffer sb = new StringBuffer(basename);
		sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
		className = sb.toString();
		String javaFileName = directory + File.separatorChar + className + ".java";

		InputStream template = this.getClass().getResourceAsStream("GladeTemplate.j");
		BufferedReader in = new BufferedReader(new InputStreamReader(template));
		PrintWriter out = new PrintWriter(new FileWriter(javaFileName));
		String line;

		while ((line = in.readLine()) != null) {
			out.println(expandLine(line));
		}
		out.close();
	}

	private String stripDir(String path) {
		int i = path.lastIndexOf(File.separatorChar);
		return (i > -1) ? path.substring(i + 1) : path;
	}

	private String expandLine(String line) {
		String[][] macros = new String[][] { { "__GLADE_FILE__", stripDir(gladeFile)}, {
				"__CLASS__", className }
		};

		if (line.indexOf("__INSERT_STUBS__") != -1)
			return createStubs();

		for (int i = 0; i < 2; i++) {
			String m = macros[i][0];
			String n = macros[i][1];
			int idx = line.indexOf(m);
			if (idx >= 0) {
				// allow for multiple macros per line.
				String expandedOnce = line.substring(0, idx) + n + line.substring(idx + m.length());
				return expandLine(expandedOnce);
			}
		}
		return line;
	}

	private String createStubs() {
		CharArrayWriter caw = new CharArrayWriter(1024);
		PrintWriter pw = new PrintWriter(caw);
		for (int i = 0; i < handlers.length; i++) {
			SignalDesc sd = handlers[i];
			pw.println("\n    /**");
			pw.print("     * " + sd.handler);
			pw.println(" method to handle the \"" + sd.signal + "\" signal.");
			pw.println("     *");
			pw.println("     * @param source the widget that fired this signal");
			pw.print("     * @param target an object defined as the ");
			pw.println("target of this signal, or null.");
			pw.println("     */");
			pw.print("    public void " + sd.handler);
			if (sd.target != null)
				pw.println("(GtkEvent event, Object target) {");
			else
				pw.println("(GtkEvent event) {");
			pw.println("        // TODO: handler code here");
			pw.println("    }");
		}
		pw.close();
		return caw.toString();
	}

	private static void error(String message) {
		System.err.println(message);
		System.exit(1);
	}

	static class SignalDesc {
		String signal;
		String handler;
		String target;

		SignalDesc(String s, String h, String t) {
			signal = s;
			handler = h;
			target = t;
		}
	}

	/*
	 * The following routines comprise an *extremely* simple, brain-dead 
	 * parser of Glade 2.0 XML files.  It purposely only looks for signal 
	 * handler definitions so as to build a Java stubs file.  Don't 
	 * confuse it with a real XML parser, and don't use it for anything 
	 * but this class!
	 */

	private void parseGladeFile() throws IOException {
		handlers = getSignalHandlers(getXML());
	}

	private String getXML() throws IOException {
		BufferedReader in = new BufferedReader(new FileReader(gladeFile));
		StringWriter sw = new StringWriter(4096);
		PrintWriter out = new PrintWriter(sw);

		String line;
		while ((line = in.readLine()) != null) {
			if (line.indexOf("<GTK-Interface>") != -1)
				error(gladeFile + ": obsolete version (use libglade-convert)");
			out.println(line);
		}

		in.close();
		out.close();
		return sw.toString();
	}

	private static SignalDesc[] getSignalHandlers(String xml) throws GladeXMLException {
		List descs = new ArrayList();
		int idx = 0;
		while ((idx = xml.indexOf(signalStart, idx)) != -1) {
			int begin = idx + signalStart.length();
			int end = xml.indexOf(signalEnd1, idx);
			if (end != -1)
				idx = end + signalEnd1.length();
			else {
				end = xml.indexOf(signalEnd2, idx);
				if (end != -1)
					idx = end + signalEnd2.length();
				else
					throw new GladeXMLException("invalid XML signal definition");
			}
			descs.add(makeSignalDesc(xml.substring(begin, end)));
		}
		return (SignalDesc[])descs.toArray(new SignalDesc[0]);
	}

	private static SignalDesc makeSignalDesc(String props) throws GladeXMLException {
		String signal = null;
		String handler = null;
		String target = null;

		StringTokenizer st = new StringTokenizer(props, nameDelims);
		while (st.hasMoreTokens()) {
			String propName = st.nextToken(nameDelims);
			String propValue;
			try {
				propValue = st.nextToken(valueDelims);
				st.nextToken(nameDelims); // skip past trailing double-quote
			} catch (NoSuchElementException e) {
				throw new GladeXMLException("invalid XML signal definition");
			}
			if ("name".equals(propName))
				signal = propValue;
			else if ("handler".equals(propName))
				handler = propValue;
			else if ("object".equals(propName))
				target = propValue;
			else
				; // ignore other properties, such as last modified time
		}

		if (signal == null || handler == null)
			throw new GladeXMLException("invalid XML signal definition");

		return new SignalDesc(signal, handler, target);
	}

	private static String signalStart = "<signal ";
	private static String signalEnd1 = "/>";
	private static String signalEnd2 = "</signal>";
	private static String nameDelims = "= \t\n\r\f";
	private static String valueDelims = "=\"";
}
