/******************************************************************************
* Copyright (C) 2013  Fabio Zadrozny
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*     Fabio Zadrozny <fabiofz@gmail.com> - initial API and implementation
******************************************************************************/
package org.python.pydev.shared_ui.outline;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.python.pydev.shared_core.model.ErrorDescription;

public abstract class BaseParsedItem implements IParsedItem, Comparable<Object> {

    protected IParsedItem parent;
    protected IParsedItem[] children;
    protected ErrorDescription errorDesc;

    /**
     * Updates the structure of this parsed item (old structure) to be the same as the structure in the passed
     * parsed item (new structure) trying to reuse the existing children (if possible).
     * 
     * This is usually only called when the structure actually changes (different number of nodes). A common case
     * is having a syntax error...
     */
    public void updateTo(IParsedItem item) {
        IParsedItem updateToItem = item;
        this.toStringCache = null;
        this.errorDesc = updateToItem.getErrorDesc();

        IParsedItem[] newStructureChildren = updateToItem.getChildren();

        //handle special cases...
        if (this.children == null) {
            this.children = newStructureChildren;
            return;
        }

        if (newStructureChildren.length == 0 || this.children.length == 0) {
            //nothing to actually update... (just set the new children directly)
            this.children = newStructureChildren;
            return;
        }

        ArrayList<IParsedItem> newChildren = new ArrayList<IParsedItem>();

        //ok, something there... let's update the requested children... 
        //(trying to maintain the existing nodes were possible)
        HashMap<String, List<IParsedItem>> childrensCache = new HashMap<String, List<IParsedItem>>();
        for (IParsedItem existing : this.children) {
            String s = existing.toString();
            List<IParsedItem> list = childrensCache.get(s);
            if (list == null) {
                list = new ArrayList<IParsedItem>();
                childrensCache.put(s, list);
            }
            list.add(existing);
        }

        for (IParsedItem n : newStructureChildren) {
            IParsedItem similarChild = getSimilarChild(n, childrensCache);
            if (similarChild != null) {
                similarChild.updateTo(n);
                n = similarChild;
            } else {
                n.setParent(this);
            }
            newChildren.add(n);
        }

        this.children = newChildren.toArray(new IParsedItem[newChildren.size()]);
    }

    public void setParent(IParsedItem parsedItem) {
        this.parent = parsedItem;
    }

    private IParsedItem getSimilarChild(IParsedItem n, HashMap<String, List<IParsedItem>> childrensCache) {
        //try to get a similar child from the 'cache'
        List<IParsedItem> list = childrensCache.get(n.toString());
        if (list != null && list.size() > 0) {
            return list.remove(0);
        }
        return null;
    }

    public IParsedItem getParent() {
        return parent;
    }

    /**
     * When null, it must be rebuilt!
     */
    protected String toStringCache;

    public String toString() {
        if (toStringCache == null) {
            toStringCache = calcToString();
        }
        return toStringCache;
    }

    protected abstract String calcToString();

    public ErrorDescription getErrorDesc() {
        return errorDesc;
    }

    public void setErrorDesc(ErrorDescription errorDesc) {
        if (this.errorDesc == null && errorDesc == null) {
            return; // don't clear the caches
        }
        this.toStringCache = null;
        this.errorDesc = errorDesc;
    }

}
