package de.spieleck.config;

import java.io.*;
import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;

import de.spieleck.util.EmptyIterator;


/**
 * Basic Data container in a Config environment. 
 * <P>
 * Not that most enhanced classes will be subnodes of this or
 * otherwise have to do a reimplementation of many methods
 */
public class ConfigNodeImpl
 implements ConfigNode
{
    public final static char PATHSEP = '/';

    /** Remember your parent(s), boy */
    protected ConfigNode parent;

    /** And cound your children! */
    protected List children;

    /** Node name */
    protected String name;

    /** Node value */
    protected String value;

    /** A Parameter-Mapping class */
    protected ConfigParamMap pm;

    public ConfigNodeImpl(String name, String value, ConfigParamMap pm)
    {
        this.name  = name == null ? null : name.intern();
        // Names are supposed to repeat more than values,
        // therefore we switch to a more moderate interning:
        // this.value = value == null ? null : value.intern();
        this.value = value;
        this.pm = pm;
    }

    public String getSourceFileName()
    {
        ConfigFileNode srn = getBranchNode();
        if (srn == null)
            return null;
        else
            return srn.getSourceFileName();
    }

    /**
   * Getting the node responsible for reading the file.
   * This API is used so rarely that we do not keep a member to hold
   * that node any more. We search the node every time we need it.
   */
    public ConfigFileNode getBranchNode()
    {
        ConfigFileNode branchNode = null;
        while (branchNode == null)
        {
            ConfigNode n = getParent();
            if (n != null)
                branchNode = n.getBranchNode();
        }

        return branchNode;
    }

    public String getName()
    {
        return name;
    }

    public String getPath()
    {
        if (getParent() == null)
            return ""; // getName();
        else
            return getParent().getPath() + PATHSEP + getName();
    }

    /* package */ void setValue(String v)
    {
        value = v;
    }

    protected String getValue()
    {
        return pm.expand(value);
    }

    // XXX: The interface only need ConfigNode, but we need ConfigNodeImpl
    // somewhere else, this is a modelation problem!
    public ConfigNode getParent()
    {
        return parent;
    }

    protected void setParent(ConfigNode p)
    {
        parent = p;
    }

    /**
   *
   *
   */
    public boolean getBoolean()
    {
        return ConvertHelp.getBoolean(getValue());
    }

    public boolean getBoolean(String path, boolean deflt)
    {
        return ConvertHelp.getBoolean(node(path), deflt);
    }

    public boolean getInhBoolean(String path, boolean deflt)
    {
        return ConvertHelp.getBoolean(nodeInh(path), deflt);
    }

    public int getInt()
    {
        return ConvertHelp.getInt(getValue());
    }

    public int getInt(String path, int deflt)
    {
        return ConvertHelp.getInt(node(path), deflt);
    }

    public int getInhInt(String path, int deflt)
    {
        return ConvertHelp.getInt(nodeInh(path), deflt);
    }

    public double getDouble()
    {
        return ConvertHelp.getDouble(getValue());
    }

    public double getDouble(String path, double deflt)
    {
        return ConvertHelp.getDouble(node(path), deflt);
    }

    public double getInhDouble(String path, double deflt)
    {
        return ConvertHelp.getDouble(nodeInh(path), deflt);
    }

    public String getString()
    {
        return getValue();
    }

    public String getString(String path, String deflt)
    {
        return ConvertHelp.getString(node(path), deflt);
    }

    public String getInhString(String path, String deflt)
    {
        return ConvertHelp.getString(nodeInh(path), deflt);
    }

    /**
   * Find the very first child, grandchild, ... that matches a path!
   * Node this is fairly involved and not at all fast! But appropriate
   * for setup files in general.
   */
    public ConfigNode node(String path)
    {
        if (path == null)
            return this;

        return node(path, 0, path.length());
    }

    private ConfigNodeImpl node(String path, int begindex, int endIndex)
    {
        // Internal API, path != null here!
        int head = begindex;
        while (head < endIndex && path.charAt(head) == PATHSEP)
            head++;
        if (head == endIndex)
            return this;
        if (children == null) // children.size() == 0 impossible!
            return null;
        int tail = head + 1;
        while (tail < endIndex && path.charAt(tail) != PATHSEP)
            tail++;
        String match = path.substring(head, tail);
        for(Iterator it = children(); it.hasNext(); )
        {
            ConfigNodeImpl child = (ConfigNodeImpl)it.next();
            if (child.getName().equals(match))
            {
                ConfigNodeImpl node = child.node(path, tail, endIndex);
                if (node != null)
                    return node;
            }
        }

        return null;
    }

    /**
   * Find the very first child of this node or a parent
   * fullfilling the path.
   */
    public ConfigNode nodeInh(String path)
    {
        ConfigNode snr = node(path);
        if (snr != null)
            return snr;
        ConfigNode current = this;
        while (snr == null && (current = current.getParent()) != null)
            snr = current.node(path);

        return snr;
    }

    public int countChildren()
    {
        return children == null
                   ? 0
                   : children.size();
    }

    public Iterator children()
    {
        if ( children == null )
            return EmptyIterator.getInstance();
        return new ConfigIterator(null);
    }

    public Iterator childrenNamed(String key)
    {
        if ( children == null )
            return EmptyIterator.getInstance();
        return new ConfigIterator(key);
    }

    public int countChildrenNamed(String key)
    {
        if ( children == null )
            return 0;
        Iterator it = childrenNamed(key);
        int count = 0;
        while ( it.hasNext() )
            if ( ((ConfigNode)it.next()).getName().equals(key) )
                count++;
        return count;
    }

    public void copyChildren(ConfigNode next)
    {
        Iterator e = next.children();
        while (e.hasNext())
        {
            ConfigNodeImpl n = (ConfigNodeImpl)e.next();
            addChild(n);
        }
    }

    public ConfigNodeImpl addChild(String name, String value)
    {
        ConfigNodeImpl child = new ConfigNodeImpl(name, value, pm);

        return addChild(child);
    }

    public ConfigNodeImpl addChild(ConfigNodeImpl child)
    {
        if (children == null)
            children = new LinkedList();
        children.add(child);
        child.setParent(this);

        return child;
    }

    /**
   *
   */
    public void print(PrintWriter os)
     throws IOException
    {
        print(os, 0);
    }

    private void print(PrintWriter os, int depth)
     throws IOException
    {
        spc(os, depth);
        os.print("<" + getName());
        if (getValue() != null)
            os.print(" this=\"" + getValue() + "\"");
        int length                 = countChildren();
        boolean hasElementChildren = false;
        for ( Iterator it = children(); it.hasNext(); )
        {
            ConfigNodeImpl child = (ConfigNodeImpl)it.next();
            if (!hasElementChildren && !(child.countChildren() > 0) )
                os.print(" " + child.getName() + "=\""
                         + child.getValue() + "\"");
            else
            {
                if (!hasElementChildren)
                    os.println(">");
                hasElementChildren = true;
                child.print(os, depth + 2);
            }
        }
        if (!hasElementChildren)
            os.println("/>");
        else
        {
            spc(os, depth);
            os.println("</" + getName() + ">");
        }
    }

    private static void spc(PrintWriter os, int depth)
     throws IOException
    {
        for (int i = 0; i < depth; i++)
            os.print(' ');
    }

    public String toString()
    {
        return "["+super.toString()+"(" + getPath() + ", " + getValue() + "), ("
               + getSourceFileName() + ")" + ", " + countChildren()
               + "]";
    }

    /**
     *
     */
    protected class ConfigIterator
       implements Iterator
    {
        private Iterator it;
        private String key = null;
        private ConfigNodeImpl ci;

        public ConfigIterator(String key)
        {
            if ( key != null )
                this.key = key.intern();
            if ( children == null )
                it = EmptyIterator.getInstance();
            else
                it = children.iterator();
            skip();
        }

        private void skip()
        {
            ci = null;
            while ( ci == null && it.hasNext() )
            {
                ci = (ConfigNodeImpl) it.next();
                if ( key != null && key != ci.getName() )
                    ci = null;
            }
        }

        public boolean hasNext()
        {
            return ci != null;
        }

        public Object next()
        {
            ConfigNodeImpl c2 = ci;
            skip();
            return c2;
        }

        public void remove()
          throws UnsupportedOperationException
        {
            throw new UnsupportedOperationException("Config is immutable.");
        }
    }
}
//
//    Jacson - Text Filtering with Java.
//    Copyright (C) 2002 Frank S. Nestel (nestefan -at- users.sourceforge.net)
//
//    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
//