/*
    chem.Verbindung
	
	Autor: J. Strübig.
	letzte Änderung: 12:52 Freitag, 22. Mai 2009
	Version 1.0
	
-------------------------------------------------
	Änderungen
-------------------------------------------------
	10:49 Montag, 20. Juni 2011
	
	* PSE Korrigiert
	
-------------------------------------------------
	Funktionen:
-------------------------------------------------
	
	parse(string)
	-------------------------------------------------
	Wandelt den String in eine chemische Formel um.
	Alle gültige Elemente werden in eine Elementliste, 
	mit deren Anteil in der Formel, eingefügt.
	
	masse()
	-------------------------------------------------
	Summe der Molmasse aller Elemente
	
	anteil(formel[, faktor])
	-------------------------------------------------
	Massenanteil des Elements in der Elementliste.
	
	search(name)
	-------------------------------------------------
	sucht ob das Element in der Elementliste vorkommt.
	Rückgabe: Element()
	
	add(formel, faktor)
	-------------------------------------------------
	fügt ein Element in die Auflistung aller Elemente 
	ein. Es wird aber nicht in die Formel eingebaut.
	
	getFormel([html])
	-------------------------------------------------
	Rückgabe der aktuellen Formel, entweder als 
	plain Text oder als HTML Code.

	reset()
	-------------------------------------------------
	löschen der Formel
	
	element()
	-------------------------------------------------
	Rückgabe aller Elemente als Array
	
*/
var chem = {};


chem.Verbindung = function (string){
	var formel,  error;
	var lowerCase = /[a-z]/;
	var upperCase = /[A-Z]/;
	var isChar = /[a-z]/i;
	var isNumber = /[0-9]/;

	var elemente = [];
	
	// Zugriffsfunktionen
	this.reset = function() { error = []; elemente = []; formel = '';};
	this.elemente = function() { return elemente;}
	this.error = function(){ return error[0] ? error : null;};
	this.parse = function (string) {
		if(typeof string != 'string' || !string.length) return null;
		this.reset();
		var klammer = 1.0; // Der Faktor einer Klammer
		var komplex = 1.0;
		/*
			Schleife zum analysieren der Formel
			pos ist die Position im String
		*/
		for(var pos = 0; pos < string.length; pos++) {
			var c = string.charAt(pos);
			if(c == "(") {
				// Klammmer auf. 
				// Schliessende Klammer suchen, um Zahl nach der Klammer zu ermitteln.
				// diese ist der Faktor für die Elemente in der Klammer
				i = string.indexOf(')', pos);
				klammer = parseFloat(string.substring(i + 1)) || 1.0;
				formel += c; // Die Klammer hinzufügen
			} else if ( c == ")" ) {
				// Klammer schliessen - Faktor zurücksetzen
				formel += c;
				if(klammer > 1) formel += klammer;
				klammer = 1.0;
			} else if(c == "x" || c == "*") {
				// Kristallwasser
				klammer = parseFloat(string.substring(pos + 1));
				formel += 'x';
				if(!klammer) klammer = 1.0;
				if(klammer > 1) formel += klammer;
			} else if ( c == '['){
				i = string.indexOf(']', pos);
				komplex = parseFloat(string.substring(i + 1)) || 1.0;
				formel += c; // Die Klammer hinzufügen
	
			} else if ( c == ']'){
				// Klammer schliessen - Faktor zurücksetzen
				formel += c;
				if(komplex > 1) formel += komplex;
				komplex = 1.0;
	
			} else if(isChar.test(string.charAt(pos))){
				// prüfen ob ein Element kommt
				var element = string.charAt(pos).toUpperCase();
				var next = string.charAt(pos + 1);
				if(lowerCase.test(next) && chem.pse.get(element + next)) {
					// Element mit zwei Buchstaben
					element += next;
					pos++;
				} else if(isChar.test(next) && !upperCase.test(next)){
					error.push(element + next + ' ist kein gültiges Element.');
				}
				
				// Element prüfen
				if(chem.pse.get(element)) {
					var faktor = parseFloat(string.substring(pos + 1)) || 1;
					this.add(element, faktor * klammer * komplex);
					formel += element;
					if(faktor != 1) formel += faktor;
				} else {
					// Kein Element
					error.push(element + ' ist kein Element.');
				}
			}
		}
		return formel;
	};
	
	this.add = function (f, faktor) {
		var el = this.search(f);
		if(!faktor) faktor = 1;
		if(!el) {
			el = chem.pse.get(f);
			if(!el) {
				error.push(f + ' ist kein Element.');
				return null;
			}
			elemente.push(el);
			el.faktor = 0;
		}
		el.faktor += faktor;
		return el;
	};
	this.getFormel = function(html){
		if(!formel) return '';
		if(!html) return formel;
		// HTML Format
		var f = "";
		var sub_auf  = "<sub>";
		var sub_zu = "</sub>";
		var tag = false;
		var kristallwasser = false;
		for(var i = 0; i < formel.length; i++) {
			var c = formel.charAt(i);
			if(isNumber.test(c)) {
				// Eine Zahl
				if(!tag &&!kristallwasser) {
					// Es ist die 1. Zahl
					f += sub_auf;
					tag = true;
				}
				f += c;
			} else if(c == 'x') {
				// Kristallwasser
				if(tag) { f += sub_zu; tag = false;}
				f += '<strong> ' + c + ' </strong>';
				kristallwasser = true;
			} else if(/[a-z()\[\]]/i.test(c)){
				if(tag) {
				// es ist keine Zahl, aber bis hierhin
				// war es eine -> Zahl beenden
				f += sub_zu + c;
				tag = false;
				} else {
				f += c;
				}
				kristallwasser = false;
			}
		}
		if(tag) f += sub_zu;
		return f;
	}

	// ini
	this.reset();
	this.parse(string);

}; // Objekt: chem.Verbindung

chem.Verbindung.prototype = {
anteil: function(s) { // Anteil des Elementes in der Formel
	if(!s) return 0;
	var el = this.search(s);
	if(!el) return 0;

	return (el.masse * el.faktor) / this.masse();
},
masse: function() {
	var mm = 0.0;
	this.elemente().forEach( function(el) {mm += el.masse * el.faktor;});
	return mm;
},
search: function (s){
	var f = null;
	this.elemente().forEach( 
		function(el) {
			if(el.formel == s) f = el;
		}
	);
	return f;
}

}


/*
chem.pse - Das Periodensystem der Elemente - als JS Objekt.

Autor: J. Strübig.
letzte Änderung: 13:48 19.05.2009
Version 1.0

1.1 - 10:48 Montag, 20. Juni 2011
* PSE Fehler verbessert und Werte auf aktuellen Stand gebracht

Beschreibung:

chem.pse ist ein statisches Objekt in dem die Daten der Elemente gespeichert sind.

Funktionen:

get()
---------------------------------------------
Syntax: Element(Object) = chem.pse.get(Formel) 

gibt das Objekt mit der Formel zurück.

Das Element hat folgende Attribute:

name 	= der deutsche Name des Elements
masse 	= die Atommasse des
oz 		= Ordnungszahl

info()

getAttribute()
---------------------------------------------

Syntax: Wert(String) = chem.pse.getAttribute(Formel, Attribut)

gibt den Wert des Attributes des Elementes zurück.
Mögliche Attribute 'name', 'masse', 'oz', 'formel'

getByName()
---------------------------------------------

Element(Object) = chem.pse.getByName(Formel[, Case]) 

Gibt das Elementobjekt des Elementes mit der Formel zurück. Der optionale Parameter gibt an ob zwischen Groß- und Kleinschreibung unterschieden wird (default: nein/false)

*/

/*
Quelle: http://www.chem.qmul.ac.uk/iupac/AtWt/index.html#01
*/
chem.pse = new function() {

var elements= [
['H', 'Wasserstoff', 1, 1.008],
['He', 'Helium', 2, 4.002602],
['Li', 'Lithium', 3, 6.94],
['Be', 'Beryllium', 4, 9.012182],
['B', 'Bor', 5, 10.81],
['C', 'Kohlenstoff', 6, 12.011],
['N', 'Stickstoff', 7, 14.007],
['O', 'Sauerstoff', 8, 15.999],
['F', 'Fluor', 9, 18.9984032],
['Ne', 'Neon', 10, 20.1797],
['Na', 'Natrium', 11, 22.98976928],
['Mg', 'Magnesium', 12, 24.305],
['Al', 'Aluminum', 13, 26.9815386],
['Si', 'Slicium', 14, 28.0855],
['P', 'Phosphor', 15, 30.973762],
['S', 'Schwefel', 16, 32.06],
['Cl', 'Chlor', 17, 35.45],
['Ar', 'Argon', 18, 39.948],
['K', 'Kalium', 19, 39.0983],
['Ca', 'Calcium', 20, 40.078],
['Sc', 'Scandium', 21, 44.955912],
['Ti', 'Titan', 22, 47.867],
['V', 'Vanadium', 23, 50.9415],
['Cr', 'Chrom', 24, 51.9961],
['Mn', 'Mangan', 25, 54.938045],
['Fe', 'Eisen', 26, 55.845],
['Co', 'Cobalt', 27, 58.933195],
['Ni', 'Nickel', 28, 58.6934],
['Cu', 'Kupfer', 29, 63.546],
['Zn', 'Zink', 30, 65.38],
['Ga', 'Gallium', 31, 69.723],
['Ge', 'Germanium', 32, 72.63],
['As', 'Arsen', 33, 74.9216],
['Se', 'Selen', 34, 78.96],
['Br', 'Brom', 35, 79.904],
['Kr', 'Krypton', 36, 83.798],
['Rb', 'Rubidium', 37, 85.4678],
['Sr', 'Strontium', 38, 87.62],
['Y', 'Yttrium', 39, 88.90585],
['Zr', 'Zirkonium', 40, 91.224],
['Nb', 'Niob', 41, 92.90638],
['Mo', 'Molybdän', 42, 95.96],
['Tc', 'Technetium', 43, 98],
['Ru', 'Ruthenium', 44, 101.07],
['Rh', 'Rhodium', 45, 102.9055],
['Pd', 'Palladium', 46, 106.42],
['Ag', 'Silber', 47, 107.8682],
['Cd', 'Cadmium', 48, 112.411],
['In', 'Indium', 49, 114.818],
['Sn', 'Zinn', 50, 118.71],
['Sb', 'Antimon', 51, 121.76],
['Te', 'Tellur', 52, 127.6],
['I', 'Jod', 53, 126.90447],
['Xe', 'Xenon', 54, 131.293],
['Cs', 'Cäsium', 55, 132.9054519],
['Ba', 'Barium', 56, 137.327],
['La', 'Lanthan', 57, 138.90547],
['Ce', 'Cer', 58, 140.116],
['Pr', 'Praseodym', 59, 140.90765],
['Nd', 'Neodym', 60, 144.242],
['Pm', 'Promethium', 61, 145],
['Sm', 'Samarium', 62, 150.36],
['Eu', 'Europium', 63, 151.964],
['Gd', 'Gadolinium', 64, 157.25],
['Tb', 'Terbium', 65, 158.92535],
['Dy', 'Dysprosium', 66, 162.5],
['Ho', 'Holmium', 67, 164.93032],
['Er', 'Erbium', 68, 167.259],
['Tm', 'Thulium', 69, 168.93421],
['Yb', 'Ytterbium', 70, 173.054],
['Lu', 'Lutetium', 71, 174.9668],
['Hf', 'Hafnium', 72, 178.49],
['Ta', 'Tantal', 73, 180.94788],
['W', 'Wolfram', 74, 183.84],
['Re', 'Rhenium', 75, 186.207],
['Os', 'Osmium', 76, 190.23],
['Ir', 'Iridium', 77, 192.217],
['Pt', 'Platin', 78, 195.084],
['Au', 'Gold', 79, 196.966569],
['Hg', 'Quecksilber', 80, 200.59],
['Tl', 'Thallium', 81, 204.38],
['Pb', 'Blei', 82, 207.2],
['Bi', 'Wismuth', 83, 208.9804],
['Po', 'Polonium', 84, 209],
['At', 'Astat', 85, 210],
['Rn', 'Radon', 86, 222],
['Fr', 'Francium', 87, 223],
['Ra', 'Radium', 88, 226],
['Ac', 'Actinium', 89, 227],
['Th', 'Thorium', 90, 232.03806],
['Pa', 'Proactinium', 91, 231.03588],
['U', 'Uran', 92, 238.02891],
['Np', 'Neptunium', 93, 237],
['Am', 'Americium', 94, 243],
['Pu', 'Plutonium', 95, 244],
['Bk', 'Berkelium', 96, 247],
['Cm', 'Curium', 97, 247],
['Cf', 'Californium', 98, 251],
['Es', 'Einsteinium', 99, 252],
['Fm', 'Fermium', 100, 257],
['Md', 'Mendelevium', 101, 258],
['No', 'Nobelium', 102, 259],
['Lr', 'Lawrencium', 103, 262],
['Rf', 'Rutherfordium', 104, 265],
['Db', 'Dubnium', 105, 268],
['Sg', 'Seaborgium', 106, 271],
['Bh', 'Bohrium', 107, 270],
['Hs', 'Hassium', 108, 277],
['Mt', 'Meitnerium', 109, 276],
['Ds', 'Darmstadtium', 110, 281]
];
// Konverter: Array => Object
function convert(arr){return { formel: arr[0], name: arr[1], oz: arr[2], masse: arr[3]};}

this.get = function(n) {
	for(var i = 0; i < elements.length; i++) 
		if(elements[i][0] === n) return convert(elements[i]);
	
	return null;
};
this.getAttribute = function(n, a) { this.get(n)[a];};
this.getByName = function(n, nc) {
	if(!n) return null;
	if(nc) n = n.toLowerCase()
	for(var i = 0; i < elements.length; i++) {
		var check = nc ? elements[i][1].toLowerCase() : elements[i][1];
		if(check === n) return convert(elements[i]);
	}
	return null;
};
};
// Helper
if (!Array.prototype.forEach ) {
Array.prototype.forEach = function(fun) {
	var len = this.length;
	if (typeof fun != "function")throw new TypeError();
	var thisp = arguments[1];
	for (var i = 0; i < len; i++) if (i in this) fun.call(thisp, this[i], i, this);
};
}


