/*
 * Copyright 2006 Mask@ Project.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.maskat.ide.editors.linkeventsrc;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.exolab.castor.mapping.Mapping;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.Marshaller;
import org.exolab.castor.xml.ValidationException;
import org.maskat.framework.eventdef.Component;
import org.maskat.framework.eventdef.Event;
import org.maskat.framework.eventdef.IEventDefSource;
import org.maskat.framework.eventdef.Layout;
import org.maskat.framework.eventdef.xml.EventDefParser;
import org.maskat.ide.editors.MaskatEditor;
import org.maskat.ide.property.ComponentProperty;
import org.maskat.ide.property.EventProperty;
import org.maskat.ide.property.LayoutProperty;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

public class LinkEventDefSource implements IEventDefSource {
	/**
	 * List of <code>org.maskat.framework.eventdef.Layout</code>.
	 */
	private List eventDefs = new ArrayList();

	/**
	 * List of <code>org.eclipse.core.resources.IFile</code>
	 */
	private List eventFiles = new ArrayList();;

	/**
	 * 
	 * @param layoutFile
	 *            layout definition file.
	 */
	public LinkEventDefSource(IFile layoutFile) {
		// look for link.xml in the same directory as layoutFile.
		IPath path = layoutFile.getProjectRelativePath();
		String layoutFileName = path.lastSegment();

		IPath directory = path.removeLastSegments(1);
		IPath linkFilePath = ((IPath) directory.clone()).append("link.xml");

		IProject project = layoutFile.getProject();

		IFile linkFile = project.getFile(linkFilePath);
		if (linkFile.exists()) {
			// if the file was defined in the link.xml and have event files
			// defined
			InputStream stream = null;
			try {
				stream = linkFile.getContents();
				LinkConfig config = LinkConfig.load(stream);
				String[] eventFileNames = config.findEventFileNames(layoutFileName);
				if (eventFileNames != null && eventFileNames.length != 0) {
					for (int i = 0; i < eventFileNames.length; i++) {
						IPath eventFilePath = ((IPath) directory.clone())
								.append(eventFileNames[i]);
						IFile eventFile = project.getFile(eventFilePath);
						eventFiles.add(eventFile);
					}
				}
			} catch (Exception e) {
				// ignore all exceptions
			} finally {
				if (stream != null)
					try {
						stream.close();
					} catch (IOException e) {
					}
			}

		}
		if (eventFiles.size() == 0) {
			// else xxx_e.xml
			int index = layoutFileName.lastIndexOf("xml") - 1;
			IPath _eFilePath = ((IPath) directory.clone()).append(layoutFileName
					.substring(0, index)
					+ "_e.xml");
			eventFiles.add(project.getFile(_eFilePath));
		}
		// the eventFiles has at least one element
	}

	// public void addEventDef(Layout layout) {
	// if (eventDefs == null)
	// eventDefs = new ArrayList();
	// eventDefs.add(layout);
	// }

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.maskat.framework.eventdef.IEventDefSource#findComponent(java.lang.String)
	 */
	public Component findComponent(String componentName) {
		for (Iterator it = eventDefs.iterator(); it.hasNext();) {
			Layout layout = (Layout) it.next();
			Component result = layout.findComponent(componentName);
			if (result != null)
				return result;
		}

		Layout layout = (Layout) eventDefs.get(0);
		ComponentProperty comp = new ComponentProperty();
		comp.setId(componentName);
		layout.addChild(comp);// Add the component to the first definition.

		return comp;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.maskat.framework.eventdef.IEventDefSource#load()
	 */
	public void load() throws Exception {
		// XXX temporarily, assume that files is not empty or null
		for (Iterator it = eventFiles.iterator(); it.hasNext();) {
			IFile eventFile = (IFile) it.next();

			eventDefs.add(loadOneEventFile(eventFile));
		}
	}

	private Layout loadOneEventFile(IFile eventXmlFile) throws CoreException,
			IOException, SAXException {

		Layout layout = new LayoutProperty();
		InputStream eventXmlStream = null;
		try {
			eventXmlFile.refreshLocal(1, null);

			if (eventXmlFile.exists()) {
				eventXmlStream = eventXmlFile.getContents();
				layout = (LayoutProperty) EventDefParser.parse(eventXmlStream);
			}
		} finally {
			if (eventXmlStream != null)
				eventXmlStream.close();
		}
		return layout;
	}

	private Mapping eventDefMapping = null;

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.maskat.framework.eventdef.IEventDefSource#save()
	 */
	public String[] save() throws Exception {
		if (eventDefs == null)
			return null;
		List eventDefString = new ArrayList();
		for (int i = 0; i < eventDefs.size(); i++) {
			Layout layout = (Layout) eventDefs.get(i);

			clearDisabledEvent(layout);
			clearBlankComponent(layout);

			layout.shrinkEvent();

			if (eventDefMapping == null) {
				eventDefMapping = new Mapping();
				InputStream mappingInput = MaskatEditor.class
						.getClassLoader()
						.getResourceAsStream(
								"org/maskat/framework/eventdef/xml/evtMarshalMapping.xml");
				eventDefMapping.loadMapping(new InputSource(mappingInput));
			}

			String result = marshallObj(layout, (IFile) eventFiles.get(i),
					eventDefMapping, null);
			eventDefString.add(result);

			// event is shrinked so expand them
			layout.expandEventRefToEvent();
		}
		return (String[]) eventDefString.toArray(new String[eventDefString.size()]);
	}

	private String marshallObj(Object obj, IFile file, Mapping mapping,
			IProgressMonitor monitor) throws IOException, MappingException,
			MarshalException, ValidationException, CoreException,
			UnsupportedEncodingException {
		StringWriter osw = new StringWriter();

		Marshaller marshaller = new Marshaller(osw);
		marshaller.setMapping(mapping);
		marshaller.setSuppressXSIType(true);
		marshaller.marshal(obj);

		String result = osw.toString();

		if (!file.exists()) {
			file.create(new ByteArrayInputStream(new byte[0]), false, null);
		}
		file.setContents(new ByteArrayInputStream(result.getBytes("UTF-8")), false,
				false, monitor);
		return result;
	}

	/**
	 * enable=falseのイベントは出力しないために、ここでLayoutオブジェクトからそういったイベントを削除する
	 * 
	 * @param layout
	 */
	private void clearDisabledEvent(Layout layout) {
		for (Iterator it = layout.getAllComponents(); it != null && it.hasNext();) {
			Component comp = (Component) it.next();
			if (comp.hasEvents()) {
				for (Iterator it2 = comp.getAllEvents(); it2 != null && it2.hasNext();) {
					Event event = (Event) it2.next();
					if (event instanceof EventProperty) {
						if (!((EventProperty) event).isEnable()) {
							it2.remove();
						}
					}
				}
			}
		}
		for (Iterator it = layout.getAllEvents(); it != null && it.hasNext();) {
			Event event = (Event) it.next();
			if (event instanceof EventProperty) {
				if (!((EventProperty) event).isEnable()) {
					it.remove();
				}
			}
		}

	}

	/**
	 * イベントのないコンポーネントは出力しない
	 * 
	 * @param layout
	 */
	private void clearBlankComponent(Layout layout) {
		for (Iterator it = layout.getAllComponents(); it != null && it.hasNext();) {
			Component comp = (Component) it.next();
			if (!comp.hasEvents()) {
				it.remove();
			}
		}
	}

	public Layout[] getDefs() {
		return (Layout[]) eventDefs.toArray(new Layout[eventDefs.size()]);
	}
}
