// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*-

// Copyright (c) 2001-2007 International Computer Science Institute
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software")
// to deal in the Software without restriction, subject to the conditions
// listed in the XORP LICENSE file. These conditions include: you must
// preserve this copyright notice, and you cannot mention the copyright
// holders in advertising related to the Software without their permission.
// The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
// notice is a summary of the XORP LICENSE file; the license in that file is
// legally binding.

#ident "$XORP: xorp/rtrmgr/conf_tree.cc,v 1.52 2007/02/16 22:47:21 pavlin Exp $"

#include "rtrmgr_module.h"

#include "libxorp/xorp.h"
#include "libxorp/xlog.h"
#include "libxorp/debug.h"
#include "libxorp/utils.hh"

#include "conf_tree.hh"
#include "template_commands.hh"
#include "template_tree.hh"
#include "template_tree_node.hh"
#include "util.hh"


extern int init_bootfile_parser(const char* configuration,
				const char* filename, ConfigTree* ct);
extern void parse_bootfile() throw (ParseError);
extern int booterror(const char* s) throw (ParseError);

/*************************************************************************
 * Config File class
 *************************************************************************/

ConfigTree::ConfigTree(TemplateTree* tt, bool verbose)
    : _template_tree(tt),
      _verbose(verbose)
{

}

ConfigTree::~ConfigTree()
{
    // XXX: _root_node will handle the deletion of all the tree nodes
}

#if 0
ConfigTree&
ConfigTree::operator=(const ConfigTree& orig_tree)
{
    root_node().clone_subtree(orig_tree.const_root_node());
    return *this;
}
#endif

bool
ConfigTree::parse(const string& configuration, const string& config_file,
		  string& error_msg)
{
    try {
	init_bootfile_parser(configuration.c_str(), config_file.c_str(), this);
	parse_bootfile();
	return true;
    } catch (const ParseError& pe) {
	error_msg = pe.why();
    }

    return false;
}

void ConfigTree::add_default_children()
{
    root_node().recursive_add_default_children();
}

const TemplateTreeNode*
ConfigTree::find_template(const list<string>& path_segments) const
{
    const TemplateTreeNode *ttn;

    debug_msg("----------------------------------------------------------\n");
    debug_msg("looking for template for \"%s\"\n",
	      path_as_string(path_segments).c_str());

    ttn = _template_tree->find_node(path_segments);
    return ttn;
}

const TemplateTreeNode*
ConfigTree::find_template_by_type(const list<ConfPathSegment>& path_segments) const
{
    const TemplateTreeNode *ttn;

    ttn = _template_tree->find_node_by_type(path_segments);
    return ttn;
}

list<ConfPathSegment>
ConfigTree::path_as_segments() const
{
    list<ConfPathSegment> path_segments;
    ConfigTreeNode* ctn = _current_node;

    while (ctn->parent() != NULL) {
	path_segments.push_front(ConfPathSegment(ctn->segname(), 
						 ctn->type(), ctn->node_id()));
	ctn = ctn->parent();
    }
    return path_segments;
}

string
ConfigTree::current_path_as_string() const
{
    string path;
    ConfigTreeNode* ctn = _current_node;

    while (ctn->parent() != NULL) {
	if (ctn == _current_node)
	    path = ctn->segname();
	else
	    path = ctn->segname() + " " + path;
	ctn = ctn->parent();
    }
    return path;
}

string
ConfigTree::path_as_string(const list<string>& path_segments) const
{
    string path;
    list<string>::const_iterator iter;

    for (iter = path_segments.begin(); iter != path_segments.end(); ++iter) {
	if (path.empty())
	    path = *iter;
	else
	    path += " " + *iter;
    }
    return path;
}

void
ConfigTree::extend_path(const string& segment, int type,
			const ConfigNodeId& node_id)
{
   debug_msg("extend_path: %s, %s\n", segment.c_str(), node_id.str().c_str());
    _path_segments.push_back(ConfPathSegment(segment, type, node_id));
}

void
ConfigTree::pop_path()
{
    size_t segments_to_pop = _segment_lengths.front();

    _segment_lengths.pop_front();
    for (size_t i = 0; i < segments_to_pop; i++) {
	_current_node = _current_node->parent();
    }
}

void
ConfigTree::push_path()
{
    string path = current_path_as_string();
    string nodename = _path_segments.back().segname();

    //
    // Keep track of how many segments comprise this frame so we can
    // pop the right number later.
    //
    size_t len = _path_segments.size();
    _segment_lengths.push_front(len);

    list<ConfPathSegment>::const_iterator iter;
    for (iter = _path_segments.begin(); iter != _path_segments.end(); ++iter) {
	add_node(iter->segname(), iter->type(), iter->node_id());
    }

    _path_segments.clear();
}

void
ConfigTree::add_node(const string& segment, int type,
		     const ConfigNodeId& node_id)
    throw (ParseError)
{
    list<ConfigTreeNode*>::const_iterator iter;
    ConfigTreeNode *found = NULL;

    if (_current_node->template_tree_node() != NULL
	&& _current_node->template_tree_node()->children().empty()) {
	//
	// The current node's template has no children, and we've been
	// asked to add a node as a child of it.  Either this is an
	// error, or this current node is a terminal node, and this
	// segment is actually a value.
	//
	if (_current_node->is_leaf_value()) {
	    terminal_value(segment, _current_node->type(), OP_ASSIGN);
	} else {
	    booterror("Invalid child node");
	}

	//
	// If we're still here, we didn't throw a parse error.  One
	// minor glitch is that need to decrement the path segments
	// before poppping, because this has become infated due to
	// counting the final value.
	//
	size_t segments_to_pop = _segment_lengths.front();
	_segment_lengths.pop_front();
	segments_to_pop--;
	_segment_lengths.push_front(segments_to_pop);
	return;
    }

    iter = _current_node->children().begin();
    while (iter != _current_node->children().end()) {
	if ((*iter)->segname() == segment) {
	    if (found != NULL) {
		//
		// If there are two nodes with the same segment name,
		// we can only distinguish between them by type.
		// extend_path doesn't have the type information
		// available because it wasn't at the relevant point
		// in the template file, so this is an error.  The
		// correct way to step past such a node would be
		// through a call to add_node().
		//
		string err = "Need to qualify type of " + segment + "\n";
		xorp_throw(ParseError, err);
	    }
	    found = *iter;
	}
	++iter;
    }
    if (found != NULL) {
	_current_node = found;
    } else {
	list<ConfPathSegment> path_segments = path_as_segments();
	path_segments.push_back(ConfPathSegment(segment, type, node_id));
	const TemplateTreeNode* ttn = find_template_by_type(path_segments);
	if (ttn == NULL) {
	    booterror("No template found in template map");
	}

	string path = current_path_as_string();
	if (path.empty())
	    path = segment;
	else {
	    debug_msg("path: >%s<\n", path.c_str());
	    debug_msg("segment: >%s<\n", segment.c_str());
	    path += " " + segment;
	}
	found = create_node(segment, path, ttn, _current_node, node_id,
			    /* user_id */ 0, _verbose);
	_current_node = found;
    }
}


void
ConfigTree::terminal_value(const string& value, int type, ConfigOperator op) 
    throw (ParseError)
{
    string error_msg;

    string path(current_path_as_string());
    string svalue = value;
    ConfigTreeNode *ctn = _current_node;

    XLOG_ASSERT(ctn != NULL);

    // Special case for bool types to avoid needing to type "true"
    if (svalue == "" && (type == NODE_VOID)) {
	if (ctn->type() == NODE_BOOL) {
	    type = NODE_BOOL;
	    svalue = "true";
	}
    }
    if ((ctn->type() == NODE_TEXT) && (type == NODE_TEXT)) {
	svalue = unquote(svalue);
    } else if ((ctn->type() == NODE_TEXT) && (type != NODE_TEXT)) {
	// We'll accept anything as text
    } else if ((ctn->type() == NODE_UINTRANGE) && (type == NODE_UINT)) {
	// Expand a single uint to a uintrange
	svalue += ".." + value;
    } else if ((ctn->type() == NODE_IPV4RANGE) && (type == NODE_IPV4)) {
	// Expand a single IPv4 to a ipv4range
	svalue += ".." + value;
    } else if ((ctn->type() == NODE_IPV6RANGE) && (type == NODE_IPV6)) {
	// Expand a single IPv6 to a ipv6range
	svalue += ".." + value;
    // Special case for bool types to avoid needing to type "true"
    } else if ((ctn->type() != NODE_TEXT) && (type == NODE_TEXT)) {
	//
	// The value was quoted in the bootfile.  We can't tell if
	// there's a mismatch without doing some secondary parsing.
	//
	svalue = unquote(svalue);

	switch (ctn->type()) {
	case NODE_VOID:
	    // Not clear what to do here
	    break;
	case NODE_UINT:
	    for (size_t i = 0; i < svalue.size(); i++) {
		if ((svalue[i] < '0') || (svalue[i] > '9')) {
		    goto parse_error;
		}
	    }
	    break;
	case NODE_BOOL:
	    if (svalue == "true" || svalue == "false" || svalue == "")
		break;
	    goto parse_error;
	case NODE_IPV4:
	    try {
		IPv4(svalue.c_str());
	    } catch (InvalidString) {
		goto parse_error;
	    }
	    break;
	case NODE_IPV4NET:
	    try {
		IPv4Net(svalue.c_str());
	    } catch (InvalidString) {
		goto parse_error;
	    }
	    break;
	case NODE_IPV6:
	    try {
		IPv6(svalue.c_str());
	    } catch (InvalidString) {
		goto parse_error;
	    }
	    break;
	case NODE_IPV6NET:
	    try {
		IPv6Net(svalue.c_str());
	    } catch (InvalidString) {
		goto parse_error;
	    }
	    break;
	case NODE_MACADDR:
	    try {
		Mac(svalue.c_str());
	    } catch (InvalidString) {
		goto parse_error;
	    }
	    break;
	case NODE_URL_FILE:
	case NODE_URL_FTP:
	case NODE_URL_HTTP:
	case NODE_URL_TFTP:
	    // TODO: we cannot do easily a secondary parsing of URLs
	    break;
	default:
	    // Did we forget to add a new type?
	    XLOG_FATAL("Unexpected type %d received", ctn->type());
	}
    } else if (ctn->type() != type) {
	error_msg = "\"" + path + "\" has type " + ctn->typestr() +
	    ", and value " + svalue + " is not a valid " + ctn->typestr();
	booterror(error_msg.c_str());
    }

    if (ctn->is_read_only()
	&& ctn->is_leaf_value()
	&& (! ctn->is_default_value(svalue))) {
	error_msg = "\"" + path + "\" is read-only node";
	booterror(error_msg.c_str());
    }

    if (ctn->set_value(svalue, /* userid */ 0, error_msg) != true) {
	error_msg = c_format("Cannot set the value of \"%s\": %s",
			     path.c_str(), error_msg.c_str());
	booterror(error_msg.c_str());
    }
    if (ctn->set_operator(op, /* userid */ 0, error_msg) != true) {
	error_msg = c_format("Cannot set the operator for \"%s\": %s",
			     path.c_str(), error_msg.c_str());
	booterror(error_msg.c_str());
    }
    return;

 parse_error:
    error_msg = "\"" + path + "\" has type " + ctn->typestr() +
	", and value " + svalue + " is not a valid " + ctn->typestr();
    booterror(error_msg.c_str());
}

const ConfigTreeNode*
ConfigTree::find_config_node(const list<string>& path_segments) const
{
    const ConfigTreeNode *found = &const_root_node();
    const ConfigTreeNode *found2 = found;
    list<string>::const_iterator pi;
    list<ConfigTreeNode *>::const_iterator ci;

    for (pi = path_segments.begin(); pi != path_segments.end(); ++pi) {
	for (ci = found->const_children().begin();
	     ci != found->const_children().end();
	     ++ci) {
	    if (*pi == (*ci)->segname()) {
		found2 = *ci;
		break;
	    }
	}
	if (found2 == found)
	    return NULL;
	found = found2;
    }
    return found;
}


string
ConfigTree::show_subtree(bool show_top, const list<string>& path_segments, 
			 bool numbered, bool suppress_default_values) const
{
    const ConfigTreeNode *found = find_config_node(path_segments);

    if (found == NULL)
	return "ERROR";

    string s = found->show_subtree(show_top,
				   /* depth */ 0,
				   /* indent */ 0,
				   /* do_indent */ true,
				   numbered,
				   /* annotate */ true,
				   suppress_default_values);
    return s;
}

string
ConfigTree::show_tree(bool numbered) const
{
    return const_root_node().show_subtree(/* show_top */ false,
					  /* depth */ 0,
					  /* indent */ 0,
					  /* do_indent */ true, 
					  numbered,
					  /* annotate */ true,
					  /* suppress_default_values */ false);
}

string
ConfigTree::show_unannotated_tree(bool numbered) const
{
    return const_root_node().show_subtree(/* show_top */ false,
					  /* depth */ 0,
					  /* indent */ 0,
					  /* do_indent */ true, 
					  numbered,
					  /* annotate */ false,
					  /* suppress_default_values */ false);
}

ConfigTreeNode*
ConfigTree::find_node(const list<string>& path)
{
    return root_node().find_node(path);
}

const ConfigTreeNode*
ConfigTree::find_const_node(const list<string>& path) const
{
    return const_root_node().find_const_node(path);
}

ConfigTreeNode*
ConfigTree::find_config_module(const string& module_name)
{
    return root_node().find_config_module(module_name);
}

string
ConfigTree::tree_str() const
{
    return const_root_node().subtree_str();
}

bool
ConfigTree::apply_deltas(uid_t user_id, const string& deltas,
			 bool provisional_change, bool preserve_node_id,
			 string& response)
{
    XLOG_TRACE(_verbose, "CT apply_deltas %u %s\n",
	       XORP_UINT_CAST(user_id), deltas.c_str());

    ConfigTree* delta_tree = create_tree(_template_tree, _verbose);
    if (delta_tree->parse(deltas, "", response) == false)
	return false;

    debug_msg("Delta tree:\n");
    debug_msg("%s", delta_tree->tree_str().c_str());
    debug_msg("end delta tree.\n");

    response = "";
    bool result;
    result = root_node().merge_deltas(user_id, delta_tree->const_root_node(),
				      provisional_change, preserve_node_id,
				      response);
    delete delta_tree;
    return result;
}

bool
ConfigTree::apply_deletions(uid_t user_id, const string& deletions,
			    bool provisional_change, string& response)
{
    XLOG_TRACE(_verbose, "CT apply_deletions %u %s\n",
	       XORP_UINT_CAST(user_id), deletions.c_str());

    ConfigTree *deletion_tree = create_tree(_template_tree, _verbose);
    if (deletion_tree->parse(deletions, "", response) == false)
	return false;

    debug_msg("Deletion tree:\n");
    debug_msg("%s", deletion_tree->tree_str().c_str());
    debug_msg("end deletion tree.\n");

    response = "";
    bool result;
    result = root_node().merge_deletions(user_id,
					  deletion_tree->const_root_node(),
					  provisional_change, response);
    delete deletion_tree;
    return result;
}

void
ConfigTree::retain_different_nodes(const ConfigTree& them,
				   bool retain_changed_values)
{
    root_node().retain_different_nodes(them.const_root_node(),
				      retain_changed_values);
}

void
ConfigTree::retain_deletion_nodes(const ConfigTree& them,
				  bool retain_changed_values)
{
    root_node().retain_deletion_nodes(them.const_root_node(),
				      retain_changed_values);
}

void
ConfigTree::retain_common_nodes(const ConfigTree& them)
{
    root_node().retain_common_nodes(them.const_root_node());
}

void
ConfigTree::expand_varname_to_matchlist(const string& varname,
				      list<string>& matches) const
{
    // Trim $( and )
    string trimmed = varname.substr(2, varname.size()-3);

    // Split on dots
    list<string> sl = split(trimmed, '.');

    // Copy into a vector
    size_t len = sl.size();
    vector<string> v(len);
    for (size_t i = 0; i < len; i++) {
	v[i] = sl.front();
	sl.pop_front();
    }

    const_root_node().expand_varname_to_matchlist(v, 0, matches);
}



syntax highlighted by Code2HTML, v. 0.9.1