/**
 * ***************************************************************
 * JADE - Java Agent DEvelopment Framework is a framework to develop
 * multi-agent systems in compliance with the FIPA specifications.
 * Copyright (C) 2000 CSELT S.p.A.
 * 
 * GNU Lesser General Public License
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation,
 * version 2.1 of the License.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 * **************************************************************
 */
package jade.content.frame;

import java.util.Vector;
import java.util.Enumeration;
import java.util.Date;
import java.io.*;

import jade.core.AID;
import jade.core.CaseInsensitiveString;
import jade.content.lang.sl.SL0Vocabulary;
import jade.content.lang.sl.SimpleSLTokenizer;
import jade.util.leap.Iterator;
import jade.lang.acl.ISO8601;

/**
   @author Giovanni Caire - TILAB
   @version $Date: 2010-04-08 15:54:18 +0200 (gio, 08 apr 2010) $ $Revision: 6298 $
 */
public class SLFrameCodec implements jade.util.leap.Serializable {
	public static final String NAME = "FIPA-SL";

	/**
     Transform a Frame into a String encoded according to the
     SL language
     @param content The Frame to be transformed
     @throws FrameException
	 */
	public String encode(Frame content) throws FrameException {
		if (content == null) {
			return null;
		}
		try {
			StringBuffer sb = new StringBuffer();
			write(sb, content);
			return sb.toString();
		} 
		catch (FrameException fe) {
			throw fe;
		}
		catch (Throwable t) {
			throw new FrameException("Error encoding content", t);
		}
	} 

	/**
     Transform a String encoded according to the SL 
     language into a Frame 
     @param content The String to be transformed.
     @throws FrameException
	 */
	public Frame decode(String content) throws FrameException {
		if (content == null || content.length() == 0) {
			return null;
		}
		try {
			return (Frame) read(new SimpleSLTokenizer(content));
		} 
		catch (FrameException fe) {
			throw fe;
		}
		catch (Throwable t) {
			throw new FrameException("Error decoding content", t);
		}
	} 

	/**
	 */
	private void write(StringBuffer sb, Object obj) throws Throwable {
		if (obj instanceof AID) {
			obj = aidToFrame((AID) obj);
		}

		if (obj instanceof QualifiedFrame) {
			writeQualified(sb, (QualifiedFrame) obj);
		}
		else if (obj instanceof OrderedFrame) {
			writeOrdered(sb, (OrderedFrame) obj);
		}
		else if (obj instanceof Date) {
			sb.append(ISO8601.toString((Date) obj));
		}
		else if (obj instanceof Integer || obj instanceof Long || obj instanceof Boolean) {
			sb.append(obj);
		}
		//#MIDP_EXCLUDE_BEGIN
		else if (obj instanceof Double) {
			sb.append(obj);
		}
		//#MIDP_EXCLUDE_END
		else if (obj instanceof byte[]) {
			byte[] b = (byte[]) obj;
			sb.append('#');
			sb.append(b.length);
			sb.append('"');
			// FIXME: Should we use Base64 encoding?
			sb.append(new String(b));
			sb.append('"');
		}
		else if (obj instanceof String) {
			String s = (String)obj;
			if (CaseInsensitiveString.equalsIgnoreCase("true",s) || CaseInsensitiveString.equalsIgnoreCase("false",s)) {
				s = '"'+s+'"'; // quote it to avoid confusion with the boolean primitives
			} else if (!SimpleSLTokenizer.isAWord(s)) {
				s = SimpleSLTokenizer.quoteString(s);
			}
			sb.append(s);
		}
		else {
			throw new FrameException("Can't encode "+obj+" of class "+obj.getClass().getName());
		}
	}

	private void writeQualified(StringBuffer sb, QualifiedFrame qf) throws Throwable {
		sb.append('(');
		sb.append(qf.getTypeName());
		Enumeration e = qf.keys();
		while (e.hasMoreElements()) {
			String key = (String) e.nextElement();
			sb.append(" :");
			sb.append(key);
			sb.append(' ');
			write(sb, qf.get(key));
		}
		sb.append(')');
	}

	private void writeOrdered(StringBuffer sb, OrderedFrame of) throws Throwable {
		sb.append('(');
		sb.append(of.getTypeName());
		Enumeration e = of.elements();
		while (e.hasMoreElements()) {
			sb.append(' ');
			write(sb, e.nextElement());
		}
		sb.append(')');
	}


	/**
	 */
	private Object read(SimpleSLTokenizer st) throws Throwable {
		if (st.nextToken().equals("(")) {
			st.consumeChar('(');
			String typeName = st.getElement();
			if (st.nextToken().startsWith(":")) {
				QualifiedFrame qf = new QualifiedFrame(typeName);
				fillQualified(st, qf);
				// If this QualifiedFrame represents an AID, convert it
				if (qf.getTypeName().equals(SL0Vocabulary.AID)) {
					return frameToAid(qf);
				}
				else {
					return qf;
				}
			}
			else {
				OrderedFrame of = new OrderedFrame(typeName);
				fillOrdered(st, of);
				return of;
			}
		}
		else {
			String val = st.getElement();
			// Long
			try {
				return new Long(Long.parseLong(val));
			}
			catch (Exception e) {
			}
			//#MIDP_EXCLUDE_BEGIN
			// Float
			try {
				// Note that Double.parseDouble() does not exist in PJava
				return Double.valueOf(val);
			}
			catch (Exception e) {
			}
			//#MIDP_EXCLUDE_END
			// Date
			try {
				return ISO8601.toDate(val);
			}
			catch (Exception e) {
			}
			// Boolean
			if (val.equals("true")) {
				return new Boolean(true);
			}
			if (val.equals("false")) {
				return new Boolean(false);
			}
			// String
			return val;
		}		
	} 

	private void fillQualified(SimpleSLTokenizer st, QualifiedFrame qf) throws Throwable {
		while (!st.nextToken().equals(")")) {
			String key = st.getElement();
			Object val = read(st);
			qf.put(key, val);
		}
		st.consumeChar(')');
	}

	private void fillOrdered(SimpleSLTokenizer st, OrderedFrame of) throws Throwable {
		while (!st.nextToken().equals(")")) {
			of.addElement(read(st));
		}
		st.consumeChar(')');
	}

	private final QualifiedFrame aidToFrame(AID id) {
		QualifiedFrame f = new QualifiedFrame(SL0Vocabulary.AID);
		// Name
		f.put(SL0Vocabulary.AID_NAME, id.getName());

		// Addresses
		Iterator i = id.getAllAddresses();
		if (i.hasNext()) {
			OrderedFrame addresses = new OrderedFrame(SL0Vocabulary.SEQUENCE);
			while (i.hasNext()) {
				addresses.addElement(i.next());
			}
			f.put(SL0Vocabulary.AID_ADDRESSES, addresses);
		}
		// Resolvers
		i = id.getAllResolvers();
		if (i.hasNext()) {
			OrderedFrame resolvers = new OrderedFrame(SL0Vocabulary.SEQUENCE);
			while (i.hasNext()) {
				AID res = (AID) i.next();
				resolvers.addElement(aidToFrame(res));
			}
			f.put(SL0Vocabulary.AID_RESOLVERS, resolvers);
		}
		return f;
	}

	private final AID frameToAid(QualifiedFrame f) {
		// Name
		AID id = new AID((String) f.get(SL0Vocabulary.AID_NAME), AID.ISGUID);

		// Addresses
		OrderedFrame addresses = (OrderedFrame) f.get(SL0Vocabulary.AID_ADDRESSES);
		if (addresses != null) {
			for (int i = 0; i < addresses.size(); ++i) {
				id.addAddresses((String) addresses.elementAt(i));
			}
		}
		// Resolvers
		OrderedFrame resolvers = (OrderedFrame) f.get(SL0Vocabulary.AID_RESOLVERS);
		if (resolvers != null) {
			for (int i = 0; i < resolvers.size(); ++i) {
				AID res = frameToAid((QualifiedFrame) resolvers.elementAt(i));
				id.addResolvers(res);
			}
		}
		return id;
	}
}

