// Copyright (c) 2001, 2002, 2003  Per M.A. Bothner and Brainfood Inc.
// This is free software;  for terms and warranty disclaimer see ./COPYING.

package gnu.kawa.xml;
import gnu.mapping.*;
import gnu.lists.*;
import gnu.xml.*;
import java.net.URL;
import gnu.text.*;
import gnu.kawa.functions.BaseUri;

/** Implement the XQuery function 'document'. */

public class Document extends Procedure1
{
  public static final Document document = new Document();

  /** Resolve relative URI, and return an URL instance. */
  public static URL makeURL(Object url, Object base)
    throws java.net.MalformedURLException
  {
    if (url instanceof URL)
      return (URL) url;
    String name = url.toString();
    if (! BaseUri.hasScheme(name))
      {
	if (base != null)
	  {
	    Object b = BaseUri.baseUri(base);
	    name = BaseUri.resolve(name,
			   b == Values.empty ?  base.toString()
			   : b.toString());
	  }
	if (! BaseUri.hasScheme(name))
	  {
	    name = BaseUri.resolve(name, BaseUri.baseUri().toString());
	  }
      }
    return new URL(name);
  }

  public static URL makeURL(Object url)
    throws java.net.MalformedURLException
  {
    return makeURL(url, null);
  }

  public static void parse (Object name, Consumer out) throws Throwable
  {
    URL url = makeURL(name, null);
    SourceMessages messages = new SourceMessages();
    XMLParser parser = new XMLParser(url, messages, out);
    out.beginDocument();
    if (out instanceof TreeList)
      ((TreeList) out).writeBaseUri(url);
    parser.parse();
    if (messages.seenErrors())
      throw new SyntaxException("document function read invalid XML",
				messages);
    out.endDocument();
  }

  public static KDocument parse (Object url) throws Throwable
  {
    NodeTree doc = new NodeTree();
    parse(url, doc);
    return new KDocument(doc, 0);
  }

  /** Internal namespace used to mange cached documents. */
  static String docNamespace = "http://gnu.org/kawa/cached-documents";

  public static Object parseCached (URL url)
    throws Throwable
  {
    Symbol sym = Symbol.make(docNamespace, url.toString());
    Environment env = Environment.getCurrent();
    synchronized (sym)
      {
        NamedLocation loc = env.getLocation(sym, null, true);
        Object val = loc.get(null);
        if (val != null)
          return val;

        NodeTree tree = new NodeTree();
        SourceMessages messages = new SourceMessages();
        XMLParser parser = new XMLParser(url, messages, tree);
        tree.beginDocument();
        tree.writeBaseUri(url);
        parser.parse();
        if (messages.seenErrors())
          throw new SyntaxException("document function read invalid XML",
                                    messages);
        tree.endDocument();
        val = new KDocument(tree, 0);
        loc.set(val);
        return val;
      }
  }

  /** Parse an XML document, caching the result.
   * Only positive results are cached; failures are not.)
   * This implements the standard XQuery <code>fn:doc</code> function.
   */
  public static Object parseCached (Object url, String base)
    throws Throwable
  {
    return parseCached(makeURL(url, base));
  }

  /** Check if an XML document is available, caching the result.
   * Only positive results are cached; failures are not.  Thus it is possible
   * for a false result to be followed by a true result, but not vice versa.
   * This implements the standard XQuery <code>fn:doc-available</code> function.
   */
  public static boolean availableCached (Object url, String base)
    throws java.net.MalformedURLException
  {
    URL resolved = makeURL(url, base);
    try
      {
        parseCached(resolved);
        return true;
      }
    catch (Throwable ex)
      {
        return false;
      }
  }

  public Object apply1 (Object arg1) throws Throwable
  {
    return parse(arg1.toString());
  }

  public void apply (CallContext ctx) throws Throwable
  {
    Object url = ctx.getNextArg();
    Object base = ctx.getNextArg(null);
    if (url instanceof Values)
      {
	int iter = 0;
	Values vals = (Values) url;
	for (;;)
	  {
	    iter = vals.nextPos(iter);
	    if (iter == 0)
	      break;
	    Object val = vals.getPosPrevious(iter);	
	    parse(makeURL(url, base), ctx.consumer);
	  }
      }
    else
      {
	parse(makeURL(url, base), ctx.consumer);
      }
  }
}
