/*
 * Copyright (c) 1998-2000 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *   Free SoftwareFoundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 *
 * $Id: Navigation.java,v 1.2 2004/09/29 00:13:49 cvs Exp $
 */

package com.caucho.web;

import java.io.*;
import java.util.*;

import org.w3c.dom.*;

import com.caucho.xpath.*;
import com.caucho.util.*;
import com.caucho.vfs.*;
import com.caucho.xml.*;

public class Navigation {
  private Element root;
  private String base;
  private Tree tree;

  public Navigation()
  {
  }

  public Navigation(Path path, String base)
    throws Exception
  {
    Document doc = new LooseXml().parseDocument(path);

    init(doc.getDocumentElement(), base);
  }

  public Navigation(Env env, Path path, String base)
    throws Exception
  {
    Document doc = new LooseXml().parseDocument(path);

    init(env, doc.getDocumentElement(), base);
  }

  /**
   * Create a new navigation structure.
   *
   * @param root the top of the navigation
   */
  public Navigation(Env env, Element root, String base)
    throws Exception
  {
    init(env, root, base);
  }

  public Navigation(Element root, String base)
    throws Exception
  {
    init(root, base);
  }

  public void init(Element root, String base)
    throws Exception
  {
    init(null,root,base);
  }

  public void init(Env env, Element root, String base)
    throws Exception
  {
    tree = new Tree(null);

    this.root = root;
    if (base == null || base == "")
      base = "/";

    this.base = base;
    
    if (root != null)
      fillChildren(env, tree, root.getFirstChild(), base);
  }

  public static Navigation createNested(Path pwd, String base)
    throws Exception
  {
    return createNested(null,pwd,base);
  }

  public static Navigation createNested(Env env, Path pwd, String base)
    throws Exception
  {
    Navigation baseNav = null;
    Navigation subNav = null;

    if (base.startsWith("/"))
      base = base.substring(1);
    
    String dir = base;
    while (true) {
      Path path = pwd.lookup(dir).lookup("toc.xml");

      Navigation nav = null;

      if (path.exists())
        nav = new Navigation(env, path, dir);

      if (baseNav == null)
        baseNav = nav;
      else if (nav != null)
        baseNav.linkParent(nav);

      if (dir.equals(""))
        break;

      int p;
      if (dir.endsWith("/")) {
        p = dir.lastIndexOf('/', dir.length() - 2);
      }
      else
        p = dir.lastIndexOf('/');

      if (p <= 0)
        dir = "";
      else
        dir = dir.substring(0, p + 1);
    }

    return baseNav;
  }

  public static Navigation createNested(ArrayList paths, String base)
    throws Exception
  {
    return createNested(null,paths,base);
  }

  public static Navigation createNested(Env env, ArrayList paths, String base)
    throws Exception
  {
    Navigation baseNav = null;
    Navigation subNav = null;

    if (base.startsWith("/"))
      base = base.substring(1);
    
    String dir = base;
    for (int i = 0; i < paths.size(); i++) {
      Path path = ((Path) paths.get(i)).lookup("toc.xml");

      Navigation nav = null;

      if (path.exists())
        nav = new Navigation(env, path, dir);

      if (baseNav == null)
        baseNav = nav;
      else if (nav != null)
        baseNav.linkParent(nav);

      if (dir.equals(""))
        break;

      int p;
      if (dir.endsWith("/")) {
        p = dir.lastIndexOf('/', dir.length() - 2);
      }
      else
        p = dir.lastIndexOf('/');

      if (p <= 0)
        dir = "";
      else
        dir = dir.substring(0, p + 1);
    }
    
    return baseNav;
  }

  public Navigation linkParent(Navigation parent)
  {
    if (tree == null) {
      tree = parent.tree;
      return this;
    }

    if (tree.getFirst() == null)
      return this;

    NavItem test = (NavItem) tree.getFirst().getData();
    NavItem link = parent.findURL(test.getLink());

    if (link == null)
      return null;

    Tree parentTree = link.getTree();
    linkTree(link.getTree(), tree.getFirst());
    this.tree = parent.tree;

    return this;
  }

  /**
   * Attaches the child tree in its proper location in the dest tree
   *
   * @param destTree parent tree 
   * @param subTree child tree
   */
  private void linkTree(Tree destTree, Tree subTree)
  {
    for (Tree child = subTree.getFirst();
         child != null;
         child = child.getNext()) {
      NavItem item = (NavItem) child.getData();
      Tree childTree = destTree.append(item);
      item.setTree(childTree);
      linkTree(childTree, child);
    }
  }

  /**
   * Returns an attribute from the top-level navigation element.
   *
   * @param name The name of the attribute.
   */
  public String getAttribute(String name)
  {
    if (root == null)
      return "";
    
    return root.getAttribute(name);
  }

  /**
   * @param url the url to match
   */
  public NavItem findURL(String url)
  {
    if (tree == null)
      return null;
    
    url = normalizeURL(url);
    
    Iterator iter = tree.dfs();
    while (iter.hasNext()) {
      Tree tree = (Tree) iter.next();
      NavItem item = (NavItem) tree.getData();

      if (item.getLink().equals(url)) {
	return item;
      }
    }

    return null;
  }
  
  /**
   */
  private void fillChildren(Env env, Tree tree, Node childNode, String base)
    throws Exception
  {
    XPathFun docShouldDisplay = env == null ? null : env.getFunction("doc-should-display");

    for (; childNode != null; childNode = childNode.getNextSibling()) {
      if (! childNode.getNodeName().equals("item"))
	continue;

      if (docShouldDisplay != null && !Expr.toBoolean(docShouldDisplay.eval(childNode,env,null,null)))
        continue;

      Element elt = (Element) childNode;

      NavItem item = new NavItem();
      String href = linkPattern.evalString(elt);

      item.setLink(resolveURL(href, childNode, base));
      item.setTitle(titlePattern.evalString(elt));

      String desc;
      desc = descPattern.evalString(elt);
      item.setDescription(desc);

      item.setProduct(_productPattern.evalString(elt));

      Tree childTree = tree.append(item);
      item.setTree(childTree);

      fillChildren(env, childTree, childNode.getFirstChild(), base);
    }
  }

  private String resolveURL(String url, Node node, String base)
  {
    if (url.length() == 0)
      return "/";
    
    if (url.startsWith("http:") || url.charAt(0) == '/')
      return url; //  normalizeURL(url);

    for (; node instanceof Element; node = node.getParentNode()) {
      Element elt = (Element) node;
    
      String nodeBase = elt.getAttribute("xml:base");

      if (nodeBase.equals(""))
        continue;
      
      if (! nodeBase.endsWith("/"))
        return resolveURL(nodeBase + "/" + url, elt.getParentNode(), base);
      else
        return resolveURL(nodeBase + url, elt.getParentNode(), base);
    }
    
    if (! base.endsWith("/"))
      return normalizeURL(base + "/" + url);
    else
      return normalizeURL(base + url);
  }
  
  private String normalizeURL(String url)
  {
    if (url.startsWith("/"))
      return url;
    else if (url.startsWith("http://"))
      return url;
    else
      return "/" + url;

    /*
    int i;
    for (i = "http://".length(); i < url.length(); i++) {
      if (url.charAt(i) == '/') {
        return url.substring(i);
      }
    }
    
    return "/";
    */
  }

  static Expr linkPattern;
  static Expr titlePattern;
  static Expr descPattern;
  static Expr _productPattern;
  static {
    try {
      linkPattern = XPath.parseExpr("if(@link,@link,link)");
      titlePattern = XPath.parseExpr("if(@title,@title,title)");
      descPattern = XPath.parseExpr("if(@description,@description,description)");
      _productPattern = XPath.parseExpr("if(@product,@product,product)");
    } catch (Exception e) {
    }
  }
}
