/* Copyright (c) 2008 Charles Rich and Worcester Polytechnic Institute.
 * All Rights Reserved.  Use is subject to license terms.  See the file
 * "license.terms" for information on usage and redistribution of this
 * file and for a DISCLAIMER OF ALL WARRANTIES.
 */
package edu.wpi.cetask;

import javax.xml.namespace.QName;
import javax.xml.XMLConstants;
import javax.xml.xpath.*;

import org.w3c.dom.*;

import java.util.*;

/**
 * Base class for parts of a Task Model Description
 */
abstract class Description {
   
   public static final XPath XPATH = XPathFactory.newInstance().newXPath();
   private static final String about = "/taskModel/@about";
   
   private final String namespace;
   public String getNamespace () { return namespace; }

   /**
    * Node in DOM
    */
   protected final Node node;
   
   /**
    * Task engine in which DOM resides
    */
   protected final TaskEngine engine; 
   public TaskEngine getEngine () { return engine; }
   
   protected Description (Node node, TaskEngine engine) { 
      if ( node == null ) throw new IllegalArgumentException("Null node");
      this.node = node; 
      this.engine = engine;
      namespace = xpath(about);
      // task models already cached separately in engine
      if ( !(this instanceof TaskModel) ) engine.put(node, this);
   }

   // abstraction to unify processing of Scripts and Decompositions
   interface Taskable { 
      TaskClass getTask ();  
      Boolean isApplicable (Task task);
      String getPlatform ();
      String getDeviceType ();
   }
   
   Taskable newTaskable (String element, Node node, TaskClass task) { 
      return element.equals("script") ? getScript(node, task) :
         element.equals("subtasks") ? getDecompositionClass(node, task) :
            null; // internal error
   }
    
   public TaskClass resolveTaskClass (String qname) {
      QName q = parseQName(qname);
      return engine.getModel(q.getNamespaceURI()).getTaskClass(q.getLocalPart()); 
   }
   
   // factory methods for subclasses
   
   TaskClass getTaskClass (Node node, TaskModel model) {
      if ( node == null ) return null;
      TaskClass task = (TaskClass) engine.get(node);
      if ( task == null ) task = new TaskClass(node, model);
      return task;
   }
   
   DecompositionClass getDecompositionClass (Node node, TaskClass goal) {
      DecompositionClass decomp = (DecompositionClass) engine.get(node);
      if ( decomp == null ) 
         try { decomp = new DecompositionClass(node, 
                  engine.getModel(XPATH.evaluate(about, node)), 
                  goal); 
         } catch (XPathExpressionException e) { throw new RuntimeException(e); }
      return decomp;
   }
       
   Script getScript (Node node, TaskClass task) {
      Script script = (Script) engine.get(node);
      if ( script == null ) script = new Script(node, engine, task);
      return script;
   }
   
   // convenient utilities for subclasses
      
   protected Object xpath (String path, QName returnType) {
      try { return  XPATH.evaluate(path, node, returnType); } 
      catch (XPathExpressionException e) { throw new RuntimeException(e); }
   }
   
   protected String xpath (String path) {
      try { return XPATH.evaluate(path, node); } 
      catch (XPathExpressionException e) { throw new RuntimeException(e); }
   }
   
   protected List<String> xpathValues (String path) {
      List<String> values = new ArrayList<String>();
      for (Node node : xpathNodes(path))
         values.add(node.getNodeValue());
      return values;
   }
   
   protected List<Node> xpathNodes (String path) { 
      List<Node> nodes = new ArrayList<Node>();
      NodeList nodelist = (NodeList) xpath(path, XPathConstants.NODESET);
      for (int i = 0; i < nodelist.getLength(); i++) // preserve order
         nodes.add(nodelist.item(i));
      return nodes;
   }
   
   protected QName parseQName (String qname) {
      String prefix;
      int i = qname.indexOf(':');
      return i >= 0 ?
         new QName(Utils.lookupNamespaceURI(node, prefix = qname.substring(0, i)), 
            qname.substring(i+1),
            prefix) :
         // default namespace is from 'about', not xmlns
         new QName(getNamespace(), qname, XMLConstants.DEFAULT_NS_PREFIX); 
   }
 
}