//------------------------------------------------------------------------------
// Copyright (c) 2005, 2006 IBM Corporation and others.
// 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:
// IBM Corporation - initial implementation
//------------------------------------------------------------------------------
package org.eclipse.epf.library.layout.elements;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.epf.common.utils.NetUtil;
import org.eclipse.epf.library.LibraryPlugin;
import org.eclipse.epf.library.LibraryService;
import org.eclipse.epf.library.configuration.ConfigurationHelper;
import org.eclipse.epf.library.edit.util.TngUtil;
import org.eclipse.epf.library.layout.ElementLayoutManager;
import org.eclipse.epf.library.layout.IElementLayout;
import org.eclipse.epf.library.layout.LayoutInfo;
import org.eclipse.epf.library.layout.LayoutResources;
import org.eclipse.epf.library.layout.util.XmlElement;
import org.eclipse.epf.library.util.ResourceHelper;
import org.eclipse.epf.uma.Activity;
import org.eclipse.epf.uma.ContentDescription;
import org.eclipse.epf.uma.ContentElement;
import org.eclipse.epf.uma.DescribableElement;
import org.eclipse.epf.uma.MethodConfiguration;
import org.eclipse.epf.uma.MethodElement;
import org.eclipse.epf.uma.MethodLibrary;
import org.eclipse.epf.uma.UmaPackage;
import org.eclipse.epf.uma.WorkOrder;
import org.eclipse.epf.uma.ecore.util.OppositeFeature;

/**
 * The abstract layout for all Method Elements.
 * 
 * @author Jinhua Xi
 * @author Kelvin Low
 */
public abstract class AbstractElementLayout implements IElementLayout {

	protected ElementLayoutManager layoutManager;

	protected MethodElement element;

	protected MethodElement ownerElement;

	protected String url = null;

	// element element path relative to the publishing root
	protected String elementPath = ""; //$NON-NLS-1$

	// the back up path to reath the publishing root
	protected String backPath = ""; //$NON-NLS-1$

	// map of layout file to the output file extention, if the element has more
	// than one layout output
	protected List layouts = null;

	protected MethodElement targetElement = null;

	protected boolean showElementLink = true;
	
	protected boolean debug = LibraryPlugin.getDefault().isDebugging();

	public AbstractElementLayout() {
	}

	/**
	 * if the element's content is target for another element, set it here. for
	 * example, step content cat target for a Task or a task descriptor
	 * copyright content can target to different elements.
	 * 
	 * The purpose of this is that the system will fix the links in the content
	 * to relative to the target element.
	 */
	public void setContentTarget(MethodElement targetElement) {
		this.targetElement = targetElement;
	}

	/**
	 * need to set the owner of the current layout element. In most cases this
	 * should be the eContainer of the element. This is needed because in some
	 * situation the element does not have an owner when the object is created.
	 * For example, the ContentDescription object's eContiner is null if the
	 * content file is not saved.
	 * 
	 * @param owner
	 *            MethodElement
	 */
	public void setElementOwner(MethodElement owner) {
		this.ownerElement = owner;
	}

	public void setShowElementLink(boolean show) {
		this.showElementLink = show;
	}
	
	public abstract void init(ElementLayoutManager layoutManager,
			MethodElement element);

	protected void __init(ElementLayoutManager layoutManager,
			MethodElement element) {
		this.layoutManager = layoutManager;
		this.element = element;
		if (!ConfigurationHelper.isDescriptionElement(element)) {
			buildPath();
			this.url = elementPath + getFileName(ResourceHelper.FILE_EXT_HTML);
		}
		
		// this is a workaround. 
		// we need to pass parameters in the file url, 
		// however, if the file does not exist, the parameters is not passed over in browsing
		// so create a dummy file
		if (!this.layoutManager.isPublishingMode() && !(
					element instanceof ContentDescription 
				|| element.eContainer() instanceof ContentDescription
				|| element instanceof MethodConfiguration) )
		{
			try {
				String html_file = this.layoutManager.getPublishDir() + this.getFilePath() + getFileName(ResourceHelper.FILE_EXT_HTML);
				File f = new File(html_file);
				if ( !f.exists() )
				{
					f.getParentFile().mkdirs();
					f.createNewFile();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
	}

	protected void setLayoutInfo(LayoutInfo info) {
		if (layouts == null) {
			layouts = new ArrayList();
		}
		layouts.add(info);
	}

	/**
	 * return a list of LayoutInfo objects
	 * 
	 * @return a list of LayoutInfo objects
	 */
	public List getLayouts() {
		return layouts;
	}

	public ElementLayoutManager getLayoutMgr() {
		return this.layoutManager;
	}

	public MethodElement getElement() {
		return element;
	}

	public String getType() {
		return element.getType().getName();
	}

	public String getName() {
		return element.getName();
	}

	public String getDisplayName() {
		/*
		 * String name = null; if (element instanceof ContentElement) { name =
		 * ((ContentElement)element).getPresentationName(); } if (name == null ||
		 * name.length() == 0) { name = this.getName(); } return name;
		 */

		return TngUtil.getPresentationName(element);
	}

	public String getId() {
		return element.getGuid();
	}

	public String getUrl() {
		return url;
	}

	public String getBackPath() {
		return backPath;
	}

	public String getFilePath() {
		return elementPath;
	}

	/**
	 * Returns the file path relative to another element. This is the
	 * relativeTo.backpath + this.elementpath.
	 */
	public String getFilePath(IElementLayout relativeTo) {
		return relativeTo.getBackPath() + this.getFilePath();
	}

	/**
	 * Returns the file name with the given extension.
	 */
	public String getFileName(String ext) {
		return ResourceHelper.getFileName(element, ext);
	}

	// /**
	// * Returns the file name with the given extension for the given layout.
	// */
	// public String getFileName(LayoutInfo info, String ext) {
	// return ResourceHelper.getFileName(element, null, info.file_suffix, ext);
	// }

	public String getXslUrl() {
		if (element instanceof ContentElement) {
			return LayoutResources.getXslUri(getType().toLowerCase(),
					"contentelement"); //$NON-NLS-1$
		} else {
			return LayoutResources.getXslUri(getType().toLowerCase(), null);
		}
	}

	private void buildPath() {
		// this is <plugin>/<element type>/
		elementPath = "";
		backPath = "./";
		if ( !(element instanceof MethodConfiguration || element instanceof MethodLibrary) )
		{
			elementPath = ResourceHelper.getElementPath(element).replace(
					File.separatorChar, '/');
			backPath = ResourceHelper.getBackPath(element);
		}
	}

	/**
	 * check if the html content generated from this xsl file needs to be scanned or not
	 * scan the content is for identifying element references in the content and copy over resource files
	 * in some cases we don't need to scan the content, for example, the activity breakdown structure
	 * 
	 * @param xslUrl the xsl that html is generated from, null for the default xsl layout
	 * @return boolean
	 */
	public boolean needContentScan(String xslUrl)
	{
		// by default all contents are scanned
		return true;
	}
	
	/**
	 * get the layout for a child element of this element
	 * ActivityElementLayout should override this method to create layout with node path
	 * @param child
	 * @return IElementLayout
	 */
	protected IElementLayout getChildLayout(MethodElement child)
	{
		return layoutManager.getLayout(child, true);
	}
	
	protected void processChild(Object feature, XmlElement parent, MethodElement e,
			boolean includeReferences) {
		if (e != null) {
			IElementLayout l = getChildLayout(e);
			if (l != null) {
				// don't include the references of the refereced elements,
				// otherwise, may cause deadlock
				boolean isContent = (e instanceof MethodElement)
						&& ConfigurationHelper
								.isDescriptionElement((MethodElement) e);
				if (isContent) {
					if (targetElement != null) {
						l.setContentTarget(targetElement);
					}
					l.setElementOwner(element);
				}
				parent.addChild(l.getXmlElement(includeReferences));
			}
		}
	}

	protected void processChild(Object feature, XmlElement parent, List items,
			boolean includeReferences) {
		if (items != null && items.size() > 0) {
			for (Iterator it = items.iterator(); it.hasNext();) {
				Object e = it.next();
				if (e instanceof MethodElement) {
					MethodElement me = (MethodElement) e;
					e = ConfigurationHelper.getCalculatedElement(me,
							layoutManager.getConfiguration());
					if (e != null) {
						IElementLayout l = getChildLayout(me);
						if (l != null) {							
							// don't include the references of the refereced
							// elements, otherwise, may cause deadlock
							parent.addChild(l.getXmlElement(ConfigurationHelper
									.isDescriptionElement(me) ? true
									: includeReferences));
						}
					}
				}
			}
		}
	}

	public void calculate0nReferences(XmlElement elementXml,
			boolean includeReferences) {
		// referenceMap.clear();

		List properties = element.getInstanceProperties();
		if (properties != null) {
			// get all string type attributes
			for (int i = 0; i < properties.size(); i++) {
				EStructuralFeature feature = (EStructuralFeature) properties
						.get(i);
				if (feature.isMany()) {
					loadFeature(feature, elementXml, includeReferences);
				}
			}
		}
	}

	protected XmlElement getXmlElement() {
		XmlElement elementXml = new XmlElement("Element") //$NON-NLS-1$
				.setAttribute("Type", getType()) //$NON-NLS-1$
				.setAttribute("TypeName", TngUtil.getTypeText(element)) //$NON-NLS-1$
				.setAttribute("Name", getName()) //$NON-NLS-1$
				.setAttribute("BackPath", getBackPath()) //$NON-NLS-1$
				.setAttribute("ShapeiconUrl", getShapeiconUrl()) //$NON-NLS-1$
				.setAttribute("DisplayName", getDisplayName()); //$NON-NLS-1$

		if ( showElementLink ) {
			elementXml.setAttribute("Url", getUrl()); //$NON-NLS-1$
		} 
		
		return elementXml;
	}

	public void loadAttributes(XmlElement elementXml) {

		boolean isActivityAttribute = (element instanceof Activity) 
			|| (element instanceof ContentDescription) && (element.eContainer() instanceof Activity);
		
		List properties = element.getInstanceProperties();
		if (properties != null) {
			// get all string type attributes
			for (int i = 0; i < properties.size(); i++) {
				EStructuralFeature p = (EStructuralFeature) properties.get(i);			
				if (!(p instanceof EAttribute) ) {
					continue;
				}
				EAttribute attrib = (EAttribute)p;
				String name = p.getName();
								
				Object value;
				if (name.equals("presentationName")) //$NON-NLS-1$
				{
					// value = TngUtil.getPresentationName(element);
					value = ConfigurationHelper.getPresentationName(element,
							layoutManager.getConfiguration());
				} else if( isActivityAttribute && String.class.isAssignableFrom(attrib.getEAttributeType().getInstanceClass())) {
					
					// for activity, the way to merge attribute from base is different
					value = ConfigurationHelper.getActivityStringAttribute(element, attrib, getLayoutMgr().getConfiguration());
				} else {
					value = getAttributeFeatureValue(p);
				}

				elementXml
						.newChild("attribute").setAttribute("name", name).setValue((value == null) ? "" : value.toString()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			}
		}
	}

	public Object getAttributeFeatureValue(EStructuralFeature feature) {
		Object value = ConfigurationHelper.calcAttributeFeatureValue(element,
				ownerElement, feature, layoutManager.getConfiguration());

		// now if the target is set and is different from the element or the
		// element's owner
		// then fix the content
		if ((targetElement != null) && (value != null)
				&& (value.toString().length() > 0)) {
			String contentPath = ResourceHelper
					.getElementPath((element instanceof ContentDescription) ? 
							((ownerElement==null) ? (MethodElement) element.eContainer() : ownerElement) 
							: element);

			String backPath = ResourceHelper
					.getBackPath((targetElement instanceof ContentDescription) ? (MethodElement) targetElement
							.eContainer()
							: targetElement);

			value = ResourceHelper.fixContentUrlPath(value.toString(),
					contentPath, backPath);

		}

		return value;
	}

	public void loadCopyright(XmlElement elementXml) {

		// List items = new ArrayList();
		// ConfigurationHelper.calculateCopyright(element,
		// layoutManager.getConfiguration(), items);
		// if ( items.size() > 0 )
		// {
		// SupportingMaterial copyright;
		// StringBuffer copyrights = new StringBuffer();
		// for ( Iterator it = items.iterator(); it.hasNext(); )
		// {
		// copyright = (SupportingMaterial) it.next();
		// String statement = copyright.getPresentation().getMainDescription();
		//							
		// // need to fix the content for relative links.
		// // since the link is a relative path to the SupportingMaterial
		// location,
		// // need to convert to relative to the current element
		// // so re-calcuate the back path
		// // jxi, 06/28/05
		// String contentPath = ResourceHelper.getElementPath(copyright);
		// String backPath = ResourceHelper.getBackPath(element);
		// statement = ResourceHelper.fixContentUrlPath(statement, contentPath,
		// backPath);
		// copyrights.append(statement);
		// }
		//			
		// elementXml.newChild("copyright").setValue(copyrights.toString());
		// //$NON-NLS-1$
		// }

		String copyright = ConfigurationHelper.getCopyrightText(element,
				layoutManager.getConfiguration());
		if (copyright != null && copyright.length() > 0) {
			elementXml.newChild("copyright").setValue(copyright); //$NON-NLS-1$
		}
	}

	public void calculate01References(XmlElement elementXml,
			boolean includeReferences) {
		List properties = element.getInstanceProperties();
		if (properties != null) {
			// get element references
			for (int i = 0; i < properties.size(); i++) {
				EStructuralFeature p = (EStructuralFeature) properties.get(i);

				// Object value = element.get(i);

				EClassifier type = p.getEType();
				if (!(type instanceof EClass) || p.isMany()) {
					continue;
				}

				loadFeature(p, elementXml, includeReferences);
			}
		}
	}

	/**
	 * load the non-attribute feature
	 * 
	 * @param feature
	 * @param elementXml
	 * @param includeReferences
	 */
	public void loadFeature(EStructuralFeature feature, XmlElement elementXml,
			boolean includeReferences) {
		if (!(feature.getEType() instanceof EClass)) {
			return;
		}

		String name = feature.getName();

		if (!feature.isMany()) {
			MethodElement e = ConfigurationHelper.calc01FeatureValue(element, ownerElement,
					feature, layoutManager.getElementRealizer());
			// RATLC00380414 - Browsing stops working when a role is set to
			// replaced another role
			// for replacer, the base will be evaluated to the replacer
			// and causing deadlock
			if (e != null && e != element) {
				boolean showDetail = (ConfigurationHelper
						.isDescriptionElement(e)
				/*
				 * || (p ==
				 * UmaPackage.eINSTANCE.getMethodUnit_CopyrightStatement())
				 */) ? true : includeReferences;
				
				if ( acceptFeatureValue(feature, e) ) {
					processChild(feature, 
							elementXml
									.newChild("reference").setAttribute("name", name), e, showDetail); //$NON-NLS-1$ //$NON-NLS-2$
				}
			}
		} else if (feature.isMany()) {
			List pv = ConfigurationHelper.calc0nFeatureValue(element, ownerElement, feature,
					layoutManager.getElementRealizer());
			if ( acceptFeatureValue(feature, pv) ) {
				addReferences(feature, elementXml, name, pv);
			}
		}
	}

	/**
	 * load the non-attribute feature
	 * 
	 * @param feature
	 * @param elementXml
	 * @param includeReferences
	 */
	public void loadFeature(OppositeFeature feature, XmlElement elementXml,
			boolean includeReferences) {
		String name = feature.getName();

		if (!feature.isMany()) {
			MethodElement e = ConfigurationHelper.calc01FeatureValue(element,
					feature, layoutManager.getElementRealizer());
			// RATLC00380414 - Browsing stops working when a role is set to
			// replaced another role
			// for replacer, the base will be evaluated to the replacer
			// and causing deadlock
			if (e != null && e != element) {
				boolean showDetail = (ConfigurationHelper
						.isDescriptionElement(e)
				/*
				 * || (p ==
				 * UmaPackage.eINSTANCE.getMethodUnit_CopyrightStatement())
				 */) ? true : includeReferences;

				if ( acceptFeatureValue(feature, e) )
				{
					processChild(feature, 
							elementXml
									.newChild("reference").setAttribute("name", name), e, showDetail); //$NON-NLS-1$ //$NON-NLS-2$
				}
			}
		} else if (feature.isMany()) {
			List pv = ConfigurationHelper.calc0nFeatureValue(element, feature,
					layoutManager.getElementRealizer());
			if ( acceptFeatureValue(feature, pv) && pv.size() > 0) {
				addReferences(feature, elementXml, name, pv);
			}
		}
	}

	public void loadReferences(XmlElement elementXml, boolean includeReferences) {
		List properties = element.getInstanceProperties();
		if (properties != null) {
			for (int i = 0; i < properties.size(); i++) {
				EStructuralFeature feature = (EStructuralFeature) properties
						.get(i);
				if (feature.getEType() instanceof EClass) {
					loadFeature(feature, elementXml, includeReferences);
				}
			}
		}
		
		Collection oppositeProperties = new ArrayList(element.getOppositeFeatures());
		for (Iterator z= oppositeProperties.iterator(); z.hasNext(); )
		{
			OppositeFeature ofeature = (OppositeFeature) z.next();
			loadFeature(ofeature, elementXml, includeReferences);
		}
	}

	public void addReference(Object feature, XmlElement elementXml, String referenceName,
			MethodElement element) {
		processChild(feature, 
				elementXml
						.newChild("reference").setAttribute("name", referenceName), element, false); //$NON-NLS-1$ //$NON-NLS-2$
	}

	public void addReferences(Object feature, XmlElement elementXml, String referenceName,
			List items) {
		processChild(feature, 
				elementXml
						.newChild("referenceList").setAttribute("name", referenceName), items, false); //$NON-NLS-1$ //$NON-NLS-2$
	}

	public XmlElement getXmlElement(boolean includeReferences) {
		XmlElement elementXml = getXmlElement();

		// load the references
		if (includeReferences) {

			// load the copyright info
			loadCopyright(elementXml);

			// load the attributes
			loadAttributes(elementXml);

			loadReferences(elementXml, false);
		}

		return elementXml;
	}

	/**
	 * some layout need to have the feature values for further processing. So
	 * this method will be called when a feature is calculated in this abstract
	 * class
	 * 
	 * @param name
	 * @param value
	 */
	protected boolean acceptFeatureValue(EStructuralFeature feature, Object value) {
		return true;
	}

	protected boolean acceptFeatureValue(OppositeFeature feature, Object value) {
		return true;
	}
	
	public String getShapeiconUrl() {
		URI uri = null;
		String imageUrl;
		// String imageFile;
		if (element instanceof DescribableElement) {
			uri = ((DescribableElement) element).getShapeicon();
		}

		if (uri == null) {
			imageUrl = getDefaultShapeiconUrl();
		} else {
			URI imageUri = ResourceHelper.getRelativeURI(uri, new File(LibraryService
					.getInstance().getCurrentMethodLibraryPath()).toURI());
			imageUrl = NetUtil.decodedFileUrl(imageUri.toString());
		}

		// need to copy the image file if it's not in the default directory
		if (!imageUrl.startsWith("images/")) //$NON-NLS-1$
		{
			File source = new File(LibraryService.getInstance()
					.getCurrentMethodLibraryPath(), imageUrl);
			File dest = new File(getLayoutMgr().getPublishDir(), imageUrl);
			ResourceHelper.copyFile(source, dest);
		}

		return imageUrl;
	}

	public String getNodeiconUrl() {
		return ""; //$NON-NLS-1$
	}

	public String getDefaultShapeiconUrl() {
		return LayoutResources.getDefaultShapeiconUrl(element.getType()
				.getName().toLowerCase());
	}

	public String getDiagramiconUrl() {
		return "icons/" + element.getType().getName() + ".gif"; //$NON-NLS-1$ //$NON-NLS-2$
	}

	/**
	 * this is a shared method to load the work order of the work breakdown
	 * element. will be used by all work breakdown elemnt layout.
	 * 
	 * @param elementXml
	 *            XmlElement the parent xml element to load the work order
	 *            layout
	 */
	protected void loadWorkOrder(XmlElement elementXml) {
		EStructuralFeature feature = UmaPackage.eINSTANCE
				.getWorkBreakdownElement_LinkToPredecessor();

		List items = ConfigurationHelper.calc0nFeatureValue(element, feature,
				layoutManager.getElementRealizer());
		XmlElement predecessorXml = elementXml
				.newChild("referenceList").setAttribute("name", feature.getName()); //$NON-NLS-1$ //$NON-NLS-2$
		if (items != null && items.size() > 0) {
			for (Iterator it = items.iterator(); it.hasNext();) {
				WorkOrder wo = (WorkOrder) it.next();
				MethodElement me = (MethodElement) ConfigurationHelper
						.calc01FeatureValue(wo, UmaPackage.eINSTANCE
								.getWorkOrder_Pred(), layoutManager
								.getElementRealizer());
				if (me != null) {
					IElementLayout l = getChildLayout(me);
					if (l != null) {
						predecessorXml.addChild(l.getXmlElement(false));
					}
				}
			}
		}
	}
	
	
}