/*
 * Copyright (C) 2004 TiongHiang Lee
 *
 * This library 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 library 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 library; if not,  write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Email: thlee@onemindsoft.org
 */

package org.onemind.commons.java.datastructure;

import java.util.*;
import java.util.logging.Logger;
/**
 * A nametable stack contains key-value mapping that has a scope. 
 * A new scope can be opened for putting new mappings and all the
 * mappings added in this scope can be wiped out easily with a 
 * closeScope command. In effect this is like a stack of Maps, 
 * hence the name NametableStack. 
 * 
 * NOTE: the implementation use a map and list to achieve the behaviour.
 * @author TiongHiang Lee (thlee@onemindsoft.org)
 * @version $Id: NametableStack.java,v 1.7 2005/06/22 22:57:37 thlee Exp $ $Name:  $
 */
public class NametableStack
{

    /** the logger * */
    private static final Logger _logger = Logger.getLogger(NametableStack.class.getName());

    /** the map * */
    private Nametable _nametable;

    /** the list of maps * */
    private ArrayList _list = new ArrayList();

    /**
     * The local nametable defines a scope where local variables mask out the global
     * variables, but the global variable can still be accessed. This is useful for 
     * implementing function context
     * 
     * @author TiongHiang Lee (thlee@onemindsoft.org)
     */
    private static class LocalNametable implements Nametable
    {

        /** the local variables **/
        private final Map _locals = new HashMap();

        /** the global map **/
        private final Nametable _global;

        /** the scope **/
        private final int _scope;

        /**
         * Constructor
         * @param global the global
         * @param scope the scope # where this local nametabe is openned
         */
        private LocalNametable(Nametable global, int scope)
        {
            _global = global;
            _scope = scope;
        }

        /**
         * Get the global
         * @return the global
         */
        private Nametable getGlobal()
        {
            return _global;
        }

        /** 
         * {@inheritDoc}
         */
        public boolean containsName(String key)
        {
            return _locals.containsKey(key) || _global.containsName(key);
        }

        /** 
         * {@inheritDoc}
         */
        public void declare(String name, Object value)
        {
            _locals.put(name, value);
        }

        /** 
         * {@inheritDoc}
         */
        public Object assign(String name, Object value)
        {
            if (_locals.containsKey(name))
            {
                return _locals.put(name, value);
            } else
            {
                //TODO: determine whether to allow local scope to reassign global
                return _global.assign(name, value);
            }
        }

        /** 
         * {@inheritDoc}
         */
        public Object access(String name)
        {
            if (_locals.containsKey(name))
            {
                return _locals.get(name);
            } else
            {
                return _global.access(name);
            }
        }

        /** 
         * {@inheritDoc}
         */
        public void undeclare(String name)
        {          
            if (_locals.containsKey(name))
            {
                _locals.remove(name);
            } else
            {
                //TODO: determine whether to allow local scope to undeclare global  
                _global.undeclare(name);
            }
        }
        
        public String toString(){
            StringBuffer sb = new StringBuffer();
            sb.append("Scope=");
            sb.append(_scope);
            sb.append("\n");
            sb.append("Locals=" + _locals + "\n");
            sb.append("Global=" + _global + "\n");
            return sb.toString();
        }
        
        public Map asMap(){
            Map m = new HashMap(_global.asMap());
            m.putAll(_locals);
            return Collections.unmodifiableMap(m);
        }
    }

    /**
     * {@inheritDoc}
     */
    public NametableStack()
    {
        this(new HashMap());
    }

    /**
     * {@inheritDoc}
     * @param m the initial mapping
     */
    public NametableStack(Map m)
    {
        _nametable = new SimpleNametable(m);
    }

    /**
     * Open a new scope for mappings. Return an integer that represents a scope id
     * @return the new scope
     */
    public int newScope()
    {
        return _list.size();
    }

    public int newLocalScope()
    {
        _nametable = new LocalNametable(_nametable, _list.size());
        return _list.size();
    }

    public void closeLocalScope(int i)
    {
        if (_nametable instanceof LocalNametable)
        {
            LocalNametable nt = (LocalNametable) _nametable;
            if (nt._scope != i)
            {
                throw new IllegalArgumentException("Local scope " + i + " not matched");
            } else
            {
                for (int k = _list.size(); k > i; k--)
                {
                    _list.remove(k-1);
                }
                _nametable = nt.getGlobal();
            }
        } else
        {
            throw new IllegalStateException("Cannot find scope " + i);
        }
    }

    /**
     * Close a scope
     * @param l the scope id
     */
    public void closeScope(int l)
    {
        if (_nametable instanceof LocalNametable)
        {
            if (l < ((LocalNametable) _nametable)._scope)
            {
                throw new IllegalStateException("Encounter unclosed local scope");
            }
        }
        int n = _list.size();
        if (l > n)
        {
            throw new IllegalArgumentException("The scope has been closed");
        } else if (l < n)
        {
            int diff = n - l;
            for (int i = 0; i < diff; i++)
            {
                _nametable.undeclare((String)_list.remove(n - i - 1));
            }
        }
    }

    /**
     * Declare name value pair
     * @param name
     * @param value
     * @return
     */
    public void declare(String name, Object value)
    {
        _nametable.declare(name, value);
        _list.add(name);
    }

    /**
     * Assign name/value pair
     * @param name
     * @param value
     * @return 
     */
    public Object assign(String name, Object value)
    {
        return _nametable.assign(name, value);
    }

    /**
     * Resolve the value associated with key name
     * @param name the key
     * @return the value associated with key
     */
    public Object access(String name)
    {
        return _nametable.access(name);
    }

    /**
     * Whether the map contains key name
     * @param name the key
     * @return true if map contains key name
     */
    public boolean containsName(String name)
    {
        return _nametable.containsName(name);
    }
    
    /**
     * Return map representation of the nametable stack
     * @return the map
     */
    public Map asMap()
    {
        return _nametable.asMap();
    }

    /**
     * {@inheritDoc}
     */
    public String toString()
    {
        return _nametable.toString();
    }    
}