Source: core/kekule.elements.js

/**
 * @fileoverview
 * This file contains basic classes to represent an element or isotope.
 * @author Partridge Jiang
 */

/*
 * requires /lan/classes.js
 * requires /core/kekule.common.js
 * requires /data/kekule.dataUtils.js
 * requires /localization/
 */

/**
 * Enumeration of series of element.
 * @enum
 */
Kekule.ElementSeries = {
	NONMETAL: 'Nonmetals',
	METAL: 'Metals',
	ALKALI_METAL: 'Alkali Metals',
	ALKALI_EARTH_METAL: 'Alkali Earth Metals',
	TRANSITION_METAL: 'Transition Metals',
	METALLOID: 'Metalloids',
	HALOGEN: 'Halogens',
	NOBLE_GAS: 'Noble Gases',
	LANTHANIDE: 'Lanthanides',
	ACTINIDE: 'Actinides'
};

/**
 * Represent an element in periodical table.
 * @class
 * @augments Kekule.ChemObject
 * @param {Variant} symbolOrAtomicNumber Symbol(String) or atomic number(Int) of element.
 *
 * @property {Int} atomicNumber The atomic number of element. Read only.
 * @property {String} symbol The symbol of element. Read only.
 * @property {String} name Name of element. Read only.
 * @property {Int} group Group of element. Read only.
 * @property {Int} period Period of element. Read only.
 * @property {String} series Series of element, e.g. nonmetal.
 * @property {Float} naturalMass Natural mass of element.
 */
Kekule.Element = Class.create(Kekule.ChemObject,
/** @lends Kekule.Element# */
{
	/** @private */
	CLASS_NAME: 'Kekule.Element',
	/**
	 * @constructs
	 * @param {Variant} symbolOrAtomicNumber Symbol (String) or atomic number (Int) of element.
	 */
	initialize: function($super, symbolOrAtomicNumber)
	{
		$super();
		if (symbolOrAtomicNumber != Kekule.Element.UNSET_ELEMENT)
		{
			var elemInfo = this.getElementInfo(symbolOrAtomicNumber);
			if (elemInfo)
			{
				this.setPropStoreFieldValue('symbol', elemInfo.symbol);
				this.setPropStoreFieldValue('atomicNumber', elemInfo.atomicNumber);
				this.setPropStoreFieldValue('group', elemInfo.group);
				this.setPropStoreFieldValue('period', elemInfo.period);
				this.setPropStoreFieldValue('series', elemInfo.chemicalSerie);
				this.setPropStoreFieldValue('naturalMass', elemInfo.naturalMass);
				if (elemInfo.name)
					this.setPropStoreFieldValue('name', elemInfo.name);
			}
			else
			{
				Kekule.chemError(
					Kekule.hasLocalRes()?
						/*Kekule.ErrorMsg.INVALID_CHEMELEMENT*/Kekule.$L('ErrorMsg.INVALID_CHEMELEMENT') + ': ' + symbolOrAtomicNumber :
						'Invalid chemical element: ' + symbolOrAtomicNumber
				);
			}
		}
		else
		{
			this.setPropStoreFieldValue('symbol', Kekule.Element.UNSET_ELEMENT);
			this.setPropStoreFieldValue('atomicNumber', Kekule.Element.UNSET_ELEMENT);
			/*
			this.setPropStoreFieldValue('group', Kekule.Element.UNSET_ELEMENT);
			this.setPropStoreFieldValue('period', Kekule.Element.UNSET_ELEMENT);
			*/
		}
	},
	/** @private */
	initProperties: function()
	{
		this.defineProp('name', {'dataType': DataType.STRING, 'serializable': false, 'setter': null});
		// Atomic number must be setted in constructor
		this.defineProp('atomicNumber', {'dataType': DataType.INT, 'serializable': false, 'setter': null});
		// Symbol of element, read only. fsymbol must be setted in constructor
		this.defineProp('symbol', {'dataType': DataType.STRING, 'serializable': false, 'setter': null});

		this.defineProp('group', {'dataType': DataType.INT, 'serializable': false, 'setter': null});
		this.defineProp('period', {'dataType': DataType.INT, 'serializable': false, 'setter': null});
		this.defineProp('series', {'dataType': DataType.STRING, 'serializable': false, 'setter': null});
		this.defineProp('naturalMass', {'dataType': DataType.FLOAT, 'serializable': false, 'setter': null});
	},
	/**
	 * Get element info of symbolOrAtomicNumber. Support pseudo element.
	 * @param {Object} symbolOrAtomicNumber
	 */
	getElementInfo: function(symbolOrAtomicNumber)
	{
		if (symbolOrAtomicNumber == Kekule.Element.UNSET_ELEMENT)
			return {'symbol': Kekule.Element.UNSET_ELEMENT, 'atomicNumber': Kekule.Element.UNSET_ELEMENT};
		if (Kekule.Element.isDummyElement(symbolOrAtomicNumber))
			return {'symbol': Kekule.Element.DUMMY_ELEMENT, 'atomicNumber': Kekule.Element.DUMMY_ELEMENT_ATOMICNUM};
		else if (Kekule.Element.isRGroup(symbolOrAtomicNumber))
			return {'symbol': Kekule.Element.RGROUP_ELEMENT, 'atomicNumber': Kekule.Element.RGROUP_ELEMENT_ATMOICNUM};
		else
			return Kekule.ChemicalElementsDataUtil.getElementInfo(symbolOrAtomicNumber);
	},
	/**
	 * Roughly get the theoretic valence of an element in a certain group.
	 * The transitionmetal is not considered and will return null.
	 * @returns {Int} Theoretic valence or null on transitionmetal.
	 */
	getTheoreticValence: function()
	{
		if (this.getGroup())
			return Kekule.Element.getTheoreticValenceOfGroup(this.getGroup());
		else
			return null;
	},
	/**
	 * Check if current element is a "dummy" one.
	 * @returns {Bool}
	 */
	isDummyElement: function()
	{
		return this.getAtomicNumber() == Kekule.Element.DUMMY_ELEMENT_ATOMICNUM;
	},
	/**
	 * Check if this element is used to represent an RGroup
	 * @returns {Bool}
	 */
	isRGroupElement: function()
	{
		return this.getAtomicNumber() == Kekule.Element.RGROUP_ELEMENT_ATMOICNUM;
	},
	/**
	 * Check if this is a normal element (not pseudo one, not unset one).
	 * @returns {Bool}
	 */
	isNormalElement: function()
	{
		return Kekule.Element.isNormalElement(this.getAtomicNumber());
	},
	/**
	 * Check if symbolOrAtomicNumber is same with this object.
	 * @param {Variant} symbolOrAtomicNumber Symbol (String) or atomic number (Int) of element.
	 * @returns {Bool}
	 */
	isSameElement: function(symbolOrAtomicNumber)
	{
		if (symbolOrAtomicNumber == Kekule.Element.UNSET_ELEMENT)
			return this.getAtomicNumber() == Kekule.Element.UNSET_ELEMENT;
		else if (typeof(symbolOrAtomicNumber) == 'number')
			return this.getAtomicNumber() == symbolOrAtomicNumber;
		else
			return this.getSymbol() == symbolOrAtomicNumber;
	},
	/**
	 * Returns whether this element is a hetero one (e.g., N, S, Cl...).
	 * @returns {Bool}
	 */
	isHetero: function()
	{
		return ((this.getSeries() === Kekule.ElementSeries.NONMETAL) || (this.getSeries() === Kekule.ElementSeries.HALOGEN))
			&& (this.getAtomicNumber() !== 6) && (this.getAtomicNumber() !== 1); // not C/H
	},
	/**
	 * Returns a string label to represent this element.
	 * @returns {String}
	 */
	getLabel: function()
	{
		return this.getSymbol();
	}
});
// Copy all methods of Kekule.ChemicalElementsDataUtil to Element as shortcut
Object.extend(Kekule.Element, Kekule.ChemicalElementsDataUtil);


/**
 * Indicate the atomic number is unset and this is a unknown element.
 * @constant
 */
Kekule.Element.UNSET_ELEMENT = undefined;
/**
 * A pseudo element to indicate a dummy atom.
 * @constant
 */
Kekule.Element.DUMMY_ELEMENT = 'DU';
Kekule.Element.DUMMY_ELEMENT_ATOMICNUM = -1;
/**
 * A pseudo element to indicate a R-Group
 * @constant
 */
Kekule.Element.RGROUP_ELEMENT = 'R';
Kekule.Element.RGROUP_ELEMENT_ATOMICNUM = -2;
/**
 * Label for deuterium.
 * @constant
 */
Kekule.Element.DEUTERIUM = 'D';
/**
 * Check if symbolOrAtomicNumber is a normal element (not pseudo one, not unset one).
 * @function
 * @param {Variant} symbolOrAtomicNumber
 * @returns {Bool}
 */
Kekule.Element.isNormalElement = function(symbolOrAtomicNumber)
{
	return (symbolOrAtomicNumber != Kekule.Element.UNSET_ELEMENT)
		&& (!Kekule.Element.isPseudoElement(symbolOrAtomicNumber))
};
/**
 * Check if symbolOrAtomicNumber is a pseudo element of dummy or RGroup.
 * @function
 * @param {Variant} symbolOrAtomicNumber
 * @returns {Bool}
 * @funtion
 */
Kekule.Element.isPseudoElement = function(symbolOrAtomicNumber)
{
	if (typeof(symbolOrAtomicNumber) == 'string')
		return (symbolOrAtomicNumber == Kekule.Element.DUMMY_ELEMENT) || (symbolOrAtomicNumber == Kekule.Element.RGROUP_ELEMENT);
	else
		return (symbolOrAtomicNumber == Kekule.Element.DUMMY_ELEMENT_ATOMICNUM) || (symbolOrAtomicNumber == Kekule.Element.RGROUP_ELEMENT_ATMOICNUM);
};
/**
 * Check if symbolOrAtomicNumber is a dummy element.
 * @function
 * @param {Variant} symbolOrAtomicNumber
 * @returns {Bool}
 */
Kekule.Element.isDummyElement = function(symbolOrAtomicNumber)
{
	return (symbolOrAtomicNumber === Kekule.Element.DUMMY_ELEMENT) || (symbolOrAtomicNumber === Kekule.Element.DUMMY_ELEMENT_ATOMICNUM);
};
/**
 * Check if symbolOrAtomicNumber is a RGroup
 * @function
 * @param {Variant} symbolOrAtomicNumber
 * @returns {Bool}
 */
Kekule.Element.isRGroup = function(symbolOrAtomicNumber)
{
	return (symbolOrAtomicNumber === Kekule.Element.RGROUP_ELEMENT) || (symbolOrAtomicNumber === Kekule.Element.RGROUP_ELEMENT_ATOMICNUM);
};
/**
 * Roughly get the theoretic valence of an element in a certain group.
 * The transitionmetal is not considered and will return null.
 * @function
 * @param {Int} group
 * @returns {Int} Theoretic valence or null on transitionmetal.
 */
Kekule.Element.getTheoreticValenceOfGroup = function(group)
{
	if (group <= 2)
		return group;
	else if (group <= 12)
		return null;
	else if (group <= 17)
		return group - 10;
	else  // group 18
		return 0;
};
/**
 * Roughly get the theoretic valence of an element. Only consider the group of element.
 * The transitionmetal is not considered and will return null.
 * @function
 * @param {Object} symbolOrAtomicNumber
 * @returns {Int} Theoretic valence or null on transitionmetal.
 */
Kekule.Element.getTheoreticValence = function(symbolOrAtomicNumber)
{
	var atomInfo = Kekule.ChemicalElementsDataUtil.getElementInfo(symbolOrAtomicNumber);
	return atomInfo.group? Kekule.Element.getTheoreticValenceOfGroup(atomInfo.group): null;
};

// Static methods of Kekule.Element
Object.extend(Kekule.Element,
/** @lends Kekule.Element */
{
	/**
	 * Check if atomic number is legal in database
	 * @param {Int} atomicNumber
	 * @returns {Bool}
	 */
	isAtomicNumberAvailable: function(atomicNumber)
	{
		return  Kekule.ChemicalElementsDataUtil.isAtomicNumberAvailable(atomicNumber);
	},
	/**
	 * Check if element symbol is legal in database.
	 * @param {Int} atmoicNumber
	 * @return {Bool}
	 */
	isElementSymbolAvailable: function(symbol)
	{
		return Kekule.ChemicalElementsDataUtil.isElementSymbolAvailable(symbol);
	}
});

/**
 * Represent an isotope in periodical table.
 * @class
 * @augments Kekule.Element
 * @param {Variant} symbolOrAtomicNumber Symbol(String) or atomic number(Int) of isotope.
 * @param {Int} massNumber Mass number of isotope.
 *
 * @property {Int} massNumber The mass number of isotope. Read only. Setting to null means a genenral element.
 * @property {Float} exactMass Read only.
 * @property {Float} naturalAbundance Read only.
 * @property {String} isotopeAlias Alias of isotope (such as D for H2). Read only.
 */
Kekule.Isotope = Class.create(Kekule.Element,
/** @lends Kekule.Isotope# */
{
	/** @private */
	CLASS_NAME: 'Kekule.Isotope',
	/**
	 * @constructs
	 * @param {Variant} symbolOrAtomicNumber Symbol (String) or atomic number (Int) of element.
	 * @param {Int} massNumber Isotope mass number.
	 */
	initialize: function($super, symbolOrAtomicNumber, massNumber)
	{
		var atomicNum = symbolOrAtomicNumber;
		var massNum = massNumber;
		// check if symbol is isotope alias
		var isoInfo = Kekule.IsotopesDataUtil.getIsotopeInfo(symbolOrAtomicNumber, massNumber);
		if (isoInfo)
		{
			//console.log(isoInfo);
			if (isoInfo.atomicNumber)
				atomicNum = isoInfo.atomicNumber;
			massNum = isoInfo.massNumber;
		}

		$super(atomicNum);
		this.setPropStoreFieldValue('massNumber', massNum);
		if (isoInfo && isoInfo.isotopeAlias)
			this.setPropStoreFieldValue('isotopeAlias', isoInfo.isotopeAlias);
		/*
		if ((symbolOrAtomicNumber != Kekule.Element.UNSET_ELEMENT)
			&& (!Kekule.Element.isPseudoElement(symbolOrAtomicNumber))
			&& (massNumber != Kekule.Isotope.UNSET_MASSNUMBER))
		*/
		if (Kekule.Element.isNormalElement(atomicNum)
			&& (massNum !== Kekule.Isotope.UNSET_MASSNUMBER))
		{
			// get rest of properties' value from isotope data
			var isotopeInfo = Kekule.IsotopesDataUtil.getIsotopeInfo(this.getAtomicNumber(), massNum);
			if (isotopeInfo)
			{
				this.setPropStoreFieldValue('exactMass', isotopeInfo.exactMass);
				this.setPropStoreFieldValue('naturalAbundance', isotopeInfo.naturalAbundance);
			}
			else
			{
				Kekule.chemError(
					(Kekule.hasLocalRes() ? /*Kekule.ErrorMsg.INVALID_ISOTOPE*/Kekule.$L('ErrorMsg.INVALID_ISOTOPE') : 'Invalid isotope')
					+ ': ' + this.getSymbol() + '/' + massNumber);
			}
		}
	},
	/** @private */
	initProperties: function()
	{
		this.defineProp('massNumber', {'dataType': DataType.INTEGER, 'serializable': false, 'setter': null});
		this.defineProp('exactMass', {'dataType': DataType.FLOAT, 'serializable': false, 'setter': null});
		this.defineProp('naturalAbundance', {'dataType': DataType.FLOAT, 'serializable': false, 'setter': null});
		this.defineProp('isotopeAlias', {'dataType': DataType.STRING, 'serializable': false, 'setter': null});
	},
	/* @ignore */
	/*
	doGetSymbol: function($super)
	{
		if (this.getAtomicNumber() === 1 && this.getMassNumber() === 2)  // DEUTERIUM
			return Kekule.Element.DEUTERIUM;
		else
			return $super();
	},
	*/
	/**
	 * Check if obj is the same isotope with this one.
	 * @param {Object} obj
	 * @returns {Bool}
	 */
	isSame: function(obj)
	{
		if (obj instanceof Kekule.Isotope)
		{
			return (obj === this) ||
			  ((obj.getAtomicNumber() == this.getAtomicNumber())
			    && (obj.getMassNumber() == this.getMassNumber()));
		}
		return false;
	},
	/**
	 * Get an unique id for current isotope.
	 * @returns {String}
	 */
	getIsotopeId: function()
	{
		return Kekule.IsotopesDataUtil.getIsotopeId(this.getAtomicNumber(), this.getMassNumber());
	},
	/** @ignore */
	getLabel: function()
	{
		return '' + (this.getMassNumber() || '') + this.getSymbol();
	}
});

// Copy all methods of Kekule.IsotopesDataUtil to Isotope as shortcut
Object.extend(Kekule.Isotope, Kekule.IsotopesDataUtil);


/**
 * Indicate the mass number is unset and this is a general element.
 * @constant
 */
Kekule.Isotope.UNSET_MASSNUMBER = undefined;
/**
 * Get an unique id for isotope.
 * @param {Variant} symbolOrAtomicNumber
 * @param {Int} massNumber
 * @returns {String}
 * @function
 */
Kekule.Isotope.getIsotopeId = function(symbolOrAtomicNumber, massNumber)
{
	return Kekule.IsotopesDataUtil.getIsotopeId(symbolOrAtomicNumber, massNumber);
};

Object.extend(Kekule.Isotope,
/** @lends Kekule.Isotope */
{
	/**
	 * Check if mass number is legal for an element
	 * @param {Varaint} symbolOrAtomicNumber Element symbol(String) or atomic number(Integer).
	 * @param {Int} massNumber
	 * @return {Bool}
	 */
	isMassNumberAvailable: function(symbolOrAtomicNumber, massNumber)
	{
		return Kekule.IsotopesDataUtil.isMassNumberAvailable(symbolOrAtomicNumber, massNumber);
	}
});

/**
 * Factory to create and get suitable isotope.
 * @class
 */
Kekule.IsotopeFactory = {
	/** @private */
	GENERIC_ELEMENT_ID: '__GENERIC__',
	/** @private */
	_isotopes: {},
	/** @private */
	getIsotopeId: function(symbolOrAtomicNumber, massNumber)
	{
		if (!symbolOrAtomicNumber)  // an generic element
			return Kekule.IsotopeFactory.GENERIC_ELEMENT_ID;
		else
			return Kekule.IsotopesDataUtil.getIsotopeId(symbolOrAtomicNumber, massNumber);
	},
	/**
	 * Returns a suitable isotope object.
	 * @param {Varaint} symbolOrAtomicNumber Element symbol(String) or atomic number(Integer).
	 * @param {Int} massNumber
	 * @returns {Kekule.Isotope}
	 */
	getIsotope: function(symbolOrAtomicNumber, massNumber)
	{
		var id = Kekule.IsotopeFactory.getIsotopeId(symbolOrAtomicNumber, massNumber);
		if (!Kekule.IsotopeFactory._isotopes[id])
		{
			var isotope = new Kekule.Isotope(symbolOrAtomicNumber, massNumber);
			Kekule.IsotopeFactory._isotopes[id] = isotope;
		}
		return Kekule.IsotopeFactory._isotopes[id];
	},
	/**
	 * Returns a suitable isotope by id.
	 * @param {String} id Isotope ID, such as H2, C13.
	 * @returns {Kekule.Isotope}
	 */
	getIsotopeById: function(id)
	{
		if (Kekule.IsotopeFactory._isotopes[id])
		{
			return Kekule.IsotopeFactory._isotopes[id];
		}
		else
		{
			var detail = Kekule.IsotopesDataUtil.getIsotopeIdDetail(id);
			if (detail)
				return Kekule.IsotopeFactory.getIsotope(detail.symbol, detail.massNumber);
			else
				return null;
		}
	}
};

/**
 * @class
 */
Kekule.AtomType = {
		/**
		 * Indicate the atom type is unset.
		 * @constant
		 */
		UNSET_ATOMTYPE: undefined
};