/*
 * 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.core.betwixt;

import java.io.InputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.beanutils.DynaProperty;
import org.apache.commons.betwixt.XMLIntrospector;
import org.apache.commons.betwixt.io.BeanReader;
import org.apache.commons.betwixt.io.BeanWriter;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import org.maskat.core.event.EventDef;
import org.maskat.core.layout.LayoutDef;
import org.maskat.core.layout.custom.ComponentLibrary;
import org.maskat.core.layout.custom.ComponentRegistry;
import org.maskat.core.layout.custom.DynaComponent;
import org.maskat.core.layout.custom.DynaComponentClass;

public class MaskatBeanIO {
	
	public static final String EVENT_DEF_CONFIG = "eventConfig.betwixt";
	public static final String LAYOUT_DEF_CONFIG = "layoutConfig.betwixt";

	/**
	 * 
	 * @param input Input source containing the XML data to be parsed
	 * @return
	 * @throws Exception
	 */
	public static EventDef readEventDef(InputSource input) throws Exception {
		
        // Now convert this to a bean using betwixt
        // Create BeanReader
        BeanReader beanReader  = new BeanReader();
        
        // Configure the reader
        beanReader.getXMLIntrospector().getConfiguration().setAttributesForPrimitives(false);
        beanReader.getBindingConfiguration().setMapIDs(false);
        
        XMLIntrospector intro = beanReader.getXMLIntrospector();
		InputStream in = MaskatBeanIO.class.getResourceAsStream(EVENT_DEF_CONFIG);
        intro.register(new InputSource(in));
        // Register beans so that betwixt knows what the xml is to be converted to
        // Since the element mapped to a Bean isn't called the same 
        // as Betwixt would have guessed, need to register the path as well
        beanReader.registerBeanClass("eventDef", EventDef.class);
        
        //
        beanReader.setEntityResolver(new MaskatEntityResolver());
        
        // Now we parse the xml
        EventDef eventDef = (EventDef) beanReader.parse(input);
        
        return eventDef;
	}
	
	/**
	 * 
	 * @param reader Reader containing the XML data to be parsed
	 * @return
	 * @throws Exception
	 */
	public static EventDef readEventDef(Reader reader) throws Exception {
        return readEventDef(new InputSource(reader));
	}
	
	/**
	 * 
	 * @param uri URI containing the XML data to be parsed
	 * @return
	 * @throws Exception
	 */
	public static EventDef readEventDef(String uri) throws Exception {
		return readEventDef(new InputSource(uri));
	}
	
	/**
	 * 
	 * @param stream Input stream containing the XML data to be parsed
	 * @return
	 * @throws Exception
	 */
	public static EventDef readEventDef(InputStream stream) throws Exception {
		return readEventDef(new InputSource(stream));
	}
	
	/**
	 * 
	 * @param eventDef EventDef containing all the data to be serialized
	 * @return
	 * @throws Exception
	 */
	public static String writeEventDef(EventDef eventDef) throws Exception {
        // Start by preparing the writer
        // We'll write to a string 
        StringWriter outputWriter = new StringWriter(); 
        
        // Betwixt just writes out the bean as a fragment
        // So if we want well-formed xml, we need to add the prolog
        outputWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
        
        // Create a BeanWriter which writes to our prepared stream
        BeanWriter beanWriter = new BeanWriter(outputWriter);
        
        // Configure betwixt
        // For more details see java docs or later in the main documentation
        beanWriter.getXMLIntrospector().getConfiguration().setAttributesForPrimitives(false);
        beanWriter.getBindingConfiguration().setMapIDs(false);
        
        beanWriter.getBindingConfiguration().setValueSuppressionStrategy(
        		new EventValueSuppressionStrategy());
        //beanWriter.getBindingConfiguration().setValueSuppressionStrategy(new MaskatValueSuppressionStrategy());
        beanWriter.enablePrettyPrint();
        beanWriter.setInitialIndentLevel(0);
        beanWriter.setWriteEmptyElements(false);

        XMLIntrospector intro = beanWriter.getXMLIntrospector();
		InputStream in = MaskatBeanIO.class.getResourceAsStream(EVENT_DEF_CONFIG);
        intro.register(new InputSource(in));
        // If the base element is not passed in, Betwixt will guess 
        // But let's write bean as base element 
        beanWriter.write(eventDef);
        
        // Write to System.out
        // (We could have used the empty constructor for BeanWriter 
        // but this way is more instructive)
        String out = outputWriter.toString();
        
        // Betwixt writes fragments not documents so does not automatically close 
        // writers or streams.
        // We will do no more writing so close the writer now.
        outputWriter.close();		
        
        return out;
	}
	
	/**
	 * 
	 * @param input Input source containing the XML data to be parsed
	 * @return
	 * @throws Exception
	 */
	public static LayoutDef readLayoutDef(InputSource input) throws Exception {
		final Map namespacesMap = new HashMap();

		// Now convert this to a bean using betwixt
		// Create BeanReader
		BeanReader beanReader = new BeanReader() {
			public void startPrefixMapping(String prefix, String namespaceURI)
					throws SAXException {
				namespacesMap.put(namespaceURI, prefix);
			}
		};
        beanReader.setNamespaceAware(true);
        
        // Configure the reader
        beanReader.getXMLIntrospector().getConfiguration().setAttributesForPrimitives(true);
        beanReader.getBindingConfiguration().setMapIDs(false);
        beanReader.getBindingConfiguration().setClassNameAttribute("maskatClassName");
        beanReader.getReadConfiguration().setBeanCreationChain(new DynaBeanCreationChain());
        
        XMLIntrospector intro = beanReader.getXMLIntrospector();
		InputStream in = MaskatBeanIO.class.getResourceAsStream(LAYOUT_DEF_CONFIG);
		intro.register(new InputSource(in));
//        intro.register(new InputSource(new StringReader(loadBetwixConfig())));
        // Register beans so that betwixt knows what the xml is to be converted to
        // Since the element mapped to a Bean isn't called the same 
        // as Betwixt would have guessed, need to register the path as well
        beanReader.registerBeanClass("layoutDef", LayoutDef.class);
        
        //
        beanReader.setEntityResolver(new MaskatEntityResolver());
        
        // Now we parse the xml
        LayoutDef layoutDef = (LayoutDef) beanReader.parse(input);
        if (layoutDef != null) {
        	layoutDef.setNamespaces(namespacesMap);
        }
        
        return layoutDef;
	}
	
	public static String loadBetwixConfig() {
		StringBuffer buffer = new StringBuffer();
		buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
		buffer.append("<betwixt-config>\n");
		InputStream in = MaskatBeanIO.class.getResourceAsStream(LAYOUT_DEF_CONFIG);
		buffer.append(slurp(in));	
		
		ComponentLibrary[] libraries =  ComponentRegistry.getLibraries();
		for (int i = 0; i < libraries.length; i++) {
			ComponentLibrary library = libraries[i];
			Object[] types = library.getComponentTypes();
			for (int j = 0; j < types.length; j++) {
				if (!(types[j] instanceof DynaComponentClass)) {
					continue;
				}
				DynaComponentClass dClass = (DynaComponentClass)types[j];
				buffer.append(getDynaComponentConfig(dClass));
				buffer.append("\n");
			}
		}
		
		buffer.append("</betwixt-config>");
//		System.out.print(buffer.toString());
		return buffer.toString();
	}
	
	public static String getDynaComponentConfig(DynaComponentClass dClass) {
		StringBuffer buffer = new StringBuffer();
		buffer.append("	<class name=\"");
		buffer.append(DynaComponent.class.getName());
		buffer.append("\">\n");
		
		buffer.append("		<element name='");
		buffer.append(dClass.getName());
		buffer.append("'>\n");
		
		buffer.append("			<element property='children' updater='addChild'/>\n");
		buffer.append("\n");
		
		DynaProperty[] properties = dClass.getDynaProperties();
		for (int i = 0; i < properties.length; i++) {
			DynaProperty property = properties[i];
			if (!DynaComponent.isAttributeProperty(property.getName())) {
				continue;
			}
			buffer.append("			<attribute name='");
			buffer.append(property.getName());
			buffer.append("' property='");
			buffer.append(property.getName());
			buffer.append("' class='");
			buffer.append(property.getType().getName());
			buffer.append("'/>\n");
		}
		buffer.append("		</element>\n");
		buffer.append("	</class>");
		return buffer.toString();
	}
	
	public static String slurp(InputStream in){
		try {
		    StringBuffer out = new StringBuffer();
		    byte[] b = new byte[4096];
		    for (int n; (n = in.read(b)) != -1;) {
		        out.append(new String(b, 0, n));
		    }
		    return out.toString();			
		} catch (Exception e) {
		}
		return "";
	}
	
	/**
	 * 
	 * @param reader Reader containing the XML data to be parsed
	 * @return
	 * @throws Exception
	 */
	public static LayoutDef readLayoutDef(Reader reader) throws Exception {
        return readLayoutDef(new InputSource(reader));
	}
	
	/**
	 * 
	 * @param uri URI containing the XML data to be parsed
	 * @return
	 * @throws Exception
	 */
	public static LayoutDef readLayoutDef(String uri) throws Exception {
		return readLayoutDef(new InputSource(uri));
	}
	
	/**
	 * 
	 * @param stream Input stream containing the XML data to be parsed
	 * @return
	 * @throws Exception
	 */
	public static LayoutDef readLayoutDef(InputStream stream) throws Exception {
		return readLayoutDef(new InputSource(stream));
	}
	
	public static String writeLayoutDef(LayoutDef layoutDef) throws Exception {
        // Start by preparing the writer
        // We'll write to a string 
        StringWriter outputWriter = new StringWriter(); 
        
        // Betwixt just writes out the bean as a fragment
        // So if we want well-formed xml, we need to add the prolog
        outputWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
        
        // Create a BeanWriter which writes to our prepared stream
        BeanWriter beanWriter = new MaskatBeanWriter(outputWriter);
        
        // Configure betwixt
        // For more details see java docs or later in the main documentation
        beanWriter.getXMLIntrospector().getConfiguration().setAttributesForPrimitives(true);
        //set this to avoid the conflict with className of Label/DivHtml/Checkbox/Radio
        beanWriter.getBindingConfiguration().setClassNameAttribute("maskatClassName");
        beanWriter.getBindingConfiguration().setMapIDs(false);
        beanWriter.getBindingConfiguration().setValueSuppressionStrategy(new LayoutValueSuppressionStrategy());
        beanWriter.enablePrettyPrint();
        beanWriter.setInitialIndentLevel(0);
        beanWriter.setWriteEmptyElements(false);

        MaskatNamespacePrefixMapper namespaceMapper = new MaskatNamespacePrefixMapper();
        Map namespaces = layoutDef.getNamespaces();
        for (Iterator ite = namespaces.keySet().iterator(); ite.hasNext();) {
        	String key = (String) ite.next();
        	namespaceMapper.setPrefix((String) namespaces.get(key), key);
        }
        beanWriter.getXMLIntrospector().getConfiguration().setPrefixMapper(namespaceMapper);      
        
        XMLIntrospector intro = beanWriter.getXMLIntrospector();
		InputStream in = MaskatBeanIO.class.getResourceAsStream(LAYOUT_DEF_CONFIG);
        intro.register(new InputSource(in));
//        intro.register(new InputSource(new StringReader(loadBetwixConfig())));

        // If the base element is not passed in, Betwixt will guess 
        // But let's write bean as base element 
        beanWriter.write(layoutDef);
        
        // Write to System.out
        // (We could have used the empty constructor for BeanWriter 
        // but this way is more instructive)
        String out = outputWriter.toString();
        
        // Betwixt writes fragments not documents so does not automatically close 
        // writers or streams.
        // We will do no more writing so close the writer now.
        outputWriter.close();		
        
        return out;
	}
}
