/* 
 * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
 * 
 * This program 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.1 of the License, or (at your option) any later version.
 * 
 * This software 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.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <lha_internal.h>

#include <sys/param.h>
#include <crm/crm.h>
#include <crm/cib.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/msg.h>

#include <glib.h>

#include <allocate.h>
#include <lib/crm/pengine/utils.h>
#include <utils.h>

gboolean update_action(action_t *action);

gboolean
update_action_states(GListPtr actions)
{
	crm_debug_2("Updating %d actions", g_list_length(actions));
	slist_iter(
		action, action_t, actions, lpc,

		update_action(action);
		);

	return TRUE;
}

gboolean
update_action(action_t *action)
{
	int local_type = 0;
	int log_level = LOG_DEBUG_2;
	gboolean changed = FALSE;
	
	do_crm_log(log_level, "Processing action %s: %s %s %s",
		   action->uuid,
		   action->optional?"optional":"required",
		   action->runnable?"runnable":"unrunnable",
		   action->pseudo?"pseudo":action->task);

	slist_iter(
		other, action_wrapper_t, action->actions_before, lpc,

		gboolean other_changed = FALSE;
		node_t *node = other->action->node;
		resource_t *other_rsc = other->action->rsc;
		enum rsc_role_e other_role = RSC_ROLE_UNKNOWN;

		if(other_rsc) {
		    other_role = other_rsc->fns->state(other_rsc, TRUE);
		}
		
		do_crm_log(log_level, "   Checking action %s: %s %s %s (flags=0x%.6x)",
			   other->action->uuid,
			   other->action->optional?"optional":"required",
			   other->action->runnable?"runnable":"unrunnable",
			   other->action->pseudo?"pseudo":other->action->task,
			   other->type);

		local_type = other->type;

		if((local_type & pe_order_demote)
		   && other->action->pseudo == FALSE
		   && other_role > RSC_ROLE_SLAVE
		   && node != NULL
		   && node->details->online) {
		    local_type |= pe_order_runnable_left;
		    do_crm_log(log_level,"Upgrading restart constraint to runnable_left");
		}

		if((local_type & pe_order_shutdown)
		   && other->action->optional == FALSE
		   && is_set(other_rsc->flags, pe_rsc_shutdown)) {
		    action->optional = FALSE;
		    changed = TRUE;
		    do_crm_log(log_level-1,
			       "   * Marking action %s manditory because of %s (complex)",
			       action->uuid, other->action->uuid);
		}
		
		if((local_type & pe_order_restart)
		   && other_role > RSC_ROLE_STOPPED) {

		    if(other_rsc->variant == pe_native) {
			local_type |= pe_order_implies_left;
			do_crm_log(log_level,"Upgrading restart constraint to implies_left");
		    }
		    
		    if(other->action->optional
		       && other->action->runnable
		       && action->runnable == FALSE) {
			do_crm_log(log_level-1,
				   "   * Marking action %s manditory because %s is unrunnable",
				   other->action->uuid, action->uuid);
			other->action->optional = FALSE;
			set_bit(other_rsc->flags, pe_rsc_shutdown);
			other_changed = TRUE;
		    } 
		}

		if((local_type & pe_order_runnable_left)
			&& other->action->runnable == FALSE) {
			if(other->action->implied_by_stonith) {
				do_crm_log(log_level, "Ignoring un-runnable - implied_by_stonith");

			} else if(action->runnable == FALSE) {
				do_crm_log(log_level+1, "Already un-runnable");
				
			} else {
				action->runnable = FALSE;
				do_crm_log(log_level-1,
					   "   * Marking action %s un-runnable because of %s",
					   action->uuid, other->action->uuid);
				changed = TRUE;
			}
		}

		if((local_type & pe_order_runnable_right)
			&& action->runnable == FALSE) {
			if(action->pseudo) {
				do_crm_log(log_level, "Ignoring un-runnable - pseudo");

			} else if(other->action->runnable == FALSE) {
				do_crm_log(log_level+1, "Already un-runnable");
				
			} else {
				other->action->runnable = FALSE;
				do_crm_log(log_level-1,
					   "   * Marking action %s un-runnable because of %s",
					   other->action->uuid, action->uuid);
				other_changed = TRUE;
			}
		}		
		
		if(local_type & pe_order_implies_left) {
			if(other->action->optional == FALSE) {
				/* nothing to do */
				do_crm_log(log_level+1, "      Ignoring implies left - redundant");
				
			} else if(safe_str_eq(other->action->task, CRMD_ACTION_STOP)
				  && other_role == RSC_ROLE_STOPPED) {
				do_crm_log(log_level-1, "      Ignoring implies left - %s already stopped",
					other_rsc->id);

			} else if((local_type & pe_order_demote)
				  && other_rsc->role < RSC_ROLE_MASTER) {
			    do_crm_log(log_level-1, "      Ignoring implies left - %s already demoted",
				       other_rsc->id);
			    
			} else if(action->optional == FALSE) {
				other->action->optional = FALSE;
				do_crm_log(log_level-1,
					   "   * (implies left) Marking action %s mandatory because of %s",
					   other->action->uuid, action->uuid);
				other_changed = TRUE;
				
			} else {
				do_crm_log(log_level, "      Ignoring implies left");
			}
		}
		
		if(local_type & pe_order_implies_right) {
			if(action->optional == FALSE) {
				/* nothing to do */
				do_crm_log(log_level+1, "      Ignoring implies right - redundant");
			} else if(other->action->optional == FALSE) {
				action->optional = FALSE;
				do_crm_log(log_level-1,
					   "   * (implies right) Marking action %s mandatory because of %s",
					   action->uuid, other->action->uuid);
				changed = TRUE;
				
			} else {
				do_crm_log(log_level, "      Ignoring implies right");
			}
		}

		if(other_changed) {
			do_crm_log(log_level, "%s changed, processing after list", other->action->uuid);
			update_action(other->action);
			slist_iter(
				before_other, action_wrapper_t, other->action->actions_after, lpc2,
				do_crm_log(log_level, "%s changed, processing %s", other->action->uuid, before_other->action->uuid);
				update_action(before_other->action);
				);
		}
		
		);

	if(changed) {
		do_crm_log(log_level, "%s changed, processing after list", action->uuid);
		slist_iter(
			other, action_wrapper_t, action->actions_after, lpc,
			do_crm_log(log_level, "%s changed, processing %s", action->uuid, other->action->uuid);
			update_action(other->action);
			);
	}
	
	return FALSE;
}


gboolean
shutdown_constraints(
	node_t *node, action_t *shutdown_op, pe_working_set_t *data_set)
{
	/* add the stop to the before lists so it counts as a pre-req
	 * for the shutdown
	 */
	slist_iter(
		rsc, resource_t, node->details->running_rsc, lpc,

		if(is_not_set(rsc->flags, pe_rsc_managed)) {
			continue;
		}
		
		custom_action_order(
			rsc, stop_key(rsc), NULL,
			NULL, crm_strdup(CRM_OP_SHUTDOWN), shutdown_op,
			pe_order_implies_left, data_set);

		);	

	return TRUE;	
}

gboolean
stonith_constraints(
	node_t *node, action_t *stonith_op, pe_working_set_t *data_set)
{
	CRM_CHECK(stonith_op != NULL, return FALSE);
	
	/*
	 * Make sure the stonith OP occurs before we start any shared resources
	 */
	if(stonith_op != NULL) {
		slist_iter(
			rsc, resource_t, data_set->resources, lpc,
			rsc->cmds->stonith_ordering(rsc, stonith_op, data_set);
			);
	}
	
	/* add the stonith OP as a stop pre-req and the mark the stop
	 * as a pseudo op - since its now redundant
	 */
	
	return TRUE;
}

static void dup_attr(gpointer key, gpointer value, gpointer user_data)
{
	g_hash_table_replace(user_data, crm_strdup(key), crm_strdup(value));
}

crm_data_t *
action2xml(action_t *action, gboolean as_input)
{
	gboolean needs_node_info = TRUE;
	crm_data_t * action_xml = NULL;
	crm_data_t * args_xml = NULL;
	char *action_id_s = NULL;
	
	if(action == NULL) {
		return NULL;
	}

	crm_debug_4("Dumping action %d as XML", action->id);
	if(safe_str_eq(action->task, CRM_OP_FENCE)) {
		action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT);
/* 		needs_node_info = FALSE; */
		
	} else if(safe_str_eq(action->task, CRM_OP_SHUTDOWN)) {
		action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT);

	} else if(safe_str_eq(action->task, CRM_OP_LRM_REFRESH)) {
		action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT);

/* 	} else if(safe_str_eq(action->task, CRMD_ACTION_PROBED)) { */
/* 		action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT); */

	} else if(action->pseudo) {
		action_xml = create_xml_node(NULL, XML_GRAPH_TAG_PSEUDO_EVENT);
		needs_node_info = FALSE;

	} else {
		action_xml = create_xml_node(NULL, XML_GRAPH_TAG_RSC_OP);
	}

	action_id_s = crm_itoa(action->id);
	crm_xml_add(action_xml, XML_ATTR_ID, action_id_s);
	crm_free(action_id_s);
	
	crm_xml_add(action_xml, XML_LRM_ATTR_TASK, action->task);
	if(action->rsc != NULL && action->rsc->clone_name != NULL) {
		char *clone_key = NULL;
		const char *interval_s = g_hash_table_lookup(action->meta, "interval");
		int interval = crm_parse_int(interval_s, "0");

		if(safe_str_eq(action->task, CRMD_ACTION_NOTIFY)) {			
			const char *n_type = g_hash_table_lookup(
				action->extra, crm_meta_name("notify_type"));
			const char *n_task = g_hash_table_lookup(
				action->extra, crm_meta_name("notify_operation"));
			CRM_CHECK(n_type != NULL, ;);
			CRM_CHECK(n_task != NULL, ;);
			clone_key = generate_notify_key(action->rsc->clone_name, n_type, n_task);
			
		} else {
			clone_key = generate_op_key(action->rsc->clone_name, action->task, interval);
		}
		
		crm_xml_add(action_xml, XML_LRM_ATTR_TASK_KEY, clone_key);
		crm_xml_add(action_xml, "internal_"XML_LRM_ATTR_TASK_KEY, action->uuid);
		crm_free(clone_key);
		
	} else {
		crm_xml_add(action_xml, XML_LRM_ATTR_TASK_KEY, action->uuid);
	}
	
	if(needs_node_info && action->node != NULL) {
		crm_xml_add(action_xml, XML_LRM_ATTR_TARGET,
			    action->node->details->uname);

		crm_xml_add(action_xml, XML_LRM_ATTR_TARGET_UUID,
			    action->node->details->id);		
	}

	if(action->failure_is_fatal == FALSE) {
		add_hash_param(action->meta,
			       XML_ATTR_TE_ALLOWFAIL, XML_BOOLEAN_TRUE);
	}
	
	if(as_input) {
		return action_xml;
	}

	if(action->notify_keys != NULL) {
		g_hash_table_foreach(
			action->notify_keys, dup_attr, action->meta);
	}
	if(action->rsc != NULL && action->pseudo == FALSE) {
		int lpc = 0;
		
		crm_data_t *rsc_xml = create_xml_node(
			action_xml, crm_element_name(action->rsc->xml));

		const char *attr_list[] = {
			XML_AGENT_ATTR_CLASS,
			XML_AGENT_ATTR_PROVIDER,
			XML_ATTR_TYPE
		};

		if(action->rsc->clone_name != NULL) {
			crm_debug("Using clone name %s for %s", action->rsc->clone_name, action->rsc->id);
			crm_xml_add(rsc_xml, XML_ATTR_ID, action->rsc->clone_name);
			crm_xml_add(rsc_xml, XML_ATTR_ID_LONG, action->rsc->id);

		} else {
			crm_xml_add(rsc_xml, XML_ATTR_ID, action->rsc->id);
			crm_xml_add(rsc_xml, XML_ATTR_ID_LONG, action->rsc->long_name);
		}
		
		for(lpc = 0; lpc < DIMOF(attr_list); lpc++) {
			crm_xml_add(rsc_xml, attr_list[lpc],
				    g_hash_table_lookup(action->rsc->meta, attr_list[lpc]));
		}
	}

	args_xml = create_xml_node(action_xml, XML_TAG_ATTRS);
	crm_xml_add(args_xml, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);

	g_hash_table_foreach(action->extra, hash2field, args_xml);
	if(action->rsc != NULL && safe_str_neq(action->task, CRMD_ACTION_STOP)) {
		g_hash_table_foreach(action->rsc->parameters, hash2field, args_xml);
	}

	g_hash_table_foreach(action->meta, hash2metafield, args_xml);
	if(action->rsc != NULL) {
		int lpc = 0;
		const char *key = NULL;
		const char *value = NULL;
		const char *meta_list[] = {
			XML_RSC_ATTR_UNIQUE,
			XML_RSC_ATTR_INCARNATION,
			XML_RSC_ATTR_INCARNATION_MAX,
			XML_RSC_ATTR_INCARNATION_NODEMAX,
			XML_RSC_ATTR_MASTER_MAX,
			XML_RSC_ATTR_MASTER_NODEMAX,
		};
		
		for(lpc = 0; lpc < DIMOF(meta_list); lpc++) {
			key = meta_list[lpc];
			value = g_hash_table_lookup(action->rsc->meta, key);
			if(value != NULL) {
				char *crm_name = crm_concat(CRM_META, key, '_');
				crm_xml_add(args_xml, crm_name, value);
				crm_free(crm_name);
			}
		}
	}
	
	crm_log_xml_debug_4(action_xml, "dumped action");
	
	return action_xml;
}

static gboolean
should_dump_action(action_t *action) 
{
	const char * interval = NULL;

	CRM_CHECK(action != NULL, return FALSE);

	interval = g_hash_table_lookup(action->meta, XML_LRM_ATTR_INTERVAL);
	if(action->optional) {
		crm_debug_5("action %d (%s) was optional",
			    action->id, action->uuid);
		return FALSE;

	} else if(action->runnable == FALSE) {
		crm_debug_5("action %d (%s) was not runnable",
			    action->id, action->uuid);
		return FALSE;

	} else if(action->dumped) {
		crm_debug_5("action %d (%s) was already dumped",
			    action->id, action->uuid);
		return FALSE;

	} else if(action->rsc != NULL
		  && is_not_set(action->rsc->flags, pe_rsc_managed)) {

		/* make sure probes go through */
		if(safe_str_neq(action->task, CRMD_ACTION_STATUS)) {
			pe_warn("action %d (%s) was for an unmanaged resource (%s)",
				action->id, action->uuid, action->rsc->id);
			return FALSE;
		}
		
		if(interval != NULL && safe_str_neq(interval, "0")) {
			pe_warn("action %d (%s) was for an unmanaged resource (%s)",
				action->id, action->uuid, action->rsc->id);
			return FALSE;
		}
	}
	
	if(action->pseudo
	   || safe_str_eq(action->task,  CRM_OP_FENCE)
	   || safe_str_eq(action->task,  CRM_OP_SHUTDOWN)) {
		/* skip the next checks */
		return TRUE;
	}	

	if(action->node == NULL) {
		pe_err("action %d (%s) was not allocated",
		       action->id, action->uuid);
		log_action(LOG_DEBUG, "Unallocated action", action, FALSE);
		return FALSE;
		
	} else if(action->node->details->online == FALSE) {
		pe_err("action %d was (%s) scheduled for offline node",
		       action->id, action->uuid);
		log_action(LOG_DEBUG, "Action for offline node", action, FALSE);
		return FALSE;
#if 0
		/* but this would also affect resources that can be safely
		 *  migrated before a fencing op
		 */
	} else if(action->node->details->unclean == FALSE) {
		pe_err("action %d was (%s) scheduled for unclean node",
		       action->id, action->uuid);
		log_action(LOG_DEBUG, "Action for unclean node", action, FALSE);
		return FALSE;
#endif
	}
	return TRUE;
}

/* lowest to highest */
static gint sort_action_id(gconstpointer a, gconstpointer b)
{
	const action_wrapper_t *action_wrapper2 = (const action_wrapper_t*)a;
	const action_wrapper_t *action_wrapper1 = (const action_wrapper_t*)b;

	if(a == NULL) { return 1; }
	if(b == NULL) { return -1; }
  
	if(action_wrapper1->action->id > action_wrapper2->action->id) {
		return -1;
	}
	
	if(action_wrapper1->action->id < action_wrapper2->action->id) {
		return 1;
	}
	return 0;
}

static gboolean
should_dump_input(int last_action, action_t *action, action_wrapper_t *wrapper)
{
    wrapper->state = pe_link_not_dumped;	
    if(last_action == wrapper->action->id) {
	crm_debug_2("Input (%d) %s duplicated",
		    wrapper->action->id,
		    wrapper->action->uuid);
	wrapper->state = pe_link_dup;
	return FALSE;
	
    } else if(wrapper->type == pe_order_none) {
	crm_debug_2("Input (%d) %s suppressed",
		    wrapper->action->id,
		    wrapper->action->uuid);
	return FALSE;
	
    } else if(wrapper->action->optional == TRUE) {
	crm_debug_2("Input (%d) %s optional",
		    wrapper->action->id,
		    wrapper->action->uuid);
	return FALSE;
	
    } else if(wrapper->action->runnable == FALSE
	      && wrapper->action->pseudo == FALSE
	      && wrapper->type == pe_order_optional) {
	crm_debug("Input (%d) %s optional (ordering)",
		  wrapper->action->id,
		  wrapper->action->uuid);
	return FALSE;

    } else if(action->pseudo
	      && (wrapper->type & pe_order_stonith_stop)) {
	crm_debug("Input (%d) %s suppressed",
		  wrapper->action->id,
		  wrapper->action->uuid);
	return FALSE;
    }
    crm_debug_3("Input (%d) %s n=%p p=%d r=%d f=0x%.6x dumped for %s",
		wrapper->action->id,
		wrapper->action->uuid,
		wrapper->action->node,
		wrapper->action->pseudo,
		wrapper->action->runnable,      
		wrapper->type,      
		action->uuid);
    return TRUE;
}
		   
void
graph_element_from_action(action_t *action, pe_working_set_t *data_set)
{
	int last_action = -1;
	int synapse_priority = 0;
	crm_data_t * syn = NULL;
	crm_data_t * set = NULL;
	crm_data_t * in  = NULL;
	crm_data_t * input = NULL;
	crm_data_t * xml_action = NULL;

	if(should_dump_action(action) == FALSE) {
		return;
	}
	
	action->dumped = TRUE;
	
	syn = create_xml_node(data_set->graph, "synapse");
	set = create_xml_node(syn, "action_set");
	in  = create_xml_node(syn, "inputs");

	crm_xml_add_int(syn, XML_ATTR_ID, data_set->num_synapse);
	data_set->num_synapse++;

	if(action->rsc != NULL) {
		synapse_priority = action->rsc->priority;
	}
	if(action->priority > synapse_priority) {
		synapse_priority = action->priority;
	}
	if(synapse_priority > 0) {
		crm_xml_add_int(syn, XML_CIB_ATTR_PRIORITY, synapse_priority);
	}
	
	xml_action = action2xml(action, FALSE);
	add_node_nocopy(set, crm_element_name(xml_action), xml_action);

	action->actions_before = g_list_sort(
		action->actions_before, sort_action_id);
	
	slist_iter(wrapper,action_wrapper_t,action->actions_before,lpc,

		   if(should_dump_input(last_action, action, wrapper) == FALSE) {
		       continue;
		   }

		   wrapper->state = pe_link_dumped;	
		   CRM_CHECK(last_action < wrapper->action->id, ;);
		   last_action = wrapper->action->id;
		   input = create_xml_node(in, "trigger");
		   
		   xml_action = action2xml(wrapper->action, TRUE);
		   add_node_nocopy(input, crm_element_name(xml_action), xml_action);
		);
}

