/*******************************************************************************
 * Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is 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:
 *     Exadel, Inc. and Red Hat, Inc. - initial API and implementation
 ******************************************************************************/ 
package org.jboss.tools.common.core.resources;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URI;

import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IJarEntryResource;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.internal.ui.javaeditor.JarEntryEditorInput;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.ui.*;
import org.eclipse.ui.editors.text.ILocationProvider;
import org.eclipse.ui.internal.part.NullEditorInput;
import org.eclipse.ui.part.FileEditorInput;
import org.jboss.tools.common.model.XModel;
import org.jboss.tools.common.model.XModelObject;
import org.jboss.tools.common.model.XModelObjectConstants;
import org.jboss.tools.common.model.filesystems.FileSystemsHelper;
import org.jboss.tools.common.model.filesystems.impl.*;
import org.jboss.tools.common.model.plugin.ModelPlugin;
import org.jboss.tools.common.model.project.IModelNature;
import org.jboss.tools.common.model.util.*;
import org.jboss.tools.common.model.ui.ModelUIPlugin;
import org.jboss.tools.common.model.ui.editor.*;

public class XModelObjectEditorInput extends FileEditorInput implements IModelObjectEditorInput {
	private XModelObject object;
	private XModelObjectCache cache;
	
	private XModelObjectEditorInput(XModelObject object) {
		super(getFileByObject(object));
		this.object = object;
		cache = new XModelObjectCache(object);
	}

	/**
	 * Returns true if XModelObject corresponding to the file is replaced.
	 * 
	 * @return
	 */
	public boolean updateXModelObject() {
		IFile file = getFile();
		XModelObject o = EclipseResourceUtil.createObjectForResource(file);
		if(o != null && o != object) {
			object = o;
			cache = new XModelObjectCache(object);
			return true;
		}
		return false;
	}
	
	private static XModelObject getMainObject(XModelObject object) {
		if(object instanceof FileAnyAuxiliaryImpl) {
			XModelObject main = ((FileAnyAuxiliaryImpl)object).getMainObject();
			if(main != null) object = main;
		}
		return object;
	}
	
	public static IModelObjectEditorInput createInstance(XModelObject object) {
		object = getMainObject(object);
		IFile f = getFileByObject(object);
		if(f == null && object instanceof FileAnyImpl) { 
			XModelObject p = object.getParent();
			if(p instanceof FolderImpl) {
				String path = ((FileAnyImpl)object).getAbsolutePath();
				if(path != null) {
					return new ModelObjectLocationEditorInput(object, new Path(path));
				}
			}
			return new ModelObjectStorageEditorInput(object);
		}
		return (f == null) ? null : new XModelObjectEditorInput(object);
	}

	public XModelObject getXModelObject() {
		XModelObject o = cache.getObject();
		if(o != null) object = o;
		return o;
	}

	public boolean exists() {
		IFile f = getFile();
		return f != null && f.exists();
	}

	public ImageDescriptor getImageDescriptor()	{
		return null;
	}

	public String getName()	{
		return object.getPresentationString();
	}

	public String getToolTipText() {
		IFile f = (IFile)EclipseResourceUtil.getResource(object);
		if(f != null && f.exists()) return f.getFullPath().makeRelative().toString();
		return object.getPresentationString();
	}

	public Object getAdapter(Class adapter)	{
		if(adapter == XModelObject.class) return getXModelObject();
		Object result = null;
		if (IFile.class.isAssignableFrom(adapter)) {
			result = EclipseResourceUtil.getResource(object);
			if (result != null && !adapter.isAssignableFrom(result.getClass())) result = null;
		} else {
			result = super.getAdapter(adapter);
		}
		return result; 
	}
	
	public boolean equals(Object o)	{
		getXModelObject();
		if (this == o) return true;
		if (o instanceof FileEditorInput) {
			if(getFile() == null) return false;
			FileEditorInput other = (FileEditorInput)o;
			IFile f1 = getFile();
			IFile f2 = other.getFile();
			if(f1 == null || f2 == null) return f1 == f2;
			if(f1.equals(f2)) return true;
			IPath loc1 = f1.getLocation();
			IPath loc2 = f2.getLocation();
			if(loc1 == null || loc2 == null) return loc1 == loc2;
			return loc1.equals(loc2);
		}
		if(o instanceof XModelObjectEditorInput) {
			return object.equals(((XModelObjectEditorInput)o).getXModelObject());
		}
		return false;
	}

	public IStorage getStorage() {
		return getFile();
	}

	public IFile getFile() {
		return super.getFile();
	}
	
	public boolean isFileChanged() {
		XModelObject o = getXModelObject();
		IFile f1 = super.getFile();
		if (o == null || o.getPath() == null) return false;
		IFile f2 = getFileByObject(o);
		return !f1.equals(f2);
	}
	
	public void synchronize() {
		XModelObject o = getXModelObject();
		IFile f1 = super.getFile();
		if (o == null || o.getPath() == null) return;
		IFile f2 = getFileByObject(o);
		if(!f1.equals(f2) && f2 != null) hackSetFile(f2);
	}
	
	private void hackSetFile(IFile f) {
		try {
			Field field = FileEditorInput.class.getDeclaredField("file"); //$NON-NLS-1$
			field.setAccessible(true);
			field.set(this, f);
		} catch (NoSuchFieldException e) {
			ModelUIPlugin.getPluginLog().logError(e);
		} catch (IllegalArgumentException e) {
			ModelUIPlugin.getPluginLog().logError(e);
		} catch (IllegalAccessException e) {
			ModelUIPlugin.getPluginLog().logError(e);
		}
	}
	
	private static IFile getFileByObject(XModelObject object) {
		return (IFile)EclipseResourceUtil.getResource(object);
	}
	
	public static IEditorInput checkInput(IEditorInput input) {
		if(input instanceof IModelObjectEditorInput) return input;
		if(input instanceof ILocationProvider) return convertExternalInput((ILocationProvider)input);
		if(input instanceof IFileEditorInput) return convertFileInput((IFileEditorInput)input);
		if(input instanceof IStorageEditorInput) return convertStorageEditorInput((IStorageEditorInput)input);
		if(input instanceof IURIEditorInput) {
			URI uri = ((IURIEditorInput)input).getURI();
			String f = uri.getPath();
			XModelObject o = EclipseResourceUtil.createObjectForLocation(f);
			return (o == null || o.getFileType() != XModelObject.FILE) ? (IEditorInput)input : new ModelObjectLocationEditorInput(getMainObject(o), new Path(f));
		}
		return input;
	}
	
	private static IEditorInput convertFileInput(IFileEditorInput input) {
		IFileEditorInput fi = (IFileEditorInput)input;
		IFile f = fi.getFile();
		if(f != null && !f.isSynchronized(IResource.DEPTH_INFINITE)) {
			try {
				f.refreshLocal(IResource.DEPTH_INFINITE, null);
			} catch (CoreException e) {
				//ignore
			}
		}
		XModelObject o = EclipseResourceUtil.getObjectByResource(f);
		if(o == null) {
			o = EclipseResourceUtil.createObjectForResource(f);
		}
		return (o == null || o.getFileType() != XModelObject.FILE) ? input : new XModelObjectEditorInput(getMainObject(o));
	}
	
	private static IEditorInput convertExternalInput(ILocationProvider input) {
		XModelObject o = EclipseResourceUtil.createObjectForLocation(input.getPath(input).toString());
		return (o == null || o.getFileType() != XModelObject.FILE) ? (IEditorInput)input : new ModelObjectLocationEditorInput(getMainObject(o), input.getPath(input));
	}
	
	private static IEditorInput convertStorageEditorInput(IStorageEditorInput input) {
		if(input instanceof JarEntryEditorInput) {
			IProject project = null;
			JarEntryEditorInput j = (JarEntryEditorInput)input;
			if(j.getStorage() instanceof IJarEntryResource) {
				IJarEntryResource file = (IJarEntryResource)j.getStorage();
				String jarFile = file.getPackageFragmentRoot().getPath().toString();
				if(file.getPackageFragmentRoot().getResource() != null) {
					jarFile = file.getPackageFragmentRoot().getResource().getLocation().toString();
				}
				String entry = file.getName();
				IJarEntryResource r = file;
				while(r != null && r.getParent() instanceof IJarEntryResource) {
					r = (IJarEntryResource)r.getParent();
					entry = r.getName() + "/" + entry;
				}
				if(r != null && r.getParent() instanceof IPackageFragment) {
					IPackageFragment pf = (IPackageFragment)r.getParent();
					IJavaProject jp = pf.getJavaProject();
					if(jp != null) project = jp.getProject();
					while(pf != null) {
						String p = pf.getElementName();
						entry = p + "/" + entry;
						if(pf.getParent() instanceof IPackageFragment) {
							pf = (IPackageFragment)pf.getParent();
						} else {
							pf = null;
						}
					}
				} else if(r != null && r.getPackageFragmentRoot() != null) {
					IPackageFragmentRoot root = r.getPackageFragmentRoot();
					if(root.getJavaProject() != null) {
						project = root.getJavaProject().getProject();
					}
				}
				IEditorInput result = createJarEntryEditorInput(project, jarFile, entry);
				if(result != null) return result;
			}
		}
		String[] entryInfo = parseJarEntryFileInput(input);
		if(entryInfo == null) return input;
		String jarFile = entryInfo[0];
		String entry = entryInfo[1];
		IEditorInput result = createJarEntryEditorInput(null, jarFile, entry);
		return (result == null || result instanceof NullEditorInput) ? input : result;
	}

	public static String[] parseJarEntryFileInput(IStorageEditorInput input) {
		IStorage storage = null;
		try {
			storage = input.getStorage();
		} catch (CoreException e) {
			// ignore
		}
		return storage == null ? null : parseJarEntryFile(storage);
	}

	public static String[] parseJarEntryFile(IStorage storage) {
		if(storage == null) return null;
		String s = storage.toString();
		if(!s.startsWith("JarEntryFile[")) return null; //$NON-NLS-1$
		s = s.substring("JarEntryFile[".length()); //$NON-NLS-1$
		int i = s.indexOf("::"); //$NON-NLS-1$
		if(i < 0) return null;
		String jarFile = s.substring(0, i);
		String entry = storage.getFullPath().toString();
		return new String[]{jarFile, entry};
	}
	
	public static XModelObject getJarEntryObject(IProject p, String jarFile, String entry) {
		File jf = new File(jarFile);
		try {
			jf = new File(new File(jarFile).getCanonicalPath());
		} catch (IOException e) {
			ModelPlugin.getDefault().logError(e);
		}
		if(p == null) {
			IFile f = EclipseResourceUtil.getFile(jarFile);
			if(f == null) return null;
			p = f.getProject();
		}
		if(p == null) return null;
		IModelNature n = EclipseResourceUtil.getModelNature(p);
		XModel model = null;
		if(n != null) {
			model = n.getModel();
		} else {
			XModelObject o = EclipseResourceUtil.createObjectForResource(p);
			if(o != null) model = o.getModel();
		}
		if(model == null) return null;
		XModelObject[] fs = FileSystemsHelper.getFileSystems(model).getChildren();
		for (XModelObject s: fs) {
			String loc = Paths.expand(s.get(XModelObjectConstants.ATTR_NAME_LOCATION), model.getProperties());
			if(new File(loc).equals(jf)) {
				XModelObject result = s.getChildByPath(entry);
				if(result == null && entry != null) {
					int q = entry.indexOf('/');
					int d = entry.indexOf('.');
					if(q > d && d >= 0) {
						String entry1 = entry.substring(0, q).replace('.', '/') + entry.substring(q);
						result = s.getChildByPath(entry1);
					}
				}
				if(result != null) return result;
			}
		}
		return (n == null) ? null : n.getModel().getByPath("/" + entry);		 //$NON-NLS-1$
	}
	
	public static IEditorInput createJarEntryEditorInput(IProject project, String jarFile, final String entry) {
		XModelObject o = getJarEntryObject(project, jarFile, entry);
		if(o != null) return new ModelObjectJarEntryEditorInput(o, jarFile, entry);
		return XModelObjectEditorInputFactory.createNullEditorInput(entry);
	}
	
	public String getFactoryId() {
		return XModelObjectEditorInputFactory.getFactoryId();
	}

	public void saveState(IMemento memento) {
		XModelObjectEditorInputFactory.saveState(memento, this);
	}
	
	public void revalidate() {
		IFile f = getFile();
		if(f == null || f.equals(super.getFile())) return;
		try {
			Field field = FileEditorInput.class.getDeclaredField("file"); //$NON-NLS-1$
			field.setAccessible(true);
			field.set(this, f);
		} catch (NoSuchFieldException e) {
			ModelUIPlugin.getPluginLog().logError(e);
		} catch (IllegalArgumentException e) {
			ModelUIPlugin.getPluginLog().logError(e);
		} catch (IllegalAccessException e) {
			ModelUIPlugin.getPluginLog().logError(e);
		}
	}

}
