// Copyright 2008-2014 severally by the contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package net.sf.practicalxml.xpath;

import java.util.Collections;
import java.util.List;

import javax.xml.namespace.QName;
import javax.xml.xpath.XPathFunctionException;

import org.w3c.dom.NodeList;


/**
 *  A base class for writing XPath functions that work with string data. Will
 *  convert its first argument into a string before calling {@link #evaluate},
 *  and provides a helper method to convert other arguments.
 */
public abstract class AbstractStringFunction
extends FunctionResolver.AbstractSelfDescribingFunction
{
    /**
     *  Constructor for a function that takes a single argumment.
     */
    protected AbstractStringFunction(String nsUri, String localName)
    {
        super(new QName(nsUri, localName), 1, 1);
    }


    /**
     *  Constructor for a function that has a fixed number of arguments.
     */
    protected AbstractStringFunction(String nsUri, String localName, int numArgs)
    {
        super(new QName(nsUri, localName), numArgs, numArgs);
    }


    /**
     *  Constructor for a function that has a variable number of arguments.
     */
    protected AbstractStringFunction(String nsUri, String localName, int minArgs, int maxArgs)
    {
        super(new QName(nsUri, localName), minArgs, maxArgs);
    }


//----------------------------------------------------------------------------
//  Implementation of XPathFunction
//----------------------------------------------------------------------------

    @SuppressWarnings("rawtypes")
    public final Object evaluate(List args)
    throws XPathFunctionException
    {
        if (args == null)
            args = Collections.EMPTY_LIST;

        if ((args.size() < getMinArgCount()) || (args.size() > getMaxArgCount()))
            throw new XPathFunctionException("illegal argument count: " + args.size());

        try
        {
            // if you use this class to implement a function that doesn't take arguments,
            // you deserve what you get
            return evaluate(convertToString(args.get(0)), args);
        }
        catch (Exception e)
        {
            throw new XPathFunctionException(e);
        }
    }


//----------------------------------------------------------------------------
//  Methods for subclasses to override
//----------------------------------------------------------------------------

    /**
     *  Subclasses must implement this method. It is passed the first argument
     *  already converted to a string, as well as the original list of arguments.
     */
    public abstract Object evaluate(String value, List<?> args);


//----------------------------------------------------------------------------
//  Helpers
//----------------------------------------------------------------------------

    /**
     *  Converts the passed object to a string, applying the same rules used for the
     *  built-in <a href="http://www.w3.org/TR/1999/REC-xpath-19991116/#function-string">string()</a>
     *  function.
     */
    protected static String convertToString(Object arg)
    {
        if (arg instanceof String)
        {
            return (String)arg;
        }
        else if (arg instanceof NodeList)
        {
            NodeList nodelist = (NodeList)arg;
            if (nodelist.getLength() == 0) return "";
            else return nodelist.item(0).getTextContent();
        }
        else if (arg instanceof Double)
        {
            // this case covers the numeric values returned by the JDK
            Double value = (Double)arg;
            double dvalue = value.doubleValue();
            if (value.isNaN()) return "NaN";
            else if (dvalue == Double.POSITIVE_INFINITY) return "Infinity";
            else if (dvalue == Double.NEGATIVE_INFINITY) return "-Infinity";
            else if (value.longValue() == dvalue) return String.valueOf(value.longValue());
            else return value.toString();
        }
        else if (arg instanceof Number)
        {
            // this is a catch-all for other implementations; may not pass tests
            return arg.toString();
        }
        else if (arg instanceof Boolean)
        {
            return ((Boolean)arg).booleanValue() ? "true" : "false";
        }
        else
            throw new IllegalArgumentException("unsupported argument type: " + arg.getClass().getName());
    }
}
