/*
 * 作成日: 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.entity;

import java.util.Iterator;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.ISourceRange;
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.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.formatter.CodeFormatter;
import org.eclipse.jdt.internal.core.PackageFragment;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
import org.eclipse.jdt.internal.corext.util.CodeFormatterUtil;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.corext.util.Strings;
import org.eclipse.jdt.ui.CodeGeneration;
import org.eclipse.jdt.ui.CodeStyleConfiguration;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.ide.IDE;

import zigen.plugin.db.ext.jdt.CodeCreatorUtil;
import zigen.plugin.db.ext.jdt.ui.Messages;
import zigen.plugin.db.ext.jdt.ui.PackageSelectDialog;
import zigen.plugin.db.ext.s2jdbc.Activator;
import zigen.plugin.db.ext.s2jdbc.entity.rule.DefaultEntityMappingFactory;
import zigen.plugin.db.ext.s2jdbc.entity.rule.IEntityMappingFactory;
import zigen.plugin.db.ui.internal.ITable;
import zigen.plugin.db.ui.views.ColumnSearchAction;
import zigen.plugin.db.ui.views.TreeView;

public abstract class AbstractGenerateByTableAction implements IObjectActionDelegate {
	
	public static class ImportsManager {
		
		ICompilationUnit getCompilationUnit() {
			return fImportsRewrite.getCompilationUnit();
		}
		
		public String addImport(String qualifiedTypeName) {
			return fImportsRewrite.addImport(qualifiedTypeName);
		}
		
		public String addImport(ITypeBinding typeBinding) {
			return fImportsRewrite.addImport(typeBinding);
		}
		
		public String addStaticImport(String declaringTypeName, String simpleName, boolean isField) {
			return fImportsRewrite.addStaticImport(declaringTypeName, simpleName, isField);
		}
		
		void create(boolean needsSave, IProgressMonitor monitor) throws CoreException {
			org.eclipse.text.edits.TextEdit edit = fImportsRewrite.rewriteImports(monitor);
			JavaModelUtil.applyEdit(fImportsRewrite.getCompilationUnit(), edit, needsSave, null);
		}
		
		void removeImport(String qualifiedName) {
			fImportsRewrite.removeImport(qualifiedName);
		}
		
		void removeStaticImport(String qualifiedName) {
			fImportsRewrite.removeStaticImport(qualifiedName);
		}
		
		private ImportRewrite fImportsRewrite;
		
		ImportsManager(CompilationUnit astRoot) throws CoreException {
			fImportsRewrite = CodeStyleConfiguration.createImportRewrite(astRoot, true);
		}
	}
	
	protected IAction action;
	
	protected IStructuredSelection selection;
	
	protected TreeView treeView;
	
	protected StructuredViewer structuredViewer;
	
	public void setActivePart(IAction action, IWorkbenchPart targetPart) {
		if (targetPart instanceof TreeView) {
			this.treeView = (TreeView) targetPart;
			this.structuredViewer = treeView.getTreeViewer();
		} else {
			throw new RuntimeException("Required TreeView"); //$NON-NLS-1$
		}
	}
	
	public void selectionChanged(IAction action, ISelection selection) {
		this.action = action;
		if (selection instanceof IStructuredSelection) {
			this.selection = (IStructuredSelection) selection;
			this.action.setEnabled(true);
		} else {
			this.action.setEnabled(false);
			throw new RuntimeException("Required IStructuredSelection."); //$NON-NLS-1$
		}
	}
	
	public void run(IAction action) {
		try {
			Shell shell = Activator.getDefault().getShell();
			
			PackageSelectDialog dialog = new PackageSelectDialog(shell, getTitle(), getMessage());
			
			int res = dialog.open();
			if (res == PackageSelectDialog.OK) {
				PackageFragment pkg = dialog.getPackageFragment();
				String project = pkg.getJavaProject().getElementName();
				Activator.getDefault().setDefaultProject(project);
				for (Iterator iter = selection.iterator(); iter.hasNext();) {
					Object obj = iter.next();
					invoke(pkg, obj);
				}
			}
		} catch (CoreException e) {
			Activator.getDefault().showErrorDialog(e);
		}
		
	}
	
	// ファイルの存在チェック
	protected boolean existFile(PackageFragment pkg, String checkFile) throws JavaModelException {
		IJavaElement[] javaElems = pkg.getChildren();
		for (int i = 0; i < javaElems.length; i++) {
			IJavaElement element = javaElems[i];
			if (element.getElementName().equals(checkFile)) {
				return true;
			}
		}
		return false;
	}
	
	protected String lineDelimiter;
	
	protected ITable entity;
	
	public void invoke(PackageFragment pack, Object element) throws CoreException {
		if (element instanceof ITable) {
			ITable table = (ITable) element;
			this.entity = table;
			
			NullProgressMonitor monitor = new NullProgressMonitor();
			ICompilationUnit connectedCU = null;
			try {
				// カラム要素の読み込み
				loadColumnInfo(table);
				String typeName = getTypeName();
				String fileName = getCompilationUnitName(typeName);
				if (existFile(pack, fileName)) {
					Shell shell = Activator.getDefault().getShell();
					MessageBox msg = new MessageBox(shell, SWT.YES | SWT.NO | SWT.ICON_QUESTION);
					msg.setMessage(fileName + "が既に存在します。上書きしますか？");
					msg.setText(Messages.getString("GenerateAction.1"));
					int res2 = msg.open();
					if (res2 == SWT.NO)
						return;
				}
				
				lineDelimiter = StubUtility.getLineDelimiterUsed(pack.getJavaProject());
				
				// 上書きしない
				// ICompilationUnit parentCU = pack.createCompilationUnit(getCompilationUnitName(typeName), "", false, new SubProgressMonitor(monitor, 2));
				// 上書きする
				ICompilationUnit parentCU = pack.createCompilationUnit(getCompilationUnitName(typeName), "", true, new SubProgressMonitor(monitor, 2));
				connectedCU = parentCU;
				
				boolean needsSave = true;
				parentCU.becomeWorkingCopy(new SubProgressMonitor(monitor, 1));
				
				IBuffer buffer = parentCU.getBuffer();
				String simpleTypeStub = constructSimpleTypeStub();
				String cuContent = constructCUContent(parentCU, simpleTypeStub, lineDelimiter);
				buffer.setContents(cuContent);
				CompilationUnit astRoot = createASTForImports(parentCU);
				// existingImports = getExistingImports(astRoot);
				ImportsManager imports = new ImportsManager(astRoot);
				imports.addImport(JavaModelUtil.concatenateName(pack.getElementName(), typeName));
				String typeContent = constructTypeStub(parentCU, imports, lineDelimiter);
				int index = cuContent.lastIndexOf(simpleTypeStub);
				
				if (index == -1) {
					AbstractTypeDeclaration typeNode = (AbstractTypeDeclaration) astRoot.types().get(0);
					int start = ((ASTNode) typeNode.modifiers().get(0)).getStartPosition();
					int end = typeNode.getStartPosition() + typeNode.getLength();
					buffer.replace(start, end - start, typeContent);
				} else {
					buffer.replace(index, simpleTypeStub.length(), typeContent);
				}
				IType createdType = parentCU.getType(typeName);
				if (monitor.isCanceled())
					throw new InterruptedException();
				ICompilationUnit cu = createdType.getCompilationUnit();
				imports.create(false, new SubProgressMonitor(monitor, 1));
				
				JavaModelUtil.reconcile(cu);
				if (monitor.isCanceled())
					throw new InterruptedException();
				
				createTypeMembers(createdType, imports, new SubProgressMonitor(monitor, 1));
				imports.create(false, new SubProgressMonitor(monitor, 1));
				
				// existingImports = getExistingImports(astRoot);
				// ImportsManager.removeUnusedImports(cu, existingImports, false);
				JavaModelUtil.reconcile(cu);
				ISourceRange range = createdType.getSourceRange();
				IBuffer buf = cu.getBuffer();
				String originalContent = buf.getText(range.getOffset(), range.getLength());
				int indent = 0;
				
				String formattedContent = CodeFormatterUtil.format(CodeFormatter.K_CLASS_BODY_DECLARATIONS, originalContent, indent, null, lineDelimiter, pack.getJavaProject());
				formattedContent = Strings.trimLeadingTabsAndSpaces(formattedContent);
				buf.replace(range.getOffset(), range.getLength(), formattedContent);
				
				if (needsSave) {
					cu.commitWorkingCopy(true, new SubProgressMonitor(monitor, 1));
				} else {
					monitor.worked(1);
				}
				// Javaエディターを起動
				openEditor(pack, fileName);
				
				return;
			} catch (Exception e) {
				Activator.getDefault().showErrorDialog(e);
				
			} finally {
				if (connectedCU != null) {
					connectedCU.discardWorkingCopy();
				}
				monitor.done();
			}
		}
		
	}
	
	protected void openEditor(PackageFragment pack, String fileName) throws PartInitException {
		String pkgName = pack.getElementName().replace('.', '/');
		String srcPath = pack.getPackageFragmentRoot().getPath().toString(); // これではSRCしか取れない
		// 任意のソースフォルダ場所に保存できるように修正
		IContainer container = pack.getJavaProject().getProject().getParent();
		IPath path = new Path(srcPath + "/" + pkgName + "/" + fileName); //$NON-NLS-1$ //$NON-NLS-2$
		IFile file = container.getFile(path);
		IWorkbenchPage page = Activator.getDefault().getPage();
		IDE.openEditor(page, file);
	}
	
	protected String constructCUContent(ICompilationUnit cu, String typeContent, String lineDelimiter) throws CoreException {
		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$
		}
		buf.append(lineDelimiter).append(lineDelimiter);
		if (typeComment != null)
			buf.append(typeComment).append(lineDelimiter);
		buf.append(typeContent);
		return buf.toString();
	}
	
	protected String constructSimpleTypeStub() {
		StringBuffer buf = new StringBuffer("public class ");
		buf.append(getTypeName());
		buf.append("{ }");
		return buf.toString();
	}
	
	
	protected String getTypeNameWithoutParameters() {
		String typeNameWithParameters = getTypeName();
		int angleBracketOffset = typeNameWithParameters.indexOf('<');
		if (angleBracketOffset == -1)
			return typeNameWithParameters;
		else
			return typeNameWithParameters.substring(0, angleBracketOffset);
	}
	
	protected String getCompilationUnitName(String typeName) {
		return typeName + ".java";
	}
	
	protected void loadColumnInfo(ITable table) {
		// テーブル要素が展開済み(カラム要素取得済み）かチェック
		if (!table.isExpanded()) {
			// 展開フラグをTrueにする(テーブル要素をキャッシュする）
			table.setExpanded(true);
			// テーブル一覧を検索
			new ColumnSearchAction(structuredViewer, table).run();
		}
	}
	
	protected CompilationUnit createASTForImports(ICompilationUnit cu) {
		ASTParser parser = ASTParser.newParser(AST.JLS3);
		parser.setSource(cu);
		parser.setResolveBindings(false);
		parser.setFocalPosition(0);
		return (CompilationUnit) parser.createAST(null);
	}
	
	
	protected String constructTypeStub(ICompilationUnit parentCU, ImportsManager imports, String lineDelimiter) throws CoreException {
		StringBuffer buf = new StringBuffer();
		buf.append("public class ");
		buf.append(getTypeName());
		// writeSuperClass(buf, imports);
		// writeSuperInterfaces(buf, imports);
		buf.append(" {").append(lineDelimiter);
		buf.append(lineDelimiter);
		buf.append('}').append(lineDelimiter);
		return buf.toString();
	}
	
	protected String getTypeComment(ICompilationUnit parentCU, String lineDelimiter) {
		try {
			StringBuffer typeName = new StringBuffer();
			typeName.append(getTypeNameWithoutParameters());
			String[] typeParamNames = new String[0];
			String comment = CodeGeneration.getTypeComment(parentCU, typeName.toString(), typeParamNames, lineDelimiter);
			return comment;
		} catch (CoreException e) {
			Activator.log(e);
		}
		return null;
	}
	
	
	// JavaTypeの取得
	protected String getJavaType(zigen.plugin.db.ui.internal.Column col) {
		IEntityMappingFactory mapping = DefaultEntityMappingFactory.getFactory(entity.getDbConfig());
		zigen.plugin.db.core.TableColumn tCol = col.getColumn();
		return mapping.getJavaType(tCol);
	}
	
	// プロパティ文字列の取得
	protected final String propertyString(String accessModifiers, String type, String property) {
		return CodeCreatorUtil.propertyString(accessModifiers, type, property);
	}
	
	abstract protected String getTypeName();
	
	abstract protected void createTypeMembers(IType createdType, ImportsManager imports, SubProgressMonitor subProgressMonitor) throws JavaModelException;
	
	abstract public String getTitle();
	
	abstract public String getMessage();
	

}
