/**
 * Copyright (c) 20015 by Brainwy Software Ltda. All Rights Reserved.
 * Licensed under the terms of the Eclipse Public License (EPL).
 * Please see the license.txt included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
package org.python.pydev.shared_ui.search;

import java.util.HashMap;
import java.util.Map;

import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.search.ui.text.AbstractTextSearchResult;
import org.eclipse.search.ui.text.Match;
import org.python.pydev.shared_core.log.Log;
import org.python.pydev.shared_core.structure.TreeNode;
import org.python.pydev.shared_core.structure.TreeNodeContentProvider;

public abstract class AbstractSearchIndexTreeContentProvider extends TreeNodeContentProvider
        implements ITreeContentProvider, ISearchIndexContentProvider {

    protected TreeNode<Object> root;
    protected Map<Object, TreeNode<?>> elementToTreeNode = new HashMap<>();
    protected TreeViewer viewer;
    protected AbstractTextSearchResult fResult;

    public int groupWith = 0;

    public AbstractSearchIndexTreeContentProvider(TreeViewer viewer) {
        this.viewer = viewer;
    }

    @Override
    public void setGroupWith(int groupWith) {
        if (this.groupWith == groupWith) {
            return;
        }
        this.groupWith = groupWith;

        // Pretend the input changed (as the whole structure changed).
        this.inputChanged(this.viewer, null, fResult);

        this.clearFilterCaches();

        // And at last, ask for a refresh!
        this.viewer.refresh();
    }

    protected void clearFilterCaches() {
        ViewerFilter[] filters = this.viewer.getFilters();
        if (filters != null) {
            for (ViewerFilter viewerFilter : filters) {
                if (viewerFilter instanceof AbstractSearchResultsViewerFilter) {
                    AbstractSearchResultsViewerFilter filter = (AbstractSearchResultsViewerFilter) viewerFilter;
                    filter.clearCache();
                }
            }
        }
    }

    public int getGroupWith() {
        return groupWith;
    }

    @Override
    public void dispose() {
        super.dispose();
    }

    @Override
    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        elementToTreeNode.clear();
        if (newInput instanceof AbstractTextSearchResult) {
            AbstractTextSearchResult abstractTextSearchResult = (AbstractTextSearchResult) newInput;
            this.fResult = abstractTextSearchResult;
            root = new TreeNode<>(null, newInput);
            Object[] elements = abstractTextSearchResult.getElements();
            int elementsLen = elements.length;
            for (int i = 0; i < elementsLen; i++) {
                Object object = elements[i];
                Match[] matches = abstractTextSearchResult.getMatches(object);
                int matchesLen = matches.length;
                for (int j = 0; j < matchesLen; j++) {
                    Match match = matches[j];
                    if (match instanceof ICustomMatch) {
                        ICustomMatch moduleMatch = (ICustomMatch) match;
                        obtainTeeNodeElement(moduleMatch.getLineElement());
                    } else {
                        Log.log("Expecting ICustomMatch. Found:" + match.getClass() + " - " + match);
                    }
                }
            }
        } else {
            this.clear();
        }
    }

    @Override
    public Object[] getElements(Object inputElement) {
        return getChildren(root);
    }

    @Override
    public void elementsChanged(Object[] updatedElements) {
        for (int i = 0; i < updatedElements.length; i++) {
            Object object = updatedElements[i];
            int matchCount;
            if (object instanceof ICustomLineElement) {
                ICustomLineElement iCustomLineElement = (ICustomLineElement) object;
                matchCount = iCustomLineElement.getNumberOfMatches(fResult);
            } else {
                matchCount = fResult.getMatchCount(updatedElements[i]);
            }
            if (matchCount > 0) {
                obtainTeeNodeElement(object);
            } else {
                TreeNode<?> treeNode = this.elementToTreeNode.get(object);
                if (treeNode != null) {
                    Object parent = treeNode.getParent();
                    treeNode.detachFromParent();
                    if (parent instanceof TreeNode<?>) {
                        checkClearParentTree((TreeNode<?>) parent);
                    }
                }
            }
        }
        this.viewer.refresh();
    }

    private void checkClearParentTree(TreeNode<?> treeNode) {
        if (!treeNode.hasChildren()) {
            Object parent = treeNode.getParent();
            treeNode.detachFromParent();
            if (parent instanceof TreeNode<?>) {
                checkClearParentTree((TreeNode<?>) parent);
            }
        }
    }

    @Override
    public void clear() {
        root = new TreeNode<Object>(null, null);
        this.elementToTreeNode.clear();
        this.clearFilterCaches();
        this.viewer.refresh();
    }

    /**
     * Subclasses should override to actually create the structure
     */
    protected abstract TreeNode<?> obtainTeeNodeElement(final Object object);

}
