/*******************************************************************************
 * Copyright (c) 2011 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 API and implementation
 *******************************************************************************/
package org.eclipse.wst.css.ui.internal.hyperlink;

import java.io.File;
import java.net.URI;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.hyperlink.AbstractHyperlinkDetector;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.wst.common.uriresolver.internal.provisional.URIResolverPlugin;
import org.eclipse.wst.css.core.internal.document.CSSRegionContainer;
import org.eclipse.wst.css.core.internal.provisional.document.ICSSNode;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.w3c.dom.css.CSSImportRule;
import org.w3c.dom.css.CSSPrimitiveValue;

/**
 * Detects hyperlink regions within CSS documents. This includes url() methods as
 * well as the resource referred to by @import 
 *
 */
public class CSSHyperlinkDetector extends AbstractHyperlinkDetector {

	/* (non-Javadoc)
	 * @see org.eclipse.jface.text.hyperlink.IHyperlinkDetector#detectHyperlinks(org.eclipse.jface.text.ITextViewer, org.eclipse.jface.text.IRegion, boolean)
	 */
	public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region, boolean canShowMultipleHyperlinks) {
		if (textViewer != null && region != null) {
			
			final IDocument document = textViewer.getDocument();
			final ICSSNode node = getNode(document, region);

			if (node == null) {
				return null;
			}
			String href = null;
			switch (node.getNodeType()) {
				case ICSSNode.PRIMITIVEVALUE_NODE:
					if (((CSSPrimitiveValue)node).getPrimitiveType() == CSSPrimitiveValue.CSS_URI) {
						href = ((CSSPrimitiveValue) node).getStringValue();
					}
					break;
				case ICSSNode.IMPORTRULE_NODE:
					href = ((CSSImportRule) node).getHref();
					break;
			}
			if (href != null) {
				final IHyperlink hyperlink = getHyperlink(node, href);
				if (hyperlink != null) {
					return new IHyperlink[] { hyperlink };
				}
			}
			
		}
		return null;
	}

	private ICSSNode getNode(IDocument document, IRegion region) {
		if (!(document instanceof IStructuredDocument))
			return null;

		IStructuredModel model = null;
		ICSSNode node = null;
		try {
			model = StructuredModelManager.getModelManager().getModelForRead((IStructuredDocument)document);
			node = (ICSSNode) model.getIndexedRegion(region.getOffset());
		}
		finally {
			if (model != null) {
				model.releaseFromRead();
			}
		}
		return node;
	}
	private IHyperlink getHyperlink(ICSSNode node, String href) {
		IHyperlink hyperlink = null;
		final String baseLocation = getBaseLocation(node.getOwnerDocument().getModel());
		if (baseLocation != null) {
			final String resolvedHref = URIResolverPlugin.createResolver().resolve(baseLocation, null, href);
			if (resolvedHref != null && isValidURI(resolvedHref)) {
				final IRegion hyperlinkRegion = getHyperlinkRegion(node, href);
				hyperlink = createHyperlink(resolvedHref, hyperlinkRegion);
			}
		}
		
		return hyperlink;
	}

	private IRegion getHyperlinkRegion(ICSSNode node, String href) {
		CSSRegionContainer uriRegion = null;
		switch (node.getNodeType()) {
			case ICSSNode.PRIMITIVEVALUE_NODE:
				uriRegion = (CSSRegionContainer) node;
				break;
			case ICSSNode.IMPORTRULE_NODE:
				ICSSNode attribute = node.getAttributes().getNamedItem("href"); //$NON-NLS-1$
				if (attribute instanceof CSSRegionContainer) {
					uriRegion = (CSSRegionContainer) attribute;
				}
				break;
		}
		if (uriRegion != null) {
			final int start = uriRegion.getStartOffset();
			final int end = uriRegion.getEndOffset();
			if (end > start)
				return new Region(start, end - start);
		}
		return null;
	}

	private IHyperlink createHyperlink(String href, IRegion region) {
		IHyperlink link = null;
		// try to locate the file in the workspace
		File systemFile = getFileFromUriString(href);
		if (systemFile != null) {
			String systemPath = systemFile.getPath();
			IFile file = getFile(systemPath);
			if (file != null) {
				// this is a WorkspaceFileHyperlink since file exists in
				// workspace
				link = new WorkspaceFileHyperlink(region, file);
			}
		}
		return link;
	}

	/**
	 * Returns an IFile from the given uri if possible, null if cannot find
	 * file from uri.
	 * 
	 * @param fileString
	 *            file system path
	 * @return returns IFile if fileString exists in the workspace
	 */
	private IFile getFile(String fileString) {
		IFile file = null;

		if (fileString != null) {
			Path filePath = new Path(fileString);
			if (filePath.segmentCount() > 1 && ResourcesPlugin.getWorkspace().getRoot().getFile(filePath).exists()) {
				return ResourcesPlugin.getWorkspace().getRoot().getFile(filePath);
			}
			IFile[] files = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocation(filePath);
			for (int i = 0; (i < files.length) && (file == null); i++) {
				if (files[i].exists()) {
					file = files[i];
				}
			}
		}

		return file;
	}

	/**
	 * Checks whether the given uriString is really pointing to a file
	 * 
	 * @param uriString
	 * @return boolean
	 */
	private boolean isValidURI(String uriString) {
		boolean isValid = false;
		File file = getFileFromUriString(uriString);

		if (file != null) {
			isValid = file.isFile();
		}
			
		return isValid;
	}

	/**
	 * Create a file from the given uri string
	 * 
	 * @param uriString -
	 *            assumes uriString is not http://
	 * @return File created from uriString if possible, null otherwise
	 */
	private File getFileFromUriString(String uriString) {
		File file = null;
		try {
			// first just try to create a file directly from uriString as
			// default in case create file from uri does not work
			file = new File(uriString);

			// try to create file from uri
			URI uri = new URI(uriString);
			file = new File(uri);
		}
		catch (Exception e) {
			// if exception is thrown while trying to create File just ignore
			// and file will be null
		}
		return file;
	}

	/**
	 * Get the base location from the current model (local file system)
	 */
	private String getBaseLocation(IStructuredModel model) {
		String result = null;

		// get the base location from the current model
		if (model != null) {
			result = model.getBaseLocation();

			IPath path = new Path(result);
			if (path.segmentCount() > 1) {
				IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
				if (file.exists()) {
					String baseLocation = null;
					if (file.getLocation() != null) {
						baseLocation = file.getLocation().toString();
					}
					if (baseLocation == null && file.getLocationURI() != null) {
						baseLocation = file.getLocationURI().toString();
					}
					if (baseLocation == null) {
						baseLocation = file.getFullPath().toString();
					}
					result = baseLocation;
				}
			}
		}
		return result;
	}
}
