/**
   * Loads definitions of all agents
   *
   * @param node
   */
  @SuppressWarnings("unchecked")
  private void loadAgents(Node root, ClassLoader classLoader) throws Exception {
    ArrayList<Node> list = XmlDocUtils.getChildrenByTagName(root, "agents");
    // No agents
    if (list.size() < 1) return;

    list = XmlDocUtils.getChildrenByTagName(list.get(0), "agent");
    Class<? extends Agent> agentType;

    for (int i = 0; i < list.size(); i++) {
      Node node = list.get(i);

      // Agent's priority
      int priority = XmlDocUtils.getIntegerValue(node, "priority", Observer.LOW_PRIORITY);
      // Agent's step time
      RationalNumber time = RationalNumber.parse(XmlDocUtils.getValue(node, "time", "1"));
      // Agent's class
      String className = node.getTextContent().trim();
      if (classLoader != null) {
        agentType = (Class<Agent>) classLoader.loadClass(className);
      } else {
        agentType = (Class<Agent>) Class.forName(className);
      }

      // Add agent's definition to the model
      addAgentType(agentType, time, priority);
    }
  }
  /**
   * Loads model methods
   *
   * @param model
   * @param root
   */
  private void loadMethods(SparkModel model, Node root) {
    ArrayList<Node> nodes = XmlDocUtils.getChildrenByTagName(root, "methods");
    if (nodes.size() > 0) nodes = XmlDocUtils.getChildrenByTagName(nodes.get(0), "method");

    for (int i = 0; i < nodes.size(); i++) {
      ModelMethod method = ModelMethod.loadMethod(model, nodes.get(i));
      if (!addMethod(method)) {
        logger.error("Method " + method.getName() + " is already defined");
      }
    }
  }
  /** Creates a chart panel based on the given xml-node */
  public SparkChartPanel(WindowManager manager, Node node) {
    this.xmlNode = node;

    int interval = XmlDocUtils.getIntegerValue(node, "interval", 1);
    String stype = XmlDocUtils.getValue(node, "chart-type", ChartType.LINE_CHART.toString());
    String location = XmlDocUtils.getValue(node, "location", null);

    ChartType type = ChartType.valueOf(stype);

    init(manager, interval, location, type);
    addSeries(node);
  }
  /**
   * Adds a chart information to the existing chart panel
   *
   * @param node
   */
  public void addSeries(Node node) {
    // TODO: everything could be done with expressions only

    String varNames = XmlDocUtils.getValue(node, "variable", null);
    String label = XmlDocUtils.getValue(node, "label", "");

    String expressions = XmlDocUtils.getValue(node, "expressions", null);
    String exprLabels = XmlDocUtils.getValue(node, "expr-labels", "");

    // Single variables
    if (varNames != null) {
      String[] names = varNames.split(";");
      String[] labels = label.split(";");
      int i = 0;

      for (String name : names) {
        if (name == null || name.length() == 0) continue;

        label = labels[i];
        name = name.trim();

        addSeries(name, label);
        if (i < labels.length - 1) i += 1;
      }
    }

    // Expressions
    if (expressions != null) {
      String[] exprs = expressions.split(";");
      String[] labels = exprLabels.split(";");
      int i = 0;

      for (String expr : exprs) {
        if (expr == null || expr.length() == 0) continue;

        label = labels[i];
        UserFunction f;

        try {
          f = UserFunction.create(expr);
        } catch (Exception ex) {
          ex.printStackTrace();
          continue;
        }

        addSeries(f, label);
        if (i < labels.length - 1) i += 1;
      }
    }
  }
  /**
   * Internal method for loading a model from the given xml document
   *
   * @param xmlDoc
   * @param rootPath
   * @return
   */
  private SparkModel load(Document xmlDoc, File rootPath) throws Exception {
    SparkModel model = null;
    ClassLoader classLoader = null;

    ArrayList<Node> nodes;
    Node root = xmlDoc.getFirstChild();
    Node node;

    if (root == null) throw new Exception("No root node in the xml document");

    root = XmlDocUtils.getChildByTagName(root, "model");

    if (root == null) throw new Exception("No model node in the xml document");

    /* Load class path */
    classLoader = setupClassPath(rootPath);

    /* Load main model class */
    nodes = XmlDocUtils.getChildrenByTagName(root, "setup");
    if (nodes.size() != 1) throw new Exception("The model class must be uniquely specified");

    node = nodes.get(0);

    String setupName = node.getTextContent().trim();
    if (classLoader != null) {
      model = (SparkModel) classLoader.loadClass(setupName).newInstance();
    } else {
      model = (SparkModel) Class.forName(setupName).newInstance();
    }

    startConstruction(model);

    /* Load basic parameters */
    loadBasicModelParameters(root);

    /* Load agents */
    loadAgents(root, classLoader);

    /* Load variables */
    loadVariables(model, root);

    /* Load methods */
    loadMethods(model, root);

    return model;
  }
  /**
   * Loads model variables
   *
   * @param model
   * @param root
   */
  private void loadVariables(SparkModel model, Node root) {
    ArrayList<Node> nodes = XmlDocUtils.getChildrenByTagName(root, "variables");
    if (nodes.size() > 0) nodes = XmlDocUtils.getChildrenByTagName(nodes.get(0), "variable");

    for (int i = 0; i < nodes.size(); i++) {
      ModelVariable var = null;

      try {
        var = ModelVariable.loadVariable(model, nodes.get(i));
      } catch (Exception e) {
        logger.error("Cannot create a variable");
        continue;
      }

      if (!addModelVariable(var)) {
        logger.error("A variable " + var.getName() + " is already declared");
      }
    }
  }
  /**
   * Loads basic model parameters
   *
   * @param model
   * @param root
   */
  private void loadBasicModelParameters(Node root) {
    /* Load tick size */
    RationalNumber tickTime = RationalNumber.parse(XmlDocUtils.getValue(root, "tick", "1"));

    // Set model's tick time
    setTickTime(tickTime);

    // Set model's default observer and execution mode
    ArrayList<Node> nodes = XmlDocUtils.getChildrenByTagName(root, "setup");

    String defaultObserver =
        XmlDocUtils.getValue(nodes.get(0), "observer", ObserverFactory.DEFAULT_OBSERVER_NAME);
    int defaultExecutionMode = ExecutionMode.SERIAL_MODE;
    try {
      defaultExecutionMode =
          ExecutionMode.parse(XmlDocUtils.getValue(nodes.get(0), "mode", "serial"));
    } catch (Exception e) {
      logger.error(e);
    }

    setDefaultObserver(defaultObserver, defaultExecutionMode);
  }
  /** Updates XML node */
  public void updateXML(
      SparkWindow location, Document xmlModelDoc, Node interfaceNode, File xmlModelFile) {
    // TODO: update an existing node (when an editing mode is implemented)
    // Existing problem: separate chart nodes from xml-document are combined
    // in one
    // chart panel now. Saving the existing chart panel will result in an
    // inconsistency.
    if (xmlNode != null) return;

    // Variable names and labels
    StringBuilder varNames = new StringBuilder();
    StringBuilder labels = new StringBuilder();

    // Expressions and their labels
    StringBuilder exprs = new StringBuilder();
    StringBuilder exprLabels = new StringBuilder();

    boolean firstVarFlag = true;
    boolean firstExprFlag = true;

    for (SeriesInfo info : series) {
      if (info.varName == null) {
        if (!firstExprFlag) {
          exprs.append(';');
          exprLabels.append(';');
        }

        exprs.append(info.function.toString());
        exprLabels.append(info.label);
        firstExprFlag = false;
      } else {
        if (!firstVarFlag) {
          varNames.append(';');
          labels.append(';');
        }

        varNames.append(info.varName);
        labels.append(info.label);
        firstVarFlag = false;
      }
    }

    // Create a new xml-node
    xmlNode = xmlModelDoc.createElement("user-chart");

    // location
    XmlDocUtils.addAttr(xmlModelDoc, xmlNode, "location", location.getName());

    // chart-type
    XmlDocUtils.addAttr(xmlModelDoc, xmlNode, "chart-type", type);

    // name
    // TODO: name != location in general
    XmlDocUtils.addAttr(xmlModelDoc, xmlNode, "name", location.getName());
    // interval
    XmlDocUtils.addAttr(xmlModelDoc, xmlNode, "interval", 1);
    // variables
    XmlDocUtils.addAttr(xmlModelDoc, xmlNode, "variable", varNames.toString());
    // labels
    XmlDocUtils.addAttr(xmlModelDoc, xmlNode, "label", labels.toString());

    if (exprs.length() > 0) {
      XmlDocUtils.addAttr(xmlModelDoc, xmlNode, "expressions", exprs.toString());
      XmlDocUtils.addAttr(xmlModelDoc, xmlNode, "expr-labels", exprLabels.toString());
    }

    // Add this node to the document
    interfaceNode.appendChild(xmlNode);
  }