/*
 *  RapidMiner
 *
 *  Copyright (C) 2001-2013 by Rapid-I and the contributors
 *
 *  Complete list of developers available at our web site:
 *
 *       http://rapid-i.com
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see http://www.gnu.org/licenses/.
 */
package com.rapidminer.operator.preprocessing.filter;

import java.util.List;

import com.rapidminer.example.Attribute;
import com.rapidminer.example.Attributes;
import com.rapidminer.example.ExampleSet;
import com.rapidminer.operator.OperatorDescription;
import com.rapidminer.operator.OperatorException;
import com.rapidminer.operator.ProcessSetupError.Severity;
import com.rapidminer.operator.UserError;
import com.rapidminer.operator.annotation.ResourceConsumptionEstimator;
import com.rapidminer.operator.ports.metadata.AttributeMetaData;
import com.rapidminer.operator.ports.metadata.AttributeSetPrecondition;
import com.rapidminer.operator.ports.metadata.ExampleSetMetaData;
import com.rapidminer.operator.ports.metadata.MetaData;
import com.rapidminer.operator.ports.metadata.SimpleMetaDataError;
import com.rapidminer.operator.preprocessing.AbstractDataProcessing;
import com.rapidminer.parameter.ParameterType;
import com.rapidminer.parameter.ParameterTypeAttribute;
import com.rapidminer.parameter.ParameterTypeList;
import com.rapidminer.parameter.ParameterTypeStringCategory;
import com.rapidminer.parameter.UndefinedParameterError;
import com.rapidminer.tools.OperatorResourceConsumptionHandler;


/**
 * <p>
 * This operator can be used to change the attribute type of an attribute of the input example set.
 * If you want to change the attribute name you should use the {@link ChangeAttributeName} operator.
 * </p>
 *
 * <p>
 * The target type indicates if the attribute is a regular attribute (used by learning operators) or a
 * special attribute (e.g. a label or id attribute). The following target
 * attribute types are possible:
 * </p>
 * <ul>
 * <li>regular: only regular attributes are used as input variables for learning tasks</li>
 * <li>id: the id attribute for the example set</li>
 * <li>label: target attribute for learning</li>
 * <li>prediction: predicted attribute, i.e. the predictions of a learning scheme</li>
 * <li>cluster: indicates the membership to a cluster</li>
 * <li>weight: indicates the weight of the example</li>
 * <li>batch: indicates the membership to an example batch</li>
 * </ul>
 * <p>
 * Users can also define own attribute types by simply using the desired name.
 * </p>
 * 
 * @author Ingo Mierswa
 */
public class ChangeAttributeRole extends AbstractDataProcessing {

	/** The parameter name for &quot;The name of the attribute of which the type should be changed.&quot; */
	public static final String PARAMETER_NAME = "attribute_name";

	/** The parameter name for &quot;The target type of the attribute (only changed if parameter change_attribute_type is true).&quot; */
	public static final String PARAMETER_TARGET_ROLE = "target_role";

	public static final String PARAMETER_CHANGE_ATTRIBUTES = "set_additional_roles";
	
	private static final String REGULAR_NAME = "regular";

	private static final String[] TARGET_ROLES = new String[] { REGULAR_NAME, Attributes.ID_NAME, Attributes.LABEL_NAME, Attributes.PREDICTION_NAME, Attributes.CLUSTER_NAME, Attributes.WEIGHT_NAME, Attributes.BATCH_NAME };


	public ChangeAttributeRole(OperatorDescription description) {
		super(description);
		getExampleSetInputPort().addPrecondition(new AttributeSetPrecondition(getExampleSetInputPort(), AttributeSetPrecondition.getAttributesByParameter(this, PARAMETER_NAME)));
		getExampleSetInputPort().addPrecondition(new AttributeSetPrecondition(getExampleSetInputPort(), AttributeSetPrecondition.getAttributesByParameterListEntry(this, PARAMETER_CHANGE_ATTRIBUTES, 0)));
	}

	@Override
	protected MetaData modifyMetaData(ExampleSetMetaData metaData) {
		try {
			String targetRole = null;
			if (isParameterSet(PARAMETER_TARGET_ROLE))
				targetRole = getParameterAsString(PARAMETER_TARGET_ROLE);
			
			if (isParameterSet(PARAMETER_NAME)) {
				String name = getParameter(PARAMETER_NAME);
				setRoleMetaData(metaData, name, targetRole);			
			}
			
			// now proceed with list
			if (isParameterSet(PARAMETER_CHANGE_ATTRIBUTES)) {
				List<String[]> list = getParameterList(PARAMETER_CHANGE_ATTRIBUTES);
				for (String[] pairs: list) {
					setRoleMetaData(metaData, pairs[0], pairs[1]);
				}
			}
		} catch (UndefinedParameterError e) {			
		}
		return metaData;
	}

	private void setRoleMetaData(ExampleSetMetaData metaData, String name, String targetRole) {
		AttributeMetaData amd = metaData.getAttributeByName(name);
		if (amd != null) {
			if (targetRole != null) {
				if (REGULAR_NAME.equals(targetRole)) {
					amd.setRegular();
				} else {
					AttributeMetaData oldRole = metaData.getAttributeByRole(targetRole);
					if (oldRole != null && oldRole != amd) {
						getInputPort().addError(new SimpleMetaDataError(Severity.WARNING, getInputPort(), "already_contains_role", targetRole));
						metaData.removeAttribute(oldRole);
					}
					amd.setRole(targetRole);
				}
			}				
		}
	}

	@Override
	public ExampleSet apply(ExampleSet exampleSet) throws OperatorException {		 
		String name = getParameterAsString(PARAMETER_NAME);
		String newRole = getParameterAsString(PARAMETER_TARGET_ROLE);
		
		setRole(exampleSet, name, newRole);

		// now do the list
		if (isParameterSet(PARAMETER_CHANGE_ATTRIBUTES)) {
			List<String[]> list = getParameterList(PARAMETER_CHANGE_ATTRIBUTES);
			for (String[] pairs: list) {
				setRole(exampleSet, pairs[0], pairs[1]);
			}
		}
		
		return exampleSet;
	}

	private void setRole(ExampleSet exampleSet, String name, String newRole) throws UserError {
		Attribute attribute = exampleSet.getAttributes().get(name);
		
		if (attribute == null) {

			if (!name.isEmpty()) {
				throw new UserError(this, 160, name);
			} else {
				throw new UserError(this, 111, name);
			}
		}

		exampleSet.getAttributes().remove(attribute);
		if ((newRole == null) || (newRole.trim().length() == 0))
			throw new UserError(this, 205, PARAMETER_TARGET_ROLE);
		if (newRole.equals(REGULAR_NAME)) {
			exampleSet.getAttributes().addRegular(attribute);
		} else {
			exampleSet.getAttributes().setSpecialAttribute(attribute, newRole);
		}
	}

	@Override
	public List<ParameterType> getParameterTypes() {
		List<ParameterType> types = super.getParameterTypes();
		types.add(new ParameterTypeAttribute(PARAMETER_NAME, "The name of the attribute whose role should be changed.", getExampleSetInputPort(), false, false));
		ParameterType type = new ParameterTypeStringCategory(PARAMETER_TARGET_ROLE, "The target role of the attribute (only changed if parameter change_attribute_type is true).", TARGET_ROLES, TARGET_ROLES[0]);
		type.setExpert(false);
		types.add(type);
		
		types.add(new ParameterTypeList(PARAMETER_CHANGE_ATTRIBUTES, "This parameter defines additional attribute role combinations.", 
				new ParameterTypeAttribute(PARAMETER_NAME, "The name of the attribute whose role should be changed.", getExampleSetInputPort(), false, false), 
				new ParameterTypeStringCategory(PARAMETER_TARGET_ROLE, "The target role of the attribute (only changed if parameter change_attribute_type is true).", TARGET_ROLES, TARGET_ROLES[0]), 
				false));
		return types;
	}
	
	@Override
	public boolean writesIntoExistingData() {
		return false;
	}
	
	@Override
	public ResourceConsumptionEstimator getResourceConsumptionEstimator() {
		return OperatorResourceConsumptionHandler.getResourceConsumptionEstimator(getInputPort(), ChangeAttributeRole.class, null);
	}
}
