package zigen.plugin.db.ui.editors.sql;

import java.util.Iterator;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.CoolBarManager;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.ToolBarContributionItem;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.ITextOperationTarget;
import org.eclipse.jface.text.ITextViewerExtension2;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationAccess;
import org.eclipse.jface.text.source.IOverviewRuler;
import org.eclipse.jface.text.source.ISharedTextColors;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.LineNumberRulerColumn;
import org.eclipse.jface.text.source.MatchingCharacterPainter;
import org.eclipse.jface.text.source.OverviewRuler;
import org.eclipse.jface.text.source.VerticalRuler;
import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DragDetectEvent;
import org.eclipse.swt.events.DragDetectListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.CoolBar;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorActionBarContributor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.internal.editors.text.EditorsPlugin;
import org.eclipse.ui.part.EditorPart;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
import org.eclipse.ui.texteditor.AnnotationPreference;
import org.eclipse.ui.texteditor.DefaultMarkerAnnotationAccess;
import org.eclipse.ui.texteditor.DefaultRangeIndicator;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.MarkerAnnotationPreferences;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;

import zigen.plugin.db.DbPlugin;
import zigen.plugin.db.ImageCacher;
import zigen.plugin.db.core.IDBConfig;
import zigen.plugin.db.ext.oracle.internal.OracleSourceDetailInfo;
import zigen.plugin.db.ext.oracle.internal.OracleSourceErrorInfo;
import zigen.plugin.db.preference.SQLEditorPreferencePage;
import zigen.plugin.db.ui.actions.GlobalAction;
import zigen.plugin.db.ui.actions.OpenSQLAction;
import zigen.plugin.db.ui.actions.SaveSQLAction;
import zigen.plugin.db.ui.internal.Root;
import zigen.plugin.db.ui.internal.TreeLeaf;
import zigen.plugin.db.ui.internal.TreeNode;
import zigen.plugin.db.ui.util.StyledTextUtil;
import zigen.plugin.db.ui.views.ISQLOperationTarget;
import zigen.plugin.db.ui.views.internal.ColorManager;
import zigen.plugin.db.ui.views.internal.PLSQLCodeConfiguration;
import zigen.plugin.db.ui.views.internal.PLSQLDocument;
import zigen.plugin.db.ui.views.internal.PLSQLSourceViewer;
import zigen.plugin.db.ui.views.internal.SQLCharacterPairMatcher;
import zigen.plugin.db.ui.views.internal.SQLSourceViewer;

public class SourceEditor extends EditorPart implements IPlsqlEditor, IPropertyChangeListener {

    public static final String ID = "zigen.plugin.db.ui.editors.sql.SourceEditor"; //$NON-NLS-1$

    protected IDBConfig config;

    protected PLSQLSourceViewer sourceViewer;

    private OracleSourceDetailInfo sourceDetailInfo;

    private OracleSourceErrorInfo[] sourceErrorInfos;

    protected LineNumberRulerColumn rulerCol;

    protected PLSQLCodeConfiguration sqlConfiguration;

    protected ColorManager colorManager = new ColorManager();

    protected IPreferenceStore store;

    protected boolean dirty = false;

    protected TreeViewer errorViewer;

    protected SashForm sash;

    GlobalAction execScriptAction = new GlobalAction(null, ISQLOperationTarget.SCRIPT_EXECUTE);

    OpenSQLAction openSQLAction = new OpenSQLAction(null);

    SaveSQLAction saveSQLAction = new SaveSQLAction(null);

    class DocumentListener implements IDocumentListener {
        public DocumentListener() {
        }

        public void documentAboutToBeChanged(DocumentEvent event) {
        }

        public void documentChanged(DocumentEvent event) {
            setDirtyMonde(true);
            // setDirty(true);
        }
    }

    public void setDirtyMonde(boolean b) {
        String pageName = getPartName();
        if (b) {
            if (!pageName.startsWith("*")) {
                setPartName("*" + pageName);
            }
        } else {
            if (pageName.startsWith("*")) {
                setPartName(pageName.substring(1));
            }
        }
    }

    public SourceEditor() {
        colorManager = new ColorManager();
        sqlConfiguration = new PLSQLCodeConfiguration(colorManager);
        this.store = DbPlugin.getDefault().getPreferenceStore();
        this.store.addPropertyChangeListener(this);

        fAnnotationPreferences = EditorsPlugin.getDefault().getMarkerAnnotationPreferences();
        setRangeIndicator(new DefaultRangeIndicator());

    }

    // start
    private Annotation fRangeIndicator;;

    IVerticalRuler fVerticalRuler;

    IOverviewRuler fOverviewRuler;

    IAnnotationAccess fAnnotationAccess;

    protected final static int VERTICAL_RULER_WIDTH = 12;

    private MarkerAnnotationPreferences fAnnotationPreferences;

    protected SourceViewerDecorationSupport fSourceViewerDecorationSupport;

    protected ISharedTextColors getSharedColors() {
        ISharedTextColors sharedColors = EditorsPlugin.getDefault().getSharedTextColors();
        return sharedColors;
    }

    protected IAnnotationAccess createAnnotationAccess() {
        return new DefaultMarkerAnnotationAccess();
    }

    protected IAnnotationAccess getAnnotationAccess() {
        if (fAnnotationAccess == null)
            fAnnotationAccess = createAnnotationAccess();
        return fAnnotationAccess;
    }

    protected IOverviewRuler getOverviewRuler() {
        if (fOverviewRuler == null)
            fOverviewRuler = createOverviewRuler(getSharedColors());
        return fOverviewRuler;
    }

    protected IVerticalRuler createVerticalRuler() {
        return new VerticalRuler(VERTICAL_RULER_WIDTH);
    }

    protected IOverviewRuler createOverviewRuler(ISharedTextColors sharedColors) {
        IOverviewRuler ruler = new OverviewRuler(getAnnotationAccess(), VERTICAL_RULER_WIDTH, sharedColors);
        Iterator e = fAnnotationPreferences.getAnnotationPreferences().iterator();
        while (e.hasNext()) {
            AnnotationPreference preference = (AnnotationPreference) e.next();
            if (preference.contributesToHeader())
                ruler.addHeaderAnnotationType(preference.getAnnotationType());
        }
        return ruler;
    }

    protected SourceViewerDecorationSupport getSourceViewerDecorationSupport(ISourceViewer viewer) {
        if (fSourceViewerDecorationSupport == null) {
            fSourceViewerDecorationSupport = new SourceViewerDecorationSupport(viewer, getOverviewRuler(), getAnnotationAccess(), getSharedColors());
            configureSourceViewerDecorationSupport(fSourceViewerDecorationSupport);
        }
        return fSourceViewerDecorationSupport;
    }

    private final static String CURRENT_LINE = AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE;

    private final static String CURRENT_LINE_COLOR = AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR;

    private final static String PRINT_MARGIN = AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN;

    private final static String PRINT_MARGIN_COLOR = AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLOR;

    private final static String PRINT_MARGIN_COLUMN = AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLUMN;

    protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support) {

        Iterator e = fAnnotationPreferences.getAnnotationPreferences().iterator();
        while (e.hasNext())
            support.setAnnotationPreference((AnnotationPreference) e.next());

        support.setCursorLinePainterPreferenceKeys(CURRENT_LINE, CURRENT_LINE_COLOR);
        support.setMarginPainterPreferenceKeys(PRINT_MARGIN, PRINT_MARGIN_COLOR, PRINT_MARGIN_COLUMN);
        support.setSymbolicFontName(getFontPropertyPreferenceKey());
    }

    protected final String getFontPropertyPreferenceKey() {
        String symbolicFontName = getSymbolicFontName();
        if (symbolicFontName != null)
            return symbolicFontName;
        return JFaceResources.TEXT_FONT;
    }

    private String getSymbolicFontName() {
        if (getConfigurationElement() != null)
            return getConfigurationElement().getAttribute("symbolicFontName"); //$NON-NLS-1$
        return null;
    }

    protected final Annotation getRangeIndicator() {
        return fRangeIndicator;
    }

    protected void setRangeIndicator(Annotation rangeIndicator) {
        fRangeIndicator = rangeIndicator;
    }

    protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) {
        fAnnotationAccess = getAnnotationAccess();
        fOverviewRuler = createOverviewRuler(getSharedColors());
        PLSQLSourceViewer viewer = new PLSQLSourceViewer(parent, ruler, getOverviewRuler(), true, styles);
        // modify
        viewer.setPlsqlEditor(this);

        getSourceViewerDecorationSupport((ISourceViewer) viewer);
        return viewer;
    }

    private IDocumentProvider fExplicitDocumentProvider;

    protected void setDocumentProvider(IDocumentProvider provider) {
        fExplicitDocumentProvider = provider;
    }

    public IDocumentProvider getDocumentProvider() {
        return fExplicitDocumentProvider;
    }

    // / end
    public void createPartControl(Composite parent) {
        fVerticalRuler = createVerticalRuler();

        Composite header = new Composite(parent, SWT.NONE);
        GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
        header.setLayoutData(gridData);
        GridLayout gridLayout = new GridLayout();
        gridLayout.numColumns = 1;
        gridLayout.makeColumnsEqualWidth = false;
        gridLayout.marginHeight = 1;
        gridLayout.marginWidth = 1;
        gridLayout.horizontalSpacing = 2;
        gridLayout.verticalSpacing = 2;
        header.setLayout(gridLayout);

        createToolbarPart(header);

        // this.composite = parent;
        sash = new SashForm(header, SWT.VERTICAL | SWT.NONE);
        sash.setLayoutData(new GridData(GridData.FILL_BOTH));

        int styles = SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI | SWT.BORDER | SWT.FULL_SELECTION;
        sourceViewer = (PLSQLSourceViewer) createSourceViewer(sash, fVerticalRuler, styles);

        // sourceViewer = new PLSQLSourceViewer(sash, ruler, null, false, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
        sourceViewer.setPlsqlEditor(this);

        if (fRangeIndicator != null)
            sourceViewer.setRangeIndicator(fRangeIndicator);

        // IEditorInput input = getEditorInput();
        // IAnnotationModel model= getDocumentProvider().getAnnotationModel(input);
        // IDocument document= getDocumentProvider().getDocument(input);
        //
        // if (document != null) {
        // sourceViewer.setDocument(document, model);
        //            
        //        
        //        

        initializeViewerFont(sourceViewer);

        sourceViewer.configure(sqlConfiguration);

        PLSQLDocument doc = new PLSQLDocument();

        IDocumentListener documentListener = new DocumentListener();

        // DosumentListenerを登録
        doc.addDocumentListener(documentListener);

        sourceViewer.setDocument(doc, new ProjectionAnnotationModel());

        String text = "";
        if (sourceDetailInfo != null) {
            text = sourceDetailInfo.getText();
        }

        sourceViewer.getDocument().set(text);

        ITextViewerExtension2 extension = (ITextViewerExtension2) sourceViewer;
        MatchingCharacterPainter painter = new MatchingCharacterPainter(sourceViewer, new SQLCharacterPairMatcher());
        painter.setColor(colorManager.getColor(SQLEditorPreferencePage.P_COLOR_MATCHING));
        extension.addPainter(painter);

        // 変更完了通知
        // setDirty(false);
        setDirtyMonde(false);

        errorViewer = new TreeViewer(sash, SWT.MULTI | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL);
        errorViewer.getControl().setLayoutData(new GridData(GridData.FILL_BOTH));
        errorViewer.getTree().setLinesVisible(true);
        ErrorContentProvider cp = new ErrorContentProvider();

        errorViewer.setContentProvider(cp);
        errorViewer.setLabelProvider(new ErrorLabelProvider());
        errorViewer.setInput(sourceErrorInfos);

        errorViewer.expandAll();

        sash.setWeights(new int[] {80, 20});

        hookContextMenu();

        updateFolding();

        // sourceViewer作成後に、Actionに割り当てる
        setSQLViewerToAction(sourceViewer);
    }

    private void setSQLViewerToAction(SQLSourceViewer viewer) {
        execScriptAction.setTextViewer(viewer);
        openSQLAction.setSQLSourceViewer(viewer);
        saveSQLAction.setSQLSourceViewer(viewer);
    }

    public void createToolbarPart(final Composite header) {
        ImageCacher ic = ImageCacher.getInstance();

        CoolBar coolBar = new CoolBar(header, SWT.FLAT);
        CoolBarManager coolBarMgr = new CoolBarManager(coolBar);

        GridData gid = new GridData();
        gid.horizontalAlignment = GridData.FILL;
        coolBar.setLayoutData(gid);

        ToolBarManager toolBarMgr1 = new ToolBarManager(SWT.FLAT);
        toolBarMgr1.add(execScriptAction);

        ToolBarManager toolBarMgr2 = new ToolBarManager(SWT.FLAT);
        toolBarMgr2.add(openSQLAction);
        toolBarMgr2.add(saveSQLAction);

        coolBarMgr.add(new ToolBarContributionItem(toolBarMgr1));
        coolBarMgr.add(new ToolBarContributionItem(toolBarMgr2));
        coolBarMgr.update(true);

        coolBar.addControlListener(new ControlListener(){
            public void controlMoved(ControlEvent e) {
            }
            public void controlResized(ControlEvent e) {
                header.getParent().layout(true);
                header.layout(true);
            }            
        });

    }

    /*
     * private void setErrorMessage() { if (sourceErrorInfo != null) { errorText.setText(sourceErrorInfo.getErrorText()); sash.setWeights(new int[] { 95, 5 }); }
     * else { errorText.setText(""); //$NON-NLS-1$ sash.setWeights(new int[] { 100, 0 }); } }
     */

    /**
     * ポップアップメニュー作成メソッド
     */
    private void hookContextMenu() {
        MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
        menuMgr.setRemoveAllWhenShown(true);
        menuMgr.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager manager) {
                getContributor().fillContextMenu(manager);
            }
        });
        StyledText text = sourceViewer.getTextWidget();
        Menu menu = menuMgr.createContextMenu(text);
        text.setMenu(menu);

        getSite().registerContextMenu(menuMgr, sourceViewer);
    }

    protected void setGlobalAction() {
        IActionBars actionbars = getEditorSite().getActionBars();
        actionbars.setGlobalActionHandler(ActionFactory.UNDO.getId(), new GlobalAction(sourceViewer, ITextOperationTarget.UNDO));
        actionbars.setGlobalActionHandler(ActionFactory.REDO.getId(), new GlobalAction(sourceViewer, ITextOperationTarget.REDO));
        actionbars.setGlobalActionHandler(ActionFactory.DELETE.getId(), new GlobalAction(sourceViewer, ITextOperationTarget.DELETE));
        actionbars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), new GlobalAction(sourceViewer, ITextOperationTarget.SELECT_ALL));
        actionbars.setGlobalActionHandler(ActionFactory.COPY.getId(), new GlobalAction(sourceViewer, ITextOperationTarget.COPY));
        actionbars.setGlobalActionHandler(ActionFactory.PASTE.getId(), new GlobalAction(sourceViewer, ITextOperationTarget.PASTE));
        actionbars.setGlobalActionHandler(ActionFactory.CUT.getId(), new GlobalAction(sourceViewer, ITextOperationTarget.CUT));
    }

    public void init(IEditorSite site, IEditorInput editorInput) throws PartInitException {
        try {
            setSite(site);
            setInput(editorInput);

            if (editorInput instanceof SourceEditorInput) {
                SourceEditorInput input = (SourceEditorInput) editorInput;

                this.sourceDetailInfo = input.getSourceDetailInfo();
                this.sourceErrorInfos = input.getSourceErrorInfos();

                config = input.getConfig();
                setPartName(input.getName());
            }

        } catch (Exception e) {
            DbPlugin.getDefault().showErrorDialog(e);
        }
    }

    protected void initializeViewerFont(ISourceViewer viewer) {
        StyledText styledText = viewer.getTextWidget();
        styledText.setFont(DbPlugin.getDefaultFont());
    }

    public void propertyChange(PropertyChangeEvent event) {
        if (sqlConfiguration != null && sourceViewer != null) {
            sqlConfiguration.updatePreferences();
            StyledTextUtil.changeColor(colorManager, sourceViewer.getTextWidget());
            // LineNumberRulerColumnUtil.changeColor(colorManager, rulerCol);
            sourceViewer.invalidateTextPresentation();// テキストエディタを再描画
        }
    }

    private void updateFolding() {
        sourceViewer.updateFolding();

    }

    public void doSave(IProgressMonitor monitor) {
        // Connection con = null;
        // try {
        // ISelection selection = sourceViewer.getSelection();
        // con = ConnectionManager.getConnection(config);
        // // 改行コードが\nでないと、PLS-00103が発生するため、置換する
        // String sql = sourceViewer.getDocument().get();
        // sql = sql.replaceAll("\r", "\n"); //$NON-NLS-1$ //$NON-NLS-2$
        // SQLInvoker.execute(con, sql);
        // String owner = sourceDetailInfo.getOwner();
        // String type = sourceDetailInfo.getType();
        // String name = sourceDetailInfo.getName();
        // // ソースを再取得する場合は以下のメソッド
        // // sourceDetailInfo = OracleSourceDetailSearcher.execute(con, owner,
        // // name, type);
        // sourceErrorInfos = OracleSourceErrorSearcher.execute(con, owner, name, type);
        // // 取得したソースを再設定
        // // sourceViewer.getDocument().set(sourceDetailInfo.getText());
        // // 保存前の位置に移動する
        // // sourceViewer.setSelection(selection, true);
        // errorViewer.setInput(sourceErrorInfos);
        // errorViewer.expandAll();
        //
        // // 変更通知(OFF)
        // setDirty(false);
        //
        // } catch (Exception e) {
        // DbPlugin.getDefault().showErrorDialog(e);
        // } finally {
        // ConnectionManager.closeConnection(con);
        // }
    }

    public void doSaveAs() {
    }

    public boolean isDirty() {
        return false;
    }

    // public boolean isDirty() {
    // return dirty;
    // }

    // protected void setDirty(boolean value) {
    // dirty = value;
    // firePropertyChange(PROP_DIRTY);
    // }

    public boolean isSaveAsAllowed() {
        return false;
    }

    public void setFocus() {
        setGlobalAction();
    }

    public void dispose() {
        super.dispose();
        colorManager.dispose();
        DbPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(this);
    }

    /**
     * Contributorの取得
     * @return
     */
    private SourceEditorContributor getContributor() {
        IEditorActionBarContributor contributor = getEditorSite().getActionBarContributor();
        if (contributor instanceof SourceEditorContributor) {
            return (SourceEditorContributor) contributor;
        } else {
            return null;
        }
    }

    public void clearError() {
        errorViewer.setInput(null);
        setDirtyMonde(false);
    }

    public IDBConfig getConfig() {
        return config;
    }

    public PLSQLSourceViewer getPLSQLSourceViewer() {
        return sourceViewer;
    }

    public void setError(OracleSourceErrorInfo[] OracleSourceErrorInfos) {
        errorViewer.setInput(OracleSourceErrorInfos);
        errorViewer.expandAll();
        setDirtyMonde(false);
    }

    public class ErrorLabelProvider extends LabelProvider {

        ImageCacher ic = ImageCacher.getInstance();

        public String getText(Object obj) {
            return obj.toString();
        }

        public Image getImage(Object obj) {
            if (obj instanceof Root) {
                return ic.getImage(DbPlugin.IMG_CODE_ERROR_ROOT);
            } else {
                return ic.getImage(DbPlugin.IMG_CODE_ERROR);
            }
        }
    }

    private class ErrorContentProvider implements ITreeContentProvider {
        private Root invisibleRoot;

        private TreeViewer viewer;

        public void inputChanged(Viewer v, Object oldInput, Object newInput) {
            this.viewer = (TreeViewer) v;
            this.invisibleRoot = new Root("invisible", true);
            if (newInput instanceof OracleSourceErrorInfo[]) {
                OracleSourceErrorInfo[] errors = (OracleSourceErrorInfo[]) newInput;
                Root root = new Root("Errors (" + errors.length + " items)");
                invisibleRoot.addChild(root);
                for (int i = 0; i < errors.length; i++) {
                    OracleSourceErrorInfo err = errors[i];
                    root.addChild(new TreeNode(err.getErrorText()));
                }
            }

        }

        public void dispose() {
        }

        public Object[] getElements(Object inputElement) {
            return getChildren(invisibleRoot);
        }

        public Object getParent(Object element) {
            if (element instanceof TreeLeaf) {
                return ((TreeLeaf) element).getParent();
            }
            return null;
        }

        public Object[] getChildren(Object parentElement) {
            if (parentElement instanceof TreeNode) {
                return ((TreeNode) parentElement).getChildrens();
            }
            return new Object[0];
        }

        public boolean hasChildren(Object element) {
            if (element instanceof TreeNode)
                return ((TreeNode) element).hasChildren();
            return false;
        }

        public Root getInvisibleRoot() {
            return invisibleRoot;
        }
    }

}
