/*
 * Copyright (C) 2011 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.exoplatform.portal.mop.navigation;

import java.util.*;


/**
 * <p>
 * The context of a tree, that performs:
 * <ul>
 * <li>holding the list of pending changes</li>
 * <li>keep a reference to the {@link NodeModel}</li>
 * <li>hold a sequence for providing id for transient contexts</li>
 * <li>hold the root context</li>
 * </ul>
 * </p>
 *
 * <p>
 * The class implements the {@link Scope.Visitor} and defines a scope describing the actual content of the context tree.
 * </p>
 *
 * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
 */
class TreeContext<N> implements Scope.Visitor, NodeChangeListener<NodeContext<N>> {

    /** . */
    private NodeChangeQueue<NodeContext<N>> changes;

    /** . */
    final NodeModel<N> model;

    /** . */
    boolean editMode;

    /** . */
    int sequence;

    /** . */
    final NodeContext<N> root;

    TreeContext(NodeModel<N> model, NodeContext<N> root) {
        this.model = model;
        this.editMode = false;
        this.sequence = 0;
        this.root = root;
    }

    public NodeChangeQueue<NodeContext<N>> getChanges() {
        return changes;
    }

    // Improve that method if we can
    Scope.Visitor origin() {

        final Map<String, Boolean> map = new HashMap<String, Boolean>();

        //
        populate(map, root);

        //
        if (changes != null) {
            ListIterator<NodeChange<NodeContext<N>>> it = changes.listIterator(changes.size());
            while (it.hasPrevious()) {
                NodeChange<NodeContext<N>> change = it.previous();
                if (change instanceof NodeChange.Created<?>) {
                    NodeChange.Created<NodeContext<N>> created = (NodeChange.Created<NodeContext<N>>) change;
                    map.remove(created.target.handle);
                } else if (change instanceof NodeChange.Destroyed<?>) {
                    NodeChange.Destroyed<NodeContext<N>> destroyed = (NodeChange.Destroyed<NodeContext<N>>) change;
                    map.put(destroyed.target.handle, Boolean.TRUE);
                }
            }
        }

        //
        return new Scope.Visitor() {
            public VisitMode enter(int depth, String id, String name, NodeState state) {
                return map.containsKey(id) ? VisitMode.ALL_CHILDREN : VisitMode.NO_CHILDREN;
            }

            public void leave(int depth, String id, String name, NodeState state) {
            }
        };
    }

    private void populate(Map<String, Boolean> map, NodeContext<N> ctx) {
        if (ctx.isExpanded()) {
            map.put(ctx.handle, Boolean.TRUE);
            for (NodeContext<N> current = ctx.getFirst(); current != null; current = current.getNext()) {
                populate(map, current);
            }
        }
    }

    void addChange(NodeChange<NodeContext<N>> change) {
        if (editMode) {
            throw new AssertionError();
        }
        if (changes == null) {
            changes = new NodeChangeQueue<NodeContext<N>>();
        }

        //
        if (change.target.tree != this) {
            // Normally should be done for all arguments depending on the change type
            throw new AssertionError("Ensure we are not mixing badly things");
        }

        // Perform state modification here
        if (change instanceof NodeChange.Renamed<?>) {
            NodeChange.Renamed<NodeContext<N>> renamed = (NodeChange.Renamed<NodeContext<N>>) change;
            renamed.target.name = renamed.name;
        } else if (change instanceof NodeChange.Created<?>) {
            NodeChange.Created<NodeContext<N>> added = (NodeChange.Created<NodeContext<N>>) change;
            if (added.previous != null) {
              added.previous.insertAfter(added.target);
            } else if (added.parent.getSize() > 0) {
              added.parent.get(0).insertBefore(added.target);
            } else {
              added.parent.insertFirst(added.target);
            }
        } else if (change instanceof NodeChange.Moved<?>) {
            NodeChange.Moved<NodeContext<N>> moved = (NodeChange.Moved<NodeContext<N>>) change;
            if (moved.previous != null) {
                moved.previous.insertAfter(moved.target);
            } else {
                moved.to.insertAt(0, moved.target);
            }
        } else if (change instanceof NodeChange.Destroyed<?>) {
            NodeChange.Destroyed<NodeContext<N>> removed = (NodeChange.Destroyed<NodeContext<N>>) change;
            removed.target.remove();
        } else if (change instanceof NodeChange.Updated<?>) {
            NodeChange.Updated<NodeContext<N>> updated = (NodeChange.Updated<NodeContext<N>>) change;
            updated.target.state = updated.state;
        }

        //
        changes.addLast(change);
    }

    boolean hasChanges() {
        return changes != null && changes.size() > 0;
    }

    List<NodeChange<NodeContext<N>>> peekChanges() {
        if (hasChanges()) {
            return changes;
        } else {
            return Collections.emptyList();
        }
    }

    List<NodeChange<NodeContext<N>>> popChanges() {
        if (hasChanges()) {
            LinkedList<NodeChange<NodeContext<N>>> tmp = changes;
            changes = null;
            return tmp;
        } else {
            return Collections.emptyList();
        }
    }

    NodeContext<N> getNode(String handle) {
        return root.getDescendant(handle);
    }

    NodeContext<N> create(String handle, String name, NodeState state) {
        return new NodeContext<N>(this, handle, name, state, true);
    }

    // Scope.Visitor implementation -------------------------------------------------------------------------------------

    public VisitMode enter(int depth, String id, String name, NodeState state) {
        NodeContext<N> descendant = root.getDescendant(id);
        if (descendant != null) {
            return descendant.isExpanded() ? VisitMode.ALL_CHILDREN : VisitMode.NO_CHILDREN;
        } else {
            return VisitMode.NO_CHILDREN;
        }
    }

    public void leave(int depth, String id, String name, NodeState state) {
    }

    //

    public void onCreate(NodeContext<N> target, NodeContext<N> parent, NodeContext<N> previous, String name)
            throws NavigationServiceException {
        addChange(new NodeChange.Created<NodeContext<N>>(parent, previous, target, name));
    }

    public void onDestroy(NodeContext<N> target, NodeContext<N> parent) {
        addChange(new NodeChange.Destroyed<NodeContext<N>>(parent, target));
    }

    public void onRename(NodeContext<N> target, NodeContext<N> parent, String name) throws NavigationServiceException {
        addChange(new NodeChange.Renamed<NodeContext<N>>(parent, target, name));
    }

    public void onUpdate(NodeContext<N> target, NodeState state) throws NavigationServiceException {
        addChange(new NodeChange.Updated<NodeContext<N>>(target, state));
    }

    public void onMove(NodeContext<N> target, NodeContext<N> from, NodeContext<N> to, NodeContext<N> previous)
            throws NavigationServiceException {
        addChange(new NodeChange.Moved<NodeContext<N>>(from, to, previous, target));
    }

    public void onAdd(NodeContext<N> target, NodeContext<N> parent, NodeContext<N> previous) {
        throw new UnsupportedOperationException();
    }

    public void onRemove(NodeContext<N> target, NodeContext<N> parent) {
        throw new UnsupportedOperationException();
    }

}
