/*
 * 作成日: 2008/08/05
 * 著作権: Copyright (c) 2005 ZIGEN
 * ライセンス：Eclipse Public License - v 1.0
 * 原文：http://www.eclipse.org/legal/epl-v10.html
 */
package zigen.plugin.db.ext.s2jdbc.wizards;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.internal.corext.refactoring.StubTypeContext;
import org.eclipse.jdt.internal.corext.refactoring.TypeContextChecker;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.ui.dialogs.FilteredTypesSelectionDialog;
import org.eclipse.jdt.internal.ui.dialogs.StatusInfo;
import org.eclipse.jdt.internal.ui.dialogs.TextFieldNavigationHandler;
import org.eclipse.jdt.internal.ui.refactoring.contentassist.CompletionContextRequestor;
import org.eclipse.jdt.internal.ui.refactoring.contentassist.ControlContentAssistHelper;
import org.eclipse.jdt.internal.ui.refactoring.contentassist.JavaTypeCompletionProcessor;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.DialogField;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.IDialogFieldListener;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.IListAdapter;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.IStringButtonAdapter;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.LayoutUtil;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.ListDialogField;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.StringButtonDialogField;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.PreferenceDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.dialogs.PreferencesUtil;

import zigen.plugin.db.ext.s2jdbc.util.GenericUtil;

public class NewEntityServiceWizardPage extends AbstractWizardPage {
	
	class ASTVisitorImpl extends ASTVisitor {
		
		LinkedHashMap<String, String> idMap = new LinkedHashMap<String, String>();
		
		public LinkedHashMap<String, String> getIdMap() {
			return idMap;
		}
		
		/**
		 * @Id などのMarkerアノテーション用
		 */
		public boolean visit(MarkerAnnotation node) {
			if (node.getParent() instanceof FieldDeclaration) {
				FieldDeclaration fd = (FieldDeclaration) node.getParent();
				String typeName = ((SimpleType) fd.getType()).getName().getFullyQualifiedName();
				

				Name name = ((SimpleType) fd.getType()).getName();
				// System.out.println(name.getFullyQualifiedName());
				
				List<VariableDeclarationFragment> list = fd.fragments();
				for (VariableDeclarationFragment vdf : list) {
					
					// @Idのみ追加するように修正
					if ("Id".equals(node.getTypeName().getFullyQualifiedName())) {
						String fieldName = vdf.getName().getFullyQualifiedName();
						idMap.put(fieldName, typeName);
					}
					
				}
			}
			return true;
		}
		
		public boolean visit(SingleMemberAnnotation node) {
			// System.out.println("SingleMemberAnnotation " + node.toString());
			return true;
		}
		
		public boolean visit(NormalAnnotation node) {
			// System.out.println("NormalAnnotation " + node.toString());
			return true;
		}
		
	}
	
	public static String DEFAULT_SUPER_CLASS = "org.seasar.extension.jdbc.service.S2AbstractService"; //$NON-NLS-1$
	
	String packageName;
	
	public NewEntityServiceWizardPage(IStructuredSelection selection) {
		super(selection, zigen.plugin.db.ext.s2jdbc.wizards.NewWizardMessages.getString("NewEntityServiceWizardPage.5")); //$NON-NLS-1$
		TypeFieldsAdapter adapter = new TypeFieldsAdapter(null);
		
		fEntityClassDialogField = new StringButtonDialogField(adapter);
		fEntityClassDialogField.setDialogFieldListener(adapter);
		fEntityClassDialogField.setLabelText(zigen.plugin.db.ext.s2jdbc.wizards.NewWizardMessages.getString("NewEntityServiceWizardPage.4")); //$NON-NLS-1$
		fEntityClassDialogField.setButtonLabel(zigen.plugin.db.ext.s2jdbc.wizards.NewWizardMessages.getString("NewEntityServiceWizardPage.3")); //$NON-NLS-1$
		
		// setSuperClass(DEFAULT_SUPER_CLASS, false);
		setSuperClass(DEFAULT_SUPER_CLASS, true); // SuperClass変更可能
		
	}
	
	@Override
	public void addPreTypeNameControl(Composite parent, int nColumns) {
		createEntityControls(parent, nColumns);
	}
	
	@Override
	public void addAfterTypeNameControl(Composite parent, int nColumns) {
		;// 実装なし
	}
	
	protected String constructCUContent(ICompilationUnit cu, String typeContent, String lineDelimiter) throws CoreException {
		// System.out.println("□constructCUContent");
		String typeComment = getTypeComment(cu, lineDelimiter);
		
		IPackageFragment pack = (IPackageFragment) cu.getParent();
		
		StringBuffer buf = new StringBuffer();
		if (!pack.isDefaultPackage()) {
			buf.append("package ").append(pack.getElementName()).append(';'); //$NON-NLS-1$
			packageName = pack.getElementName();
		}
		
		buf.append(lineDelimiter).append(lineDelimiter);
		if (typeComment != null)
			buf.append(typeComment).append(lineDelimiter);
		
		buf.append(typeContent);
		return buf.toString();
	}
	
	protected void createTypeMembers(IType type, ImportsManager imports, IProgressMonitor monitor) throws CoreException {
		// System.out.println("□createTypeMembers");
		try {
			boolean doMain = false;
			boolean doConstr = false;
			boolean doInherited = true;
			// 抽象メソッドの出力
			createInheritedMethods(type, doConstr, doInherited, imports, new SubProgressMonitor(monitor, 1));
			
			// PrimaryKey検索メソッドの作成
			String entityClass = getEntityClass();
			String superClass = super.getSuperClass();// 親クラスのgetSuperClassを呼ぶこと
			
			if (entityClass != null && !"".equals(entityClass)) {
				String entity = imports.addImport(entityClass);
				
				boolean hasSelectMethod = false;
				if (superClass != null && !"".equals(superClass)) {
					IType superType = getPackageFragmentRoot().getJavaProject().findType(superClass);
					hasSelectMethod = hasSelectMethod(superType);
				}
				
				if (hasSelectMethod) {
					IType eType = getPackageFragmentRoot().getJavaProject().findType(entityClass);
					ASTParser parser = ASTParser.newParser(AST.JLS3);
					parser.setResolveBindings(true);
					parser.setSource(eType.getSource().toCharArray());
					parser.setKind(ASTParser.K_COMPILATION_UNIT);
					CompilationUnit unit = (CompilationUnit) parser.createAST(new NullProgressMonitor());
					ASTVisitorImpl visitor = new ASTVisitorImpl();
					unit.accept(visitor);
					
					Map<String, String> idMap = visitor.getIdMap();
					
					StringBuffer p1 = new StringBuffer();
					StringBuffer p2 = new StringBuffer();
					Iterator<String> itr = idMap.keySet().iterator();
					int cnt = 0;
					while (itr.hasNext()) {
						String propertyName = itr.next();
						String typeName = idMap.get(propertyName);
						// TODO:java.math.BigDecimalなどの場合は、Importが必要だが・・・どうするかな・・
						if (cnt == 0) {
							p1.append(typeName + " " + propertyName); //$NON-NLS-1$
							p2.append(propertyName);
						} else {
							p1.append(", " + typeName + " " + propertyName); //$NON-NLS-1$ //$NON-NLS-2$
							p2.append(", " + propertyName); //$NON-NLS-1$
						}
						cnt++;
					}
					
					if (cnt > 0) {
						// EntityからPrimaryKey(ID)を取得して、findByPrimaryKeyメソッドを生成する
						StringBuffer buf = new StringBuffer();
						buf.append("public "); //$NON-NLS-1$
						buf.append(entity);
						buf.append(" findByPrimaryKey("); //$NON-NLS-1$
						buf.append(p1.toString());
						buf.append("){").append(LS); //$NON-NLS-1$
						
						buf.append("return super.select().id("); //$NON-NLS-1$
						buf.append(p2.toString());
						buf.append(").getSingleResult();").append(LS); //$NON-NLS-1$
						buf.append("}").append(LS); //$NON-NLS-1$
						// buf.append(LS);
						// Methodの作成
						type.createMethod(buf.toString(), null, false, monitor);
						
						// PrimaryKeyでソートしたfindAllメソッドを生成する
						buf = new StringBuffer();
						buf.append("\t@").append(imports.addImport("java.lang.Override")).append(LS); //$NON-NLS-1$ //$NON-NLS-2$
						
						buf.append("public "); //$NON-NLS-1$
						buf.append(imports.addImport("java.util.List"));
						buf.append("<");
						buf.append(entity);
						buf.append(">");
						buf.append(" findAll()"); //$NON-NLS-1$
						buf.append("{").append(LS); //$NON-NLS-1$
						buf.append("return super.select().orderBy(\""); //$NON-NLS-1$
						buf.append(p2.toString());
						buf.append("\").getResultList();").append(LS); //$NON-NLS-1$
						buf.append("}").append(LS); //$NON-NLS-1$
						// buf.append(LS);
						// Methodの作成
						type.createMethod(buf.toString(), null, false, monitor);
						
					}
				}
			}
			if (monitor != null)
				monitor.done();
			
		} catch (RuntimeException e) {
			e.printStackTrace();
		}
		
	}
	
	private boolean hasSelectMethod(IType type) throws JavaModelException {
		IMethod[] ms = type.getMethods();
		for (IMethod m : ms) {
			if ("select".equals(m.getElementName())) {
				return true;
			}
		}
		return false;
	}
	
	private String getTypeNameWithoutParameters() {
		String typeNameWithParameters = getTypeName();
		int angleBracketOffset = typeNameWithParameters.indexOf('<');
		if (angleBracketOffset == -1)
			return typeNameWithParameters;
		else
			return typeNameWithParameters.substring(0, angleBracketOffset);
	}
	
	List<String> superClassParameters;
	
	public String getSuperClass() {
		
		String superClass = super.getSuperClass();
		
		if (fCurrType != null)
			try {
				if (superClass != null) {
					IType sType = getPackageFragmentRoot().getJavaProject().findType(superClass);
					if (sType != null) {
						String wk = sType.getFullyQualifiedParameterizedName();
						
						superClassParameters = GenericUtil.getParametars(wk);
						StringBuffer sb = new StringBuffer();
						sb.append(superClass);
						
						if (superClassParameters.size() > 0) {
							sb.append("<"); //$NON-NLS-1$
							
							String entity = getEntityClass();
							if (entity != null && !"".equals(entity)) { //$NON-NLS-1$
								sb.append(entity);
							} else {
								sb.append("Object"); //$NON-NLS-1$
							}
							sb.append(">"); //$NON-NLS-1$
						}
						
						superClass = sb.toString();
						
					}
				}
				
			} catch (JavaModelException e) {
				e.printStackTrace();
			}
		
		return superClass;
		
	}
	
	protected IStatus typeNameChanged() {
		
		String typeName = getTypeNameWithoutParameters();
		IPackageFragment pack = getPackageFragment();
		ICompilationUnit cu = pack.getCompilationUnit(getCompilationUnitName(typeName));
		fCurrType = cu.getType(typeName);
		IStatus status = super.typeNameChanged();
		// String typeName = getTypeName();
		if (typeName != null && !"".equals(typeName)) { //$NON-NLS-1$
			if (typeName.endsWith("Service")) { //$NON-NLS-1$
				return status;
			} else {
				StatusInfo errStatus = new StatusInfo();
				errStatus.setError(zigen.plugin.db.ext.s2jdbc.wizards.NewWizardMessages.getString("NewEntityServiceWizardPage.2")); //$NON-NLS-1$
				return errStatus;
			}
		}
		return status;
	}
	
	protected String getFullyQualifiedName(IType type) {
		String pkg = JavaModelUtil.getTypeContainerName(type);
		if (pkg != null) {
			return pkg + "." + type.getElementName(); //$NON-NLS-1$
		} else {
			return type.getElementName();
		}
	}
	
	// ////////////////////////////////////////
	
	protected StringButtonDialogField fEntityClassDialogField;
	
	protected StubTypeContext fEntityClassStubTypeContext;
	
	protected IStatus fEntityClassStatus;
	
	protected class TypeFieldsAdapter implements IStringButtonAdapter, IDialogFieldListener, IListAdapter, SelectionListener {
		
		public void changeControlPressed(DialogField field) {
			typePageChangeControlPressed(field);
		}
		
		public void customButtonPressed(ListDialogField field, int index) {
			typePageCustomButtonPressed(field, index);
		}
		
		public void selectionChanged(ListDialogField listdialogfield) {}
		
		public void dialogFieldChanged(DialogField field) {
			typePageDialogFieldChanged(field);
		}
		
		public void doubleClicked(ListDialogField listdialogfield) {}
		
		public void widgetSelected(SelectionEvent e) {
			typePageLinkActivated(e);
		}
		
		public void widgetDefaultSelected(SelectionEvent e) {
			typePageLinkActivated(e);
		}
		
		private TypeFieldsAdapter() {}
		
		TypeFieldsAdapter(TypeFieldsAdapter typefieldsadapter) {
			this();
		}
	}
	
	protected void typePageLinkActivated(SelectionEvent e) {
		IJavaProject project = getJavaProject();
		if (project != null) {
			PreferenceDialog dialog = PreferencesUtil.createPropertyDialogOn(getShell(), project.getProject(), "org.eclipse.jdt.ui.propertyPages.CodeTemplatePreferencePage", null, null); //$NON-NLS-1$
			dialog.open();
		} else {
			String title = ""; //$NON-NLS-1$
			String message = ""; //$NON-NLS-1$
			MessageDialog.openInformation(getShell(), title, message);
			
		}
	}
	
	protected void typePageChangeControlPressed(DialogField field) {
		if (field == fEntityClassDialogField) {
			IType type = chooseEntityClass();
			if (type != null) {
				fEntityClassDialogField.setText(getFullyQualifiedName(type));
			}
		}
	}
	
	protected void typePageDialogFieldChanged(DialogField field) {
		String fieldName = null;
		if (field == fEntityClassDialogField) {
			fEntityClassStatus = entityClassChanged();
			fieldName = "NewPagerServiceWizardPage.entityclass"; //$NON-NLS-1$
		}
		handleFieldChanged(fieldName);
	}
	
	protected void typePageCustomButtonPressed(DialogField field, int index) {}
	
	protected void createEntityControls(Composite composite, int nColumns) {
		fEntityClassDialogField.doFillIntoGrid(composite, nColumns);
		Text text = fEntityClassDialogField.getTextControl(null);
		LayoutUtil.setWidthHint(text, getMaxFieldWidth());
		JavaTypeCompletionProcessor superClassCompletionProcessor = new JavaTypeCompletionProcessor(false, false, true);
		superClassCompletionProcessor.setCompletionContextRequestor(new CompletionContextRequestor() {
			
			public StubTypeContext getStubTypeContext() {
				return getEntityClassStubTypeContext();
			}
			
		});
		ControlContentAssistHelper.createTextContentAssistant(text, superClassCompletionProcessor);
		TextFieldNavigationHandler.install(text);
	}
	
	protected StubTypeContext getEntityClassStubTypeContext() {
		if (fEntityClassStubTypeContext == null) {
			String typeName;
			if (fCurrType != null)
				typeName = getTypeName();
			else
				typeName = "$$__$$"; //$NON-NLS-1$
			fEntityClassStubTypeContext = TypeContextChecker.createSuperClassStubTypeContext(typeName, getEnclosingType(), getPackageFragment());
		}
		return fEntityClassStubTypeContext;
	}
	
	protected IStatus entityClassChanged() {
		StatusInfo status = new StatusInfo();
		IPackageFragmentRoot root = getPackageFragmentRoot();
		fEntityClassDialogField.enableButton(root != null);
		fEntityClassStubTypeContext = null;
		String sclassName = getEntityClass();
		if (sclassName.length() == 0)
			return status;
		
		if (root != null) {
			org.eclipse.jdt.core.dom.Type type = TypeContextChecker.parseSuperClass(sclassName);
			
			if (type == null) {
				status.setError(""); //$NON-NLS-1$
				return status;
			}
			// if ((type instanceof ParameterizedType) &&
			// !JavaModelUtil.is50OrHigher(root.getJavaProject())) {
			// status.setError();
			// return status;
			// }
		} else {
			status.setError(""); //$NON-NLS-1$
		}
		
		// Entity名を使ってService名を設定する
		setTypeName(sclassName.substring(sclassName.lastIndexOf(".") + 1) + "Service", true); //$NON-NLS-1$ //$NON-NLS-2$
		
		return status;
	}
	
	public String getEntityClass() {
		return fEntityClassDialogField.getText();
	}
	
	public void setEntityClass(String name, boolean canBeModified) {
		fEntityClassDialogField.setText(name);
		fEntityClassDialogField.setEnabled(canBeModified);
	}
	
	protected IType chooseEntityClass() {
		IJavaProject project = getJavaProject();
		if (project == null)
			return null;
		IJavaElement elements[] = {project};
		org.eclipse.jdt.core.search.IJavaSearchScope scope = SearchEngine.createJavaSearchScope(elements);
		FilteredTypesSelectionDialog dialog = new FilteredTypesSelectionDialog(getShell(), false, getWizard().getContainer(), scope, IJavaSearchConstants.CLASS);
		dialog.setTitle(zigen.plugin.db.ext.s2jdbc.wizards.NewWizardMessages.getString("NewEntityServiceWizardPage.1"));// Entityの選択 //$NON-NLS-1$
		dialog.setMessage(zigen.plugin.db.ext.s2jdbc.wizards.NewWizardMessages.getString("NewEntityServiceWizardPage.0"));// Entity選択してください //$NON-NLS-1$
		dialog.setInitialPattern(getEntityClass());
		if (dialog.open() == 0)
			return (IType) dialog.getFirstResult();
		else
			return null;
	}
	
	protected void setFocus() {
		if (fEntityClassDialogField != null)
			fEntityClassDialogField.setFocus();
	}
	
}