/*
 * BaseClassData.java
 *
 * Generated by {productTitle}  {versionName} - {utilityTitle}
 *
 * {copywrite}
 */
package {packageName}.common;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.cim.CIMDataType;
import javax.cim.CIMInstance;
import javax.cim.CIMObjectPath;
import javax.cim.CIMProperty;

import javax.wbem.WBEMException;

import com.ws.wbem.query.fql.FQLParser;

/**
 * This class is used as a base class for property management. Super classes are
 * responsible for setting property values and type, this class will handle
 * storage and retrieval
 * 
 */
public abstract class BaseClassData {

	// contains a map which holds the properties
	private final Map<String, CIMProperty<?>> properties;
	// List of the key properties
	private final List<String> keyNames;
	// name of class
	private final String classname;

	/**
	 * Creates an instance of this class
	 * 
	 * @param classname
	 *            The names of the class this data is used for
	 * @param keyNames
	 *            The names of the key properties
	 * @throws NullPointerException
	 *             if keyNames is null or empty
	 */
	public BaseClassData(String classname, String[] keyNames) {
		super();
		this.classname = classname;
		properties = new HashMap<String, CIMProperty<?>>(30);
		if (null != keyNames && keyNames.length > 0) {
			this.keyNames = Arrays.asList(keyNames);
			Collections.sort(this.keyNames);
		} else {
			throw new NullPointerException("Key names must be provided");
		}
	}

	private boolean isPropertyKey(String name) {
		final boolean ret;
		// For case insensitive string comparison
		Comparator<String> propertyNameChecker = new Comparator<String>() {
			@Override
			public int compare(String o1, String o2) {
				return o1.compareToIgnoreCase(o2);
			}
		};
		
		if (Collections.binarySearch(keyNames, name, propertyNameChecker) >= 0) {
			ret = true;
		} else {
			ret = false;
		}
		return ret;
	}

	/**
	 * Stores a value for a specific property
	 * 
	 * @param propertyName
	 *            The name of the property whose values is being set
	 * @param type
	 *            The CIM Data type of the passed in object
	 * @param value
	 *            The value for that property
	 */
	protected void setPropertyValue(String propertyName, CIMDataType type,
			Object value) throws WBEMException {
		// This should only be called by the super class which will have the
		// correct data type, so we can ignore these warnings
		@SuppressWarnings({ "unchecked", "rawtypes" })
		final CIMProperty<?> prop = new CIMProperty(propertyName, type, value,
				isPropertyKey(propertyName));
		properties.put(propertyName, prop);
	}

	/**
	 * Generates a CIMInstance based on the properties currently set in this
	 * class
	 *
	 * @param op
	 *            The CIMObjectPath that represents this class
	 * @param propertyList
	 *            a list of the properties to return
	 * @param FQLParser
	 *            an FQLParser object use to validate the instance against
	 * @return If the specified FQLParser object is not null and the generate
	 *         CIMInstance doesn't validate against the FQLParser object, return
	 *         null. Otherwise, returns the generate CIMInstance
	 * @throws IllegalArgumentException
	 *             if the passed in CIMObjectpath is null or
	 *             {@link CIMObjectpath#getOjectName()} returns null or a key
	 *             value has not been set
	 */
	public CIMInstance generateCIMInstance(CIMObjectPath op,
			String[] propertyList, final FQLParser fqlParser)
					throws IllegalArgumentException {
		CIMInstance ret = null;
		// validate specified CIMObjectPath is not null
		if (null == op) {
			throw new IllegalArgumentException(
					"NULL CIMObjectPath passed to generateInstance() method");
		}

		final CIMObjectPath newOP = new CIMObjectPath(op.getScheme(),
				op.getHost(), op.getPort(), op.getNamespace(), classname,
				getKeys());

		ret = new CIMInstance(newOP, getProperties());
		// if there is a valid FQLParser object and the instance cannot evaluate
		// to it, set the return instance to null
		if (null != fqlParser && !fqlParser.evaluate(ret)) {
			ret = null;
		}
		return ret;
	}

	private CIMProperty<?>[] getProperties() {
		List<CIMProperty<?>> props = new ArrayList<CIMProperty<?>>(
				properties.size());

        // get all the keys and values, avoids look-up for property name
        Set<Entry<String, CIMProperty<?>>> entries = properties.entrySet();
        for (Entry<String, CIMProperty<?>> entry : entries) {
            // if name is null then property has not been set, skip it
            if (null != entry.getKey()) {
                props.add(entry.getValue());
            }
        }

		return props.toArray(new CIMProperty<?>[props.size()]);
	}

	private CIMProperty<?>[] getKeys() throws IllegalArgumentException {
		List<CIMProperty<?>> props = new ArrayList<CIMProperty<?>>(
				properties.size());

		for (String name : keyNames) {
			CIMProperty<?> tv = properties.get(name);
			// if tv is null then property has no value, throw error
			if (null != tv) {
				props.add(tv);
			} else {
				throw new IllegalArgumentException("Key(" + name
						+ ") property value has not been set");
			}
		}

		return props.toArray(new CIMProperty<?>[props.size()]);
	}
}