//------------------------------------------------------------------------------
// 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.publishing.services;

import java.io.File;
import java.net.URI;
import java.net.URL;
import java.net.URLDecoder;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.edit.provider.ItemProviderAdapter;
import org.eclipse.epf.common.utils.Timer;
import org.eclipse.epf.library.LibraryService;
import org.eclipse.epf.library.configuration.ConfigurationHelper;
import org.eclipse.epf.library.layout.ElementLayoutManager;
import org.eclipse.epf.library.layout.HtmlBuilder;
import org.eclipse.epf.library.layout.IElementLayout;
import org.eclipse.epf.library.layout.LayoutResources;
import org.eclipse.epf.library.util.IconUtil;
import org.eclipse.epf.library.util.ResourceHelper;
import org.eclipse.epf.publishing.PublishingResources;
import org.eclipse.epf.publishing.layout.Bookmark;
import org.eclipse.epf.uma.ContentCategory;
import org.eclipse.epf.uma.DescribableElement;
import org.eclipse.epf.uma.MethodConfiguration;
import org.eclipse.epf.uma.MethodElement;
import org.eclipse.epf.uma.ecore.util.OppositeFeature;
import org.eclipse.osgi.util.NLS;


/**
 * abstract class for publishing views
 * 
 * @author Jinhua Xi
 * @since 1.0
 */
public abstract class AbstractViewBuilder {

	private static final String APPLET_PATH = "applet" + File.separatorChar; //$NON-NLS-1$
	private static final String NO_APPLET_PATH = "noapplet" + File.separatorChar; //$NON-NLS-1$
	private static final String ICON_PATH = "images" + File.separatorChar; //$NON-NLS-1$
	//private static final String ICON_ZIP_FILE = "rpw_img.zip"; //$NON-NLS-1$


	// instead of publishing the whole content library, we collect the elements show up in the view tree
	// only publish those element show up in the view and it's referenced (including content reference) elements
	//protected List elementsTobePublished = new ArrayList();

	// move this to validator as well
	//protected List publishedElements = new ArrayList();

	protected HtmlBuilder builder;

	protected MethodConfiguration config;

	protected Bookmark defaultView = null;

	protected PublishOptions options = null;

	protected File iconPath;

	protected static final int timeout_millis = 600000;  // 10 minutes???

	public AbstractViewBuilder(HtmlBuilder builder, PublishOptions options)
	{
		this.builder = builder;
		this.options = options;

		this.config = builder.getLayoutManager().getConfiguration();

		// the path for the icons
		// we need the icons in the applet/images all the time since thias folder is user in Atlantic configuration
//		if ( options.useApplet )
//		{
			iconPath = new File(builder.getPublishDir(), APPLET_PATH + ICON_PATH);
//		}
//		else
//		{
//			iconPath = new File(builder.getPublishDir(), NO_APPLET_PATH + ICON_PATH);
//		}
	}

	public HtmlBuilder getHtmlBuilder()
	{
		return builder;
	}

	public PublishOptions getOptions()
	{
		return options;
	}

	public ElementLayoutManager getLayoutMgr()
	{
		return builder.getLayoutManager();
	}

	public PublishingContentValidator getValidator()
	{
		return (PublishingContentValidator)getHtmlBuilder().getValidator();
	}
	
	protected boolean canPublish(MethodElement element)
	{
		return canShow(element);
	}

	protected boolean canShow(MethodElement element) {
		if (element == null) {
			return false;
		}

		if ( getValidator().isDiscarded(null, null, element) )
		{
			return false;
		}
		
		if ( !ConfigurationHelper.canShow(element, config) ) {
			return false;
		}
		
		if (options != null && options.isPublishProcess()) {
			return getValidator().inClosure(element);
		} else {
			return true;
		}
	}
	
	protected void discardEmptyCategory(ContentCategory element, boolean discard)
	{
		if ( discard )
		{
			getValidator().setDiscardedElement(element);
			getValidator().logWarning(element, PublishingResources.discaredCategoryWarning_msg);
		}
		else
		{
			getValidator().addValidCategory( (ContentCategory)element);
		}
	}

	protected MethodElement calc01FeatureValue(MethodElement element, EStructuralFeature feature) {
		return ConfigurationHelper.calc01FeatureValue(element, feature, getLayoutMgr().getElementRealizer());
	}

	protected List calc0nFeatureValue(MethodElement element, EStructuralFeature feature) {
		return ConfigurationHelper.calc0nFeatureValue(element, feature, getLayoutMgr().getElementRealizer());
	}

	protected MethodElement calc01FeatureValue(MethodElement element, OppositeFeature feature) {
		return ConfigurationHelper.calc01FeatureValue(element, feature, getLayoutMgr().getElementRealizer());
	}
	
	protected List calc0nFeatureValue(MethodElement element, OppositeFeature feature) {
		return ConfigurationHelper.calc0nFeatureValue(element, feature, getLayoutMgr().getElementRealizer());
	}

	protected List getPublishedElements() {
		return getValidator().getPublishedElements();
	}
	
	/**
	 * publish the element, collect the linked elements in the published contents
	 * @param monitor
	 * @param element
	 * @param recursive
	 * @param linkedElements
	 */
	protected void publish(final IProgressMonitor monitor, final MethodElement element)
	{
		//System.out.println("--- Begin publishing element " + element.getGuid() + ":" + element.getType().getName() + ":" + element.getName() );
		Runnable runnable = new Runnable(){
			public void run() {
							
		try {
			List linkedElements = getValidator().getReferencedElements();
			if ( !canPublish(element) )
			{
				builder.getValidator().logWarning(element, PublishingResources.invalidElementWarning_msg); 
			}
			else if ( !getPublishedElements().contains(element) )
			{
				try
				{
					if ( monitor != null )
					{
						String str; //$NON-NLS-1$
						if ( linkedElements != null )
						{
							str = NLS.bind(PublishingResources.publishingLinkedElementTask_name, Integer.toString(getPublishedElements().size()), Integer.toString(linkedElements.size())); 
						}
						else
						{
							str = NLS.bind(PublishingResources.publishingElementTask_name, element.getType().getName(), element.getName()); 
						}
						monitor.subTask(str);

					}
					//builder.generateHtml(element);
					IElementLayout layout = builder.getLayoutManager().getLayout(element, true);
					String htmlfile = builder.generateHtml(layout);
					
					elementPublished(layout, htmlfile);

				}
				catch (Exception ex)
				{
					//ex.printStackTrace();
					builder.getValidator().logError(element, NLS.bind(PublishingResources.publishElementError_msg, ex.getMessage()), ex); 
				}
				getPublishedElements().add(element);
			}
			else
			{
				//System.out.println("Already generated: " + getURL(element) );
			}

//			if ( recursive )
//			{
//				// iterator all contained and referenced elements
//				EList children = element.eContents();
//				if ( children != null && children.size()>0 )
//				{
//					for (Iterator it = children.iterator(); it.hasNext(); )
//					{
//						Object e = it.next();
//						if ( e instanceof ContentDescription)
//						{
//							continue;
//						}
//
//						if ( e instanceof MethodElement )
//						{
//							publish(monitor, (MethodElement)e, true);
//						}
//						else
//						{
//							//System.out.println("Not method element,can't publish: " + e);
//							builder.getValidator().logWarning(element, PublishingResources.formatString("Publishing.invalidMethodElementWarning.msg", e.toString())); //$NON-NLS-1$
//						}
//					}
//				}
//			}
		} catch (RuntimeException e) {
			//e.printStackTrace();
			builder.getValidator().logError(element, NLS.bind(PublishingResources.publishElementError_msg, e.getMessage()), e); 
		}
		//System.out.println("--- End publishing element " + element.getGuid() + ":" + element.getType().getName() + ":" + element.getName() );
		
			}};

		
		Timer timer = new Timer();
		try {	
			// set the target element for thre content validator
			getValidator().setTargetElement(element);
			
			// run the publishing and check the time, if timeout, terminate it
			Thread t = new Thread(runnable);
			t.start();
			t.join(timeout_millis);
			if ( t.isAlive() ) {				
				// wait for the thread to die and log an error
				timer.stop();
				getValidator().logInfo(element, "publishing element takes " + timer.getTime() + " mini seconds already and is still not done yet ...");  //$NON-NLS-1$ //$NON-NLS-2$
				timer.start();
				t.join();
			}
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		finally {
			getValidator().setTargetElement(null);
			getValidator().getReferencedElements().remove(element);	

			timer.stop();
			getValidator().logInfo(element, timer.getTotalTime() + " mini seconds publishing element" ); //$NON-NLS-1$
		}
		
	}

	protected void elementPublished(IElementLayout layout, String htmlfile) {
		
	}
	
	private void copyNodeIcon(File source)
	{
		String name = source.getName();

		File dest = new File(iconPath, name);
		if ( ResourceHelper.copyFile(source, dest) == false )
		{
			builder.getValidator().logWarning(NLS.bind(PublishingResources.copyFileWarning_msg, source.getAbsolutePath(), dest.getAbsolutePath()));	 
		}

	}

	private String getNodeIconName(Object obj)
	{
		File iconFile = null;
		String iconName = null;

		if ( obj instanceof DescribableElement )
		{
			URI uri = ((DescribableElement)obj).getNodeicon();
			
			String elementName = ((DescribableElement)obj).getType().getName().toLowerCase();
			if (DefaultElementTypeResources.useDefaultIcon(elementName))
				uri = null;
				
			if ( uri != null )
			{
				// try if this is a valid URL or not
				boolean isFullPath = false;
				try
				{
					URL url = uri.toURL();
					if ( url != null )
					{
						iconFile = new File(URLDecoder.decode(url.getFile(), "UTF-8")); //$NON-NLS-1$
						isFullPath = true;
					}
				}
				catch (Exception ex)
				{
					; // not a valid url, maybe a relative path
				}
				
				if ( !isFullPath )
				{
					iconFile = new File(LibraryService.getInstance().getCurrentMethodLibraryPath(), URLDecoder.decode(uri.toString()));
				}
			}
		}
		
		if ( (iconFile != null ) && !iconFile.exists() )
		{
			iconFile = null;
		}

		if ( iconFile == null )
		{
			// get the default icon name
			if ( obj instanceof MethodElement )
			{
				String type = ((MethodElement)obj).getType().getName().toLowerCase();
				iconFile = IconUtil.getNodeIconFile(type);
			}
		}

		if ( iconFile != null )
		{
				// need to copy the file together and zip for the applet
			if ( !iconFile.exists() )
			{
				if ( obj instanceof MethodElement )
				{
					builder.getValidator().logWarning((MethodElement)obj, NLS.bind(PublishingResources.missingIconFileWarning_msg, iconFile.getAbsolutePath())); 
				}
				else
				{
					builder.getValidator().logWarning(NLS.bind(PublishingResources.missingIconFileWarning_msg, iconFile.getAbsolutePath())); 
				}
			}
				
			copyNodeIcon(iconFile);
			iconName = iconFile.getName();
		}

		if ( iconName == null || iconName.length() == 0 )
		{
			String name;
			if ( obj instanceof MethodElement )
			{
				name = ((MethodElement)obj).getName();
			}
			else
			{
				name = obj.toString();
			}

			builder.getValidator().logWarning(NLS.bind(PublishingResources.missingIconNameWarning_msg, name)); 
		}

		return iconName;
	}

	private String getOpenIconName(Object obj)
	{
		return ""; //$NON-NLS-1$
	}

	/**
	 * Get name
	 * @param obj
	 * @return
	 */
	private String getName(Object obj)
	{
		String name = null;
		if (obj instanceof MethodElement)
		{
			MethodElement e = (MethodElement)obj;
			
			// calculate the presentation name, for extenders, get from base if needed
			name = ConfigurationHelper.getPresentationName(e, config);							
			if ( name == null || name.equals("") ) //$NON-NLS-1$
			{
				name = e.getClass().getName();
				int index = name.lastIndexOf("."); //$NON-NLS-1$
				if ( index >=0 )
				{
					name = name.substring(index+1);
					if ( name.endsWith("Impl") ) //$NON-NLS-1$
					{
						name = name.substring(0, name.length()-4);
					}
				}
			}
		}
		else if (obj instanceof ItemProviderAdapter)
		{
			ItemProviderAdapter provider = (ItemProviderAdapter) obj;
			name = provider.getText(obj);

		}
		return name;
	}

	/**
	 * get guid
	 * @param obj
	 * @return
	 */
	private String getGUID(Object obj)
	{
		if (obj instanceof MethodElement)
		{
			return ((MethodElement) obj).getGuid();
		}
		else
			return null;
	}

	/**
	 * get url
	 * @param obj
	 * @return
	 */
	private String getURL(Object obj)
	{
		if (obj instanceof MethodElement)
		{
			return getLayoutMgr().getLayout((MethodElement) obj, true).getUrl();
		}
		else
		{
//			 TODO - layout needs to be provided for uncategorized and category object
			return "applet//empty.htm"; //$NON-NLS-1$
		}
	}

	/**
	 * Create bookmark
	 * @param element
	 * @return Bookmark
	 */
	protected Bookmark createBookmark(IProgressMonitor monitor, Object element)
	{
		// publish this element,
		if ( element instanceof MethodElement )
		{
			// delay the publishing till the bookmarks are created.
			builder.getValidator().addReferencedElement(null, (MethodElement)element);

			//this.publish(monitor, (MethodElement)element, false, elementsTobePublished);
		}

		// create a bookmark for this element
		String name = getName(element);
		String guid = getGUID(element);
		String url = getURL(element);
		String nodeIcon = getNodeIconName(element);

		String msg = NLS.bind(PublishingResources.generatingBookmarkTask_name, name); 
		monitor.subTask(msg);
		return createBookmark(name, guid, url, nodeIcon, nodeIcon);
	}

	protected Bookmark createBookmark(String name, String guid, String url, String closeIcon, String openIcon)
	{

		Bookmark b = new Bookmark(name);
		b.setPresentationName(name);
		b.setUniqueId(guid);
		b.setClosedIconName(closeIcon);
		b.setOpenIconName(openIcon);
		b.setFileName(url);
		b.setFromContentLibrary(true);
		b.setEnabled(true);
		b.setExist(true);
		b.setVisible(true);
		b.setTransparency(false);
		b.setDefault(true);
		b.setCurrent(false);

		return b;
	}

	public Bookmark getDefaultView()
	{
		return defaultView;
	}


	protected void copyIconsForNonApplet()
	{
		try
		{
			// don't jar it. copy the icons to the images
//			File jarFile = new File(builder.getPublishDir(), APPLET_PATH + ICON_ZIP_FILE);
//			PublishingUtil.jarFiles(iconPath, jarFile);

			if ( options.useDefaultTreeBrowser )
			{
				// also copy the icons to the no-applet folder
				LayoutResources.copyDir(iconPath.getAbsolutePath(), builder.getPublishDir() + NO_APPLET_PATH + ICON_PATH);
			}
		}
		catch (Exception ex)
		{
			ex.printStackTrace();
		}
	}

	/**
	 * build the views and returns a list of Boolmark objects
	 * @param pubMgr
	 * @return List a list of Bookmarks
	 */
	public abstract List buildViews(IProgressMonitor monitor);


	public void dispose()
	{
		//publishedElements.clear();
		
		if ( builder != null )
		{
			builder.getLayoutManager().clear();
			builder.dispose();
		}
		config = null;
		builder = null;
	}
	
}
