/*
 * Copyright (c) 2006-2008 Maskat Project.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Maskat Project - initial API and implementation
 */
package org.maskat.ui.views.properties.tabbed.action;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

import org.eclipse.swt.dnd.ByteArrayTransfer;
import org.eclipse.swt.dnd.TransferData;

import org.maskat.core.MaskatElement;
import org.maskat.core.event.Bind;
import org.maskat.core.event.Header;
import org.maskat.core.event.Source;
import org.maskat.core.event.Target;
import org.maskat.ui.views.properties.tabbed.beanwrapper.HeaderTreeNode;
import org.maskat.ui.views.properties.tabbed.beanwrapper.SourceBind;
import org.maskat.ui.views.properties.tabbed.beanwrapper.SourceChildNode;
import org.maskat.ui.views.properties.tabbed.beanwrapper.SourceNode;
import org.maskat.ui.views.properties.tabbed.beanwrapper.TargetBind;
import org.maskat.ui.views.properties.tabbed.beanwrapper.TargetChildNode;
import org.maskat.ui.views.properties.tabbed.beanwrapper.TargetNode;
import org.maskat.ui.views.properties.tabbed.beanwrapper.Validation;
import org.maskat.ui.views.properties.tabbed.treenodewrapper.ITreeNode;


/**
 * Class for serializing treenode to/from a byte array
 */
public class TreeNodeTransfer extends ByteArrayTransfer {

	private static TreeNodeTransfer instance = new TreeNodeTransfer();

	private static final String TYPE_NAME = "org.maskat.treenode";

	private static final int TYPEID = registerType(TYPE_NAME);

	/**
	 * Returns the singleton gadget transfer instance.
	 */
	public static TreeNodeTransfer getInstance() {
		return instance;
	}

	/**
	 * Avoid explicit instantiation
	 */
	private TreeNodeTransfer() {
	}

	protected ITreeNode[] fromByteArray(byte[] bytes) {
		DataInputStream in = new DataInputStream(new ByteArrayInputStream(bytes));

		try {
			// read number of nodes
			int n = in.readInt();
			// read nodes
			ITreeNode[] nodes = new ITreeNode[n];
			for (int i = 0; i < n; i++) {
				ITreeNode node = readTreeNode(null, in);
				if (node == null) {
					return null;
				}
				nodes[i] = node;
			}
			return nodes;
		} catch (IOException e) {
			return null;
		}
	}

	/*
	 * Method declared on Transfer.
	 */
	protected int[] getTypeIds() {
		return new int[] { TYPEID };
	}

	/*
	 * Method declared on Transfer.
	 */
	protected String[] getTypeNames() {
		return new String[] { TYPE_NAME };
	}

	/*
	 * Method declared on Transfer.
	 */
	protected void javaToNative(Object object, TransferData transferData) {
		byte[] bytes = toByteArray((ITreeNode[]) object);
		if (bytes != null)
			super.javaToNative(bytes, transferData);
	}

	/*
	 * Method declared on Transfer.
	 */
	protected Object nativeToJava(TransferData transferData) {
		try {
			byte[] bytes = (byte[]) super.nativeToJava(transferData);
			return fromByteArray(bytes);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Reads and returns a single treenode from the given stream.
	 */
	private ITreeNode readTreeNode(ITreeNode parent, DataInputStream dataIn) throws IOException {
		/**
		 * treenode serialization format is as follows: 
		 * ... repeat for each child
		 */
		String treeNodeClassName = dataIn.readUTF();
		ITreeNode child = getTreeNodeInstance(treeNodeClassName);
		if (child == null) {
			return null;
		}
		//set the global flag
		if (child instanceof HeaderTreeNode) {
			child = new HeaderTreeNode(null,dataIn.readBoolean());
		}

		MaskatElement model = readModelInfo(dataIn);
		if (model == null) {
			return null;
		}
		child.setModel(model);
		
		if (parent != null) {
			parent.addChild(child);
			//SourceChildTreeNode, SourceValidationTreeNode and TargetChildTreeNode 
			//models are the same as their parents, except the child is SourceBind.
			if (parent instanceof SourceNode || parent instanceof TargetNode) {
				if (!(child instanceof SourceBind)) {
					child.setModel(parent.getModel());
				}
			}
			//set the model relation
			if (child instanceof Header || child instanceof Bind) {
				parent.getModel().addChild(child.getModel());
			}
		}
		
		int n = dataIn.readInt();
		for (int i = 0; i < n; i++) {
			readTreeNode(child, dataIn);
		}
		return child;
	}

	protected byte[] toByteArray(ITreeNode[] treeNodes) {
		/**
		 * Transfer data is an array of treenodes. Serialized version is: (int)
		 * number of treenodes (ITreeNode) treenode 1 (ITreeNode) treenode 2 ... repeat for
		 * each subsequent treenode .
		 */
		ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
		DataOutputStream out = new DataOutputStream(byteOut);

		byte[] bytes = null;

		try {
			/* write number of markers */
			out.writeInt(treeNodes.length);

			/* write markers */
			for (int i = 0; i < treeNodes.length; i++) {
				writeTreeNode(treeNodes[i], out);
			}
			out.close();
			bytes = byteOut.toByteArray();
		} catch (IOException e) {
			// when in doubt send nothing
		}
		return bytes;
	}

	/**
	 * Writes the given treenode to the stream.
	 */
	private void writeTreeNode(ITreeNode node, DataOutputStream dataOut) throws IOException {
		/**
		 * treenode serialization format is as follows: 
		 * ... repeat for each child
		 */
		dataOut.writeUTF(node.getClass().getName());
		//write the global flag
		if (node instanceof HeaderTreeNode) {
			dataOut.writeBoolean(((HeaderTreeNode)node).isGlobal());
		}
		
		writeModelInfo(node.getModel(), dataOut);
		Object[] children = node.getChildren();
		dataOut.writeInt(children.length);
		for (int i = 0; i < children.length; i++) {
			writeTreeNode((ITreeNode)children[i], dataOut);
		}
	}
	
	private ITreeNode getTreeNodeInstance(String className) {
		try {
			Class treeNodeClass = Class.forName(className);
//			return (ITreeNode) treeNodeClass.newInstance();
			if (treeNodeClass.equals(HeaderTreeNode.class)) {
				return new HeaderTreeNode(null,false);
			} else if (treeNodeClass.equals(SourceBind.class)) {
				return new SourceBind(null);
			} else if (treeNodeClass.equals(TargetBind.class)) {
				return new TargetBind(null);
			} else if (treeNodeClass.equals(SourceNode.class)) {
				return new SourceNode(null);
			} else if (treeNodeClass.equals(Validation.class)) {
				return new Validation(null);
			} else if (treeNodeClass.equals(SourceChildNode.class)) {
				return new SourceChildNode(null);
			} else if (treeNodeClass.equals(TargetNode.class)) {
				return new TargetNode(null);
			} else if (treeNodeClass.equals(TargetChildNode.class)) {
				return new TargetChildNode(null);
			} 
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	private MaskatElement readModelInfo(DataInputStream dataIn) throws IOException {
		String modelClassName = dataIn.readUTF();
		if (modelClassName.equals(Bind.class.getName())) {
			Bind bind = new Bind();
			bind.setNode(dataIn.readUTF());
			bind.setFromkey(dataIn.readUTF());
			bind.setTokey(dataIn.readUTF());
			return bind;
		}
		
		if (modelClassName.equals(Header.class.getName())) {
			Header header = new Header();
			header.setName(dataIn.readUTF());
			header.setValue(dataIn.readUTF());
			return header;
		}
		
		if (modelClassName.equals(Source.class.getName())) {
			Source source = new Source();
			source.setFromkey(dataIn.readInt());
			source.setMax(dataIn.readInt());
			source.setMin(dataIn.readInt());
			String childNode = dataIn.readUTF();
			childNode = "".equals(childNode) ? null : childNode;
			source.setChildNode(childNode);
			source.setDesc(dataIn.readUTF());
			source.setIdxRef(dataIn.readUTF());
			source.setNode(dataIn.readUTF());
			source.setObj(dataIn.readUTF());
			source.setRegexp(dataIn.readUTF());
			source.setTeleType(dataIn.readUTF());
			source.setType(dataIn.readUTF());
			source.setSendBlankElement(dataIn.readBoolean());
			return source;
		}
		
		if (modelClassName.equals(Target.class.getName())) {
			Target target = new Target();
			target.setIn(dataIn.readUTF());
			String inkey = dataIn.readUTF();
			inkey = "".equals(inkey) ? null : inkey;
			target.setInkey(inkey);
			target.setOut(dataIn.readUTF());
			target.setTeleType(dataIn.readUTF());
			target.setType(dataIn.readUTF());
			target.setWorkType(dataIn.readUTF());
			return target;
		}
		
		return null;
	}
	
	private void writeModelInfo(MaskatElement bean, DataOutputStream dataOut) throws IOException {
		dataOut.writeUTF(bean.getClass().getName());

		if (bean instanceof Bind) {
			Bind bind = (Bind)bean;
			dataOut.writeUTF(nullString(bind.getNode()));
			dataOut.writeUTF(nullString(bind.getFromkey()));
			dataOut.writeUTF(nullString(bind.getTokey()));
			return;
		}
		
		if (bean instanceof Header) {
			Header header = (Header)bean;
			dataOut.writeUTF(nullString(header.getName()));
			dataOut.writeUTF(nullString(header.getValue()));
		}
		
		if (bean instanceof Source) {
			Source source = (Source)bean;
			dataOut.writeInt(source.getFromkey());
			dataOut.writeInt(source.getMax());
			dataOut.writeInt(source.getMin());
			dataOut.writeUTF(nullString(source.getChildNode()));
			dataOut.writeUTF(nullString(source.getDesc()));
			dataOut.writeUTF(nullString(source.getIdxRef()));
			dataOut.writeUTF(nullString(source.getNode()));
			dataOut.writeUTF(nullString(source.getObj()));
			dataOut.writeUTF(nullString(source.getRegexp()));
			dataOut.writeUTF(nullString(source.getTeleType()));
			dataOut.writeUTF(nullString(source.getType()));
			dataOut.writeBoolean(source.isSendBlankElement());
			return;
		}
		
		if (bean instanceof Target) {
			Target target = (Target)bean;
			dataOut.writeUTF(nullString(target.getIn()));
			dataOut.writeUTF(nullString(target.getInkey()));
			dataOut.writeUTF(nullString(target.getOut()));
			dataOut.writeUTF(nullString(target.getTeleType()));
			dataOut.writeUTF(nullString(target.getType()));
			dataOut.writeUTF(nullString(target.getWorkType()));
			return;
		}
	}
	
	private String nullString(String in) {
		return in == null ? "" : in;
	}
}