/*

Copyright 2008-2015 Clipperz Srl

This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.

* Clipperz 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.

* Clipperz 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 Clipperz. If not, see http://www.gnu.org/licenses/.

*/

try { if (typeof(Clipperz.ByteArray) == 'undefined') { throw ""; }} catch (e) {
	throw "Clipperz.Crypto.ECC depends on Clipperz.ByteArray!";
}  
if (typeof(Clipperz.Crypto.ECC) == 'undefined') { Clipperz.Crypto.ECC = {}; }
if (typeof(Clipperz.Crypto.ECC.BinaryField) == 'undefined') { Clipperz.Crypto.ECC.BinaryField = {}; }

Clipperz.Crypto.ECC.BinaryField.Value = function(aValue, aBase) {
	if (aValue.constructor == String) {
		var	value;
		var	stringLength;
		var numberOfWords;
		var	i,c;
	
		if (aBase != 16) {
			throw Clipperz.Crypto.ECC.BinaryField.Value.exception.UnsupportedBase;
		}

		value = aValue.replace(/ /g, '');
		stringLength = value.length;
		numberOfWords = Math.ceil(stringLength / 8);
		this._value = new Array(numberOfWords);
	
		c = numberOfWords;
		for (i=0; i<c; i++) {
			var	word;
		
			if (i < (c-1)) {
				word = parseInt(value.substr(stringLength-((i+1)*8), 8), 16);
			} else {
				word = parseInt(value.substr(0, stringLength-(i*8)), 16);
			}
			
			this._value[i] = word;
		}
	} else if (aValue.constructor == Array) {
		var itemsToCopy;

		itemsToCopy = aValue.length;
		while (aValue[itemsToCopy - 1] == 0) {
			itemsToCopy --;
		}

		this._value = aValue.slice(0, itemsToCopy);
	} else if (aValue.constructor == Number) {
		this._value = [aValue];
	} else {
//		throw Clipperz.Crypto.ECC.BinaryField.Value.exception.UnsupportedConstructorValueType;
	}
	
	return this;
}

Clipperz.Crypto.ECC.BinaryField.Value.prototype = MochiKit.Base.update(null, {

	'value': function() {
		return this._value;
	},

	//-----------------------------------------------------------------------------
	
	'wordSize': function() {
		return this._value.length
	},

	//-----------------------------------------------------------------------------

	'clone': function() {
		return new Clipperz.Crypto.ECC.BinaryField.Value(this._value.slice(0));
	},
	
	//-----------------------------------------------------------------------------

	'isZero': function() {
		return (this.compare(Clipperz.Crypto.ECC.BinaryField.Value.O) == 0);
	},

	//-----------------------------------------------------------------------------

	'asString': function(aBase) {
		var	result;
		var i,c;
		
		if (aBase != 16) {
			throw Clipperz.Crypto.ECC.BinaryField.Value.exception.UnsupportedBase;
		}
		
		result = "";
		c = this.wordSize();
		for (i=0; i<c; i++) {
			var	wordAsString;
			
//			wordAsString = ("00000000" + this.value()[i].toString(16));
			wordAsString = ("00000000" + this._value[i].toString(16));
			wordAsString = wordAsString.substring(wordAsString.length - 8);
			result = wordAsString + result;
		}
		
		result = result.replace(/^(00)*/, "");
		
		if (result == "") {
			result = "0";
		}
		
		return result;
	},
	
	//-----------------------------------------------------------------------------

	'shiftLeft': function(aNumberOfBitsToShift) {
		return new Clipperz.Crypto.ECC.BinaryField.Value(Clipperz.Crypto.ECC.BinaryField.Value._shiftLeft(this._value, aNumberOfBitsToShift));
	},

	//-----------------------------------------------------------------------------

	'bitSize': function() {
		return Clipperz.Crypto.ECC.BinaryField.Value._bitSize(this._value);
	},
	
	//-----------------------------------------------------------------------------

	'isBitSet': function(aBitPosition) {
		return Clipperz.Crypto.ECC.BinaryField.Value._isBitSet(this._value, aBitPosition);
	},
	
	//-----------------------------------------------------------------------------

	'xor': function(aValue) {
		return new Clipperz.Crypto.ECC.BinaryField.Value(Clipperz.Crypto.ECC.BinaryField.Value._xor(this._value, aValue._value));
	}, 

	//-----------------------------------------------------------------------------

	'compare': function(aValue) {
		return Clipperz.Crypto.ECC.BinaryField.Value._compare(this._value, aValue._value);
	},
	
	//-----------------------------------------------------------------------------
	__syntaxFix__: "syntax fix"
});

Clipperz.Crypto.ECC.BinaryField.Value.O = new Clipperz.Crypto.ECC.BinaryField.Value('0', 16);
Clipperz.Crypto.ECC.BinaryField.Value.I = new Clipperz.Crypto.ECC.BinaryField.Value('1', 16);

Clipperz.Crypto.ECC.BinaryField.Value._xor = function(a, b, aFirstItemOffset) {
	var result;
	var resultSize;
	var i,c;
	var firstItemOffset;
	
	firstItemOffset = aFirstItemOffset || 0;
	resultSize = Math.max((a.length - firstItemOffset), b.length) + firstItemOffset;

	result = new Array(resultSize);
	
	c = firstItemOffset;
	for (i=0; i<c; i++) {
		result[i] = a[i];
	}

	c = resultSize;
	for (i=firstItemOffset; i<c; i++) {
		result[i] = (((a[i] || 0) ^ (b[i - firstItemOffset] || 0)) >>> 0);
	}
	
	return result;
};

Clipperz.Crypto.ECC.BinaryField.Value._overwriteXor = function(a, b, aFirstItemOffset) {
	var i,c;
	var firstItemOffset;
	
	firstItemOffset = aFirstItemOffset || 0;

	c = Math.max((a.length - firstItemOffset), b.length) + firstItemOffset;
	for (i=firstItemOffset; i<c; i++) {
		a[i] = (((a[i] || 0) ^ (b[i - firstItemOffset] || 0)) >>> 0);
	}
};

Clipperz.Crypto.ECC.BinaryField.Value._shiftLeft = function(aWordArray, aNumberOfBitsToShift) {
	var numberOfWordsToShift;
	var	numberOfBitsToShift;
	var result;
	var	overflowValue;
	var	i,c;

	numberOfWordsToShift = Math.floor(aNumberOfBitsToShift / 32);
	numberOfBitsToShift = aNumberOfBitsToShift % 32;

	result = new Array(aWordArray.length + numberOfWordsToShift);
	
	c = numberOfWordsToShift;
	for (i=0; i<c; i++) {
		result[i] = 0;
	}
	
	overflowValue = 0;
	nextOverflowValue = 0;
	
	c = aWordArray.length;
	for (i=0; i<c; i++) {
		var	value;
		var	resultWord;

//		value = this.value()[i];
		value = aWordArray[i];

		if (numberOfBitsToShift > 0) {
			var nextOverflowValue;
		
			nextOverflowValue = (value >>> (32 - numberOfBitsToShift));
			value = value & (0xffffffff >>> numberOfBitsToShift);
			resultWord = (((value << numberOfBitsToShift) | overflowValue) >>> 0);
		} else {
			resultWord = value;
		}
		
		result[i+numberOfWordsToShift] = resultWord;
		overflowValue = nextOverflowValue;
	}

	if (overflowValue != 0) {
		result[aWordArray.length + numberOfWordsToShift] = overflowValue;
	}

	return result;
};

Clipperz.Crypto.ECC.BinaryField.Value._overwriteShiftLeft = function(aWordArray, aNumberOfBitsToShift) {
	var numberOfWordsToShift;
	var	numberOfBitsToShift;
	var result;
	var	overflowValue;
	var	i,c;

	numberOfWordsToShift = Math.floor(aNumberOfBitsToShift / 32);
	numberOfBitsToShift = aNumberOfBitsToShift % 32;

	result = new Array(aWordArray.length + numberOfWordsToShift);
	
	c = numberOfWordsToShift;
	for (i=0; i<c; i++) {
		result[i] = 0;
	}
	
	overflowValue = 0;
	nextOverflowValue = 0;
	
	c = aWordArray.length;
	for (i=0; i<c; i++) {
		var	value;
		var	resultWord;

//		value = this.value()[i];
		value = aWordArray[i];

		if (numberOfBitsToShift > 0) {
			var nextOverflowValue;
		
			nextOverflowValue = (value >>> (32 - numberOfBitsToShift));
			value = value & (0xffffffff >>> numberOfBitsToShift);
			resultWord = (((value << numberOfBitsToShift) | overflowValue) >>> 0);
		} else {
			resultWord = value;
		}
		
		result[i+numberOfWordsToShift] = resultWord;
		overflowValue = nextOverflowValue;
	}

	if (overflowValue != 0) {
		result[aWordArray.length + numberOfWordsToShift] = overflowValue;
	}

	return result;
};

Clipperz.Crypto.ECC.BinaryField.Value._bitSize = function(aWordArray) {
	var	result;
	var	notNullElements;
	var mostValuableWord;
	var matchingBitsInMostImportantWord;
	var mask;
	var i,c;

	notNullElements = aWordArray.length;
	
	if ((aWordArray.length == 1) && (aWordArray[0] == 0)) {
		result = 0;
	} else {
		while((aWordArray[notNullElements - 1] == 0) && (notNullElements > 0)) {
			notNullElements --;
		}
	
		result = (notNullElements - 1) * 32;
		mostValuableWord = aWordArray[notNullElements - 1];

		matchingBits = 32;
		mask = 0x80000000;
	
		while ((matchingBits > 0) && ((mostValuableWord & mask) == 0)) {
			matchingBits --;
			mask >>>= 1;
		}
	
		result += matchingBits;
	}
	
	return result;
};

Clipperz.Crypto.ECC.BinaryField.Value._isBitSet = function(aWordArray, aBitPosition) {
	var result;
	var	byteIndex;
	var bitIndexInSelectedByte;

	byteIndex = Math.floor(aBitPosition / 32);
	bitIndexInSelectedByte = aBitPosition % 32;
	
	if (byteIndex <= aWordArray.length) {
		result = ((aWordArray[byteIndex] & (1 << bitIndexInSelectedByte)) != 0);
	} else {
		result = false;
	}

	return result;
};

Clipperz.Crypto.ECC.BinaryField.Value._compare = function(a,b) {
	var	result;
	var i,c;
	
	result = MochiKit.Base.compare(a.length, b.length);

	c = a.length;
	for (i=0; (i<c) && (result==0); i++) {
//console.log("compare[" + c + " - " + i + " - 1] " + this.value()[c-i-1] + ", " + aValue.value()[c-i-1]);
//		result = MochiKit.Base.compare(this.value()[c-i-1], aValue.value()[c-i-1]);
		result = MochiKit.Base.compare(a[c-i-1], b[c-i-1]);
	}
	
	return result;
};


Clipperz.Crypto.ECC.BinaryField.Value['exception']= {
	'UnsupportedBase':					new MochiKit.Base.NamedError("Clipperz.Crypto.ECC.BinaryField.Value.exception.UnsupportedBase"),
	'UnsupportedConstructorValueType':	new MochiKit.Base.NamedError("Clipperz.Crypto.ECC.BinaryField.Value.exception.UnsupportedConstructorValueType")
};