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

            String entityClass = getEntityClass();
            String superClass = super.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) {
                        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);
                        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();
    }

}