/*******************************************************************************
 * 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.model.files.handlers;

import java.io.*;
import java.text.MessageFormat;
import java.util.*;

import org.eclipse.core.resources.IResource;
import org.eclipse.swt.widgets.Display;

import org.jboss.tools.common.meta.XMapping;
import org.jboss.tools.common.meta.XModelMetaData;
import org.jboss.tools.common.meta.action.*;
import org.jboss.tools.common.meta.action.impl.*;
import org.jboss.tools.common.meta.action.impl.handlers.DefaultCreateHandler;
import org.jboss.tools.common.meta.impl.XMetaDataConstants;
import org.jboss.tools.common.model.*;
import org.jboss.tools.common.model.filesystems.impl.*;
import org.jboss.tools.common.model.loaders.EntityRecognizerContext;
import org.jboss.tools.common.model.loaders.impl.MappedEntityRecognizer;
import org.jboss.tools.common.model.plugin.ModelMessages;
import org.jboss.tools.common.model.util.*;
import org.jboss.tools.common.util.FileUtil;

public class CreateFileSupport extends SpecialWizardSupport {
	public static String INITIAL_FOLDER_PROPERTY = "initialFolder"; //$NON-NLS-1$
	public static String INITIAL_FILE_PROPERTY = "initialFile"; //$NON-NLS-1$
	
	static final String ATTR_TEMPLATE = "template"; //$NON-NLS-1$
	static final String ATTR_FOLDER = "folder"; //$NON-NLS-1$

	protected TargetHolder targetHolder = new TargetHolder();
	TreeMap<String,String> versionEntities = new TreeMap<String,String>();
	boolean useVersions = false;

	public static interface DefaultVersionResolver {
		public String resolve(String[] versionList, XModelObject context);
	}
	
	public void reset() {
		targetHolder.setAction(action);
		if(hasTemplate()) {
			String[] s = getPageTemplateList();
			setValueList(0, ATTR_TEMPLATE, s);
			if(s.length > 0) {
				//take from preferences
				String defaultPageTemplate = getDefaultPageTemplate();
				if (defaultPageTemplate == null) {
					defaultPageTemplate = s[0];
				} else {
					boolean bFound = false;
					for (int i = 0; i < s.length && !bFound; i++) {
						if (s[i].equals(defaultPageTemplate)) bFound = true; 
					}
					if (!bFound) defaultPageTemplate = s[0];
				}
				setAttributeValue(0, ATTR_TEMPLATE, defaultPageTemplate);
			}
		}
		targetHolder.target = getTarget();
		IResource r = (IResource)getTarget().getAdapter(IResource.class);
		if(r == null) {
			setAttributeValue(0, ATTR_FOLDER, ""); //$NON-NLS-1$
			targetHolder.revalidate(null);
		} else {
			targetHolder.revalidate(r.getFullPath().toString());
			setAttributeValue(0, ATTR_FOLDER, "" + targetHolder.path); //$NON-NLS-1$
		}
		String initialFolder = p.getProperty(INITIAL_FOLDER_PROPERTY);
		if(initialFolder != null && initialFolder.length() > 0) {
			setAttributeValue(0, ATTR_FOLDER, initialFolder);
		}
		String initialFile = p.getProperty(INITIAL_FILE_PROPERTY);
		if(initialFile != null && initialFile.length() > 0) {
			setAttributeValue(0, XModelObjectConstants.ATTR_NAME, initialFile);
		}
		initVersions();
	}
	
	protected String getDefaultPageTemplate() {
		return null;
	}
	void initVersions() {
		useVersions = false;
		versionEntities.clear();
		XModelMetaData meta = getTarget().getModel().getMetaData();
		XMapping m = meta.getMapping("FileVersions"); //$NON-NLS-1$
		if(m == null) return;
		String entityVersion = action.getProperty("entityVersion"); //$NON-NLS-1$
		if(entityVersion == null || entityVersion.length() == 0) return;
		if(getAttributeValue(0, "version") == null) return; //$NON-NLS-1$
		useVersions = true;
		String[] keys = m.getKeys();
		for (int i = 0; i < keys.length; i++) {
			if(!keys[i].startsWith(entityVersion)) continue;
			String entity = m.getValue(keys[i]);
			if(meta.getEntity(entity) == null) continue;
			String version = keys[i].substring(entityVersion.length());
			versionEntities.put(version, entity);
		}
		if(versionEntities.size() == 0 && action.getProperty(XMetaDataConstants.ENTITY) != null) {
			versionEntities.put("default", action.getProperty(XMetaDataConstants.ENTITY)); //$NON-NLS-1$
		}
		String[] versionList = (String[])versionEntities.keySet().toArray(new String[0]);
		setValueList(0, "version", versionList); //$NON-NLS-1$
		if(versionList.length > 0) {
			String defaultVersion = versionList[versionList.length - 1];
			String defaultVersionResolver = action.getProperty("defaultVersionResolver"); //$NON-NLS-1$
			if(defaultVersionResolver != null) {
				DefaultVersionResolver resolver = (DefaultVersionResolver)ModelFeatureFactory.getInstance().createFeatureInstance(defaultVersionResolver);
				if(resolver != null) {
					String d = resolver.resolve(versionList, getTarget());
					if(d != null) defaultVersion = d;
				}
			}
			setAttributeValue(0, "version", defaultVersion); //$NON-NLS-1$
		}
	}
	
	private boolean hasTemplate() {
		return findAttribute(0, ATTR_TEMPLATE) != null; 
	}

	public void action(String name) throws XModelException {
		if(FINISH.equals(name)) {
			if(!checkResource()) return;
			execute();
			setFinished(true);
		} else if(CANCEL.equals(name)) {
			setFinished(true);
		} else if(HELP.equals(name)) {
			help();
		}
	}

	public String[] getActionNames(int stepId) {
		return new String[]{FINISH, CANCEL, HELP};
	}

	protected void execute() throws XModelException {
		Properties p = extractStepData(0);
		String path = p.getProperty(XModelObjectConstants.ATTR_NAME);
		path = revalidatePath(path);
		XModelObject f = createFile(path);
		if(f != null) targetHolder.saveLastPath();
		if(f != null) open(f);		
	}
	
	protected boolean checkResource() {
		if(targetHolder.addPath.length() == 0) return true;
		ServiceDialog d = getTarget().getModel().getService();
		String message = MessageFormat.format(
				"Folder {0} does not exist. Do you want to create it?",
				targetHolder.path);
		int q = d.showDialog(ModelMessages.WARNING, message, new String[]{SpecialWizardSupport.OK, SpecialWizardSupport.CANCEL}, null, ServiceDialog.QUESTION);
		return q == 0;
	}
	
	public boolean isFieldEditorEnabled(int stepId, String name, Properties values) {
		String path = values.getProperty(XModelObjectConstants.ATTR_NAME);
		boolean c = canCreateFile(path);
		if(name.equals(ATTR_TEMPLATE)) {
			return c;
		}
		return true;
	}
	
	public boolean canCreateFile(String path) {
		if(targetHolder.target == null) return false;
		if(path == null || path.length() == 0 || path.indexOf('*') >= 0) return false;
		return isCorrectPath(path) && !fileExists(path);
	}
	
	boolean isCorrectPath(String path) {
		path = revalidatePath(path);
		if(path == null || path.equals(XModelObjectConstants.SEPARATOR) || path.indexOf("//") >= 0) return false; //$NON-NLS-1$
		  return true;
	}
	
	boolean fileExists(String path) {
		if(path == null || targetHolder.target == null) return false;
		path = revalidatePath(path);
		if(path.startsWith(XModelObjectConstants.SEPARATOR)) path = path.substring(1);
		return targetHolder.target.getChildByPath(path) != null;
	} 
	
	protected String revalidatePath(String path) {
		if(path == null || path.length() == 0) return path;
		if(!path.startsWith(XModelObjectConstants.SEPARATOR)) path = XModelObjectConstants.SEPARATOR + path;
		String extension = "." + action.getProperty(XModelObjectConstants.ATTR_NAME_EXTENSION); //$NON-NLS-1$
		if(path.lastIndexOf('.') < 0) {
			path += extension;
		} else {
			String ext = path.substring(path.lastIndexOf('.'));
			if(!ext.equals(extension) && extension.length() > 1) path += extension;
		}
		if(targetHolder.addPath.length() > 0) {
			path = targetHolder.addPath + path;
		}
		return path;
	}

	protected XModelObject createFile(String path) throws XModelException {
		if(!canCreateFile(path)) return null;
		try {
			String body = getTemplateBody();
			body = modifyBody(body);
			return createFile(targetHolder.target, path, body, extractStepData(0));
		} catch (IOException e) {
			throw new XModelException(e);
		}
	}
	
	protected String getTemplateBody() throws IOException {
		File templateFile = null;
		String template = getAttributeValue(0, ATTR_TEMPLATE);
		if(template != null && template.trim().length() > 0) {
			templateFile = findTemplate(template.trim());
			if(templateFile == null || !templateFile.isFile()) throw new IOException(
					MessageFormat.format("Template {0} is not found.", template));
		}		
		return (templateFile == null) ? "" : FileUtil.readFile(templateFile); //$NON-NLS-1$
	}
	
	protected String modifyBody(String body) throws IOException {
		return body;
	}
	
	protected void open(final XModelObject f) {
		Display.getDefault().asyncExec(new Runnable() {
			public void run() {        
				FindObjectHelper.findModelObject(f, FindObjectHelper.EVERY_WHERE);
			}
		});
	} 

	XModelObject createFile(XModelObject fs, String path, String body, Properties p) throws XModelException {
		StringTokenizer st = new StringTokenizer(path, XModelObjectConstants.SEPARATOR);
		int c = st.countTokens(), i = 0;
		while(i < c - 1) {
			String s = st.nextToken();
			XModelObject o = fs.getChildByPath(s);
			if(o == null) {
				o = fs.getModel().createModelObject("FileFolder", null); //$NON-NLS-1$
				o.setAttributeValue(XModelObjectConstants.ATTR_NAME, s);
				DefaultCreateHandler.addCreatedObject(fs, o, FindObjectHelper.IN_NAVIGATOR_ONLY);
				((FolderImpl)o).save();
			}
			fs = o;
			i++;
		}
		String s = st.nextToken();
		int dot = s.lastIndexOf('.');
		String n = s.substring(0, dot);
		String e = s.substring(dot + 1);
		String entity = getFileEntity(e);
		XModelObject f = XModelObjectLoaderUtil.createValidObject(fs.getModel(), entity, p);
		f.setAttributeValue(XModelObjectConstants.ATTR_NAME, n);
		f.setAttributeValue(XModelObjectConstants.ATTR_NAME_EXTENSION, e);
		if(body != null) f.setAttributeValue(XModelObjectConstants.ATTR_NAME_BODY, body);
		f = modifyCreatedObject(f);
		XModelObject fq = fs.getChildByPath(f.getPathPart());
		if(fq != null) return fq;
		DefaultCreateHandler.addCreatedObject(fs, f, FindObjectHelper.IN_NAVIGATOR_ONLY);
		if(f instanceof FileAnyImpl && f.getModelEntity().getAttribute(XModelObjectConstants.ATTR_NAME_ENCODING) != null) {
			String txt = ((FileAnyImpl)f).getAsText();
			String enc = XModelObjectLoaderUtil.getEncoding(txt);
			if(enc != null) f.setAttributeValue(XModelObjectConstants.ATTR_NAME_ENCODING, enc);
		}
		String modelPath = f.getPath();
		((FolderImpl)fs).saveChild(f);
		fs.getModel().update();
		if(f.getParent() == null) {
			//entity could change after saving child.
			f = f.getModel().getByPath(modelPath);
		}
		if(f != null) getProperties().put("created", f); //$NON-NLS-1$
		return f;
	}
	
	protected String getFileEntity(String extension) {
		if(useVersions) {
			String version = getAttributeValue(0, "version"); //$NON-NLS-1$
			String entity = version == null ? null : (String)versionEntities.get(version);
			if(entity != null) return entity;
		}
		
		String entity = null;
		if(extension.equals(action.getProperty(XModelObjectConstants.ATTR_NAME_EXTENSION))) {
			entity = action.getProperty(XMetaDataConstants.ENTITY);
		}
		if(entity == null) {
			entity = new MappedEntityRecognizer().getEntityName(new EntityRecognizerContext(extension));
		}
		if(entity != null && getTarget().getModel().getMetaData().getEntity(entity) != null) {
			return entity;
		}
		
		return "FileAny"; //$NON-NLS-1$
	}

	protected XModelObject modifyCreatedObject(XModelObject o) {
		return o;
	}
	
	protected File findTemplate(String name) {
		return null;
	}
	
	public String[] getPageTemplateList() {
		return new String[0];
	}
	
	protected final XModelObject getTargetFolder() {
		return targetHolder.target;
	}
	
	protected DefaultWizardDataValidator validator = createValidator();
    
	public WizardDataValidator getValidator(int step) {
		validator.setSupport(this, step);
		return validator;    	
	}
	
	protected DefaultWizardDataValidator createValidator() {
		return new Validator(); 
	}
	
	protected class Validator extends DefaultWizardDataValidator {
		public void validate(Properties data) {
			String folder = data.getProperty(ATTR_FOLDER);
			targetHolder.revalidate(folder);
			message = null;
			validateFolderName();
			if(message != null) return;
			validateFileName(data);
			if(message != null) return;
			String template = data.getProperty(ATTR_TEMPLATE);
			if(template != null && template.trim().length() > 0) {
				File templateFile = findTemplate(template.trim());
				if(templateFile == null || !templateFile.isFile()) {
					message = "Template does not exist.";
				}
			}
			if(message != null) return;
			super.validate(data);
		}
		
		String FORBIDDEN_INDICES = "\"\n\t*\\/:<>?|"; //$NON-NLS-1$
		protected void validateFileName(Properties data) {
			if(message != null) return;
			String fileName = data.getProperty(XModelObjectConstants.ATTR_NAME);
			if(fileName == null || fileName.length() == 0) return;
			if(fileName.equals(".")) { //$NON-NLS-1$
				message = "Incorrect file name.";
			} else if(fileName.endsWith(".") && fileName.indexOf('.') != fileName.lastIndexOf('.')) { //$NON-NLS-1$
				message = "File name must not end in a period.";
			} else {
				for (int i = 0; i < FORBIDDEN_INDICES.length(); i++) {
					if(fileName.indexOf(FORBIDDEN_INDICES.charAt(i)) >= 0) {
						message = MessageFormat.format(
								"File name must not contain character {0} .",
								FORBIDDEN_INDICES.charAt(i));
						return;
					}
				}				
			}
		}

		String FORBIDDEN_FOLDER_LETTERS = "\"\n\t*:<>?|"; //$NON-NLS-1$
		private void validateFolderName() {
			if(targetHolder.addPath.length() == 0) return;
			for (int i = 0; i < FORBIDDEN_FOLDER_LETTERS.length(); i++) {
				if(targetHolder.addPath.indexOf(FORBIDDEN_FOLDER_LETTERS.charAt(i)) >= 0) {
					message = MessageFormat.format(
							"Folder name must not contain character {0} .",
							FORBIDDEN_FOLDER_LETTERS.charAt(i));
					return;
				}
			}				
		}
		

		protected void validateAddFile(XEntityData[] ds, Properties data) {
			CreateFileHandler.validateNameAndExtension(action, data, null);
			if(targetHolder.target != null) {
				String entity = action.getProperty(XMetaDataConstants.ENTITY);
				if(entity == null) entity = getEntityData()[step].getModelEntity().getName();
				if(targetHolder.addPath == null || targetHolder.addPath.length() == 0) {
					if(!checkChild(targetHolder.target, entity, data)) return;
				}
			} else {
				message = "Cannot create file in specified folder.";
			}
		}
	
	}

	public String getFocusAttribute(int stepId) {
		if(stepId == 0) {
			return XModelObjectConstants.ATTR_NAME;
		}
		return super.getFocusAttribute(stepId);
	}

}
