// boekkat: cuecat support for javascript
// copyright 2005 jared jennings. all rights reserved.
// licensed under a BSD-style license. See LICENSE.txt.

// The Interpreter validates and interprets barcodes into desired formats.
// It's used by the Actor.

// Any method the Interpreter has which does not start with an underline (_) is
// taken to be a function which takes a CueCat triple (serial code, symbology,
// barcode content), and, if it is valid, returns only the data of the barcode,
// interpreted or re-checksummed as necessary. If it is not valid under the 
// interpretation the function provides, the function must raise an
// InterpreterFailure.

function Interpreter() {}
	// ISBN check digit calculated according to the standard at
	// http://www.isbn.org/standards/home/isbn/international/html/usm4.htm

	// multiply last (non-check) digit by 2, next-to-last by 3, etc.,
	// first by 10. the complete sum plus the check digit = 0 (mod 11).
	// i.e., (sum + check) % 11 == 0.

	// if the check digit is 10, it's written as X; 0-9 written normally.
	// a nine-digit isbn is always less than 2^29 
	// -> ok to parse the whole thing on 32-bit machines
	Interpreter.prototype._checkDigit_ISBN10 = function(isbn_str) {
		var x = parseInt(isbn_str,10);
		var remain;
		var digit;
		var sum = 0;
		var weight;
		for(weight=2; weight<=10; weight++) {
			remain = Math.floor( x/10 );
			digit = x % 10;
			sum += digit * weight;
			x = remain;
		}
		sum = 11 - (sum % 11);
		return (sum == 10 ? "X" : String(sum));
	}


	// EAN13 check digit
	// reference: http://www.barcodeisland.com/ean13.phtml
	//
	// we can't parseInt here, the number's too big
	//
	Interpreter.prototype._checkDigit_EAN13 = function(ean_str) {
		var odd = 0, even = 0;
		// starting at the beginning, the "13th" digit is even
		var oddp = false;
		var digit, i;

		for(i=0; i<ean_str.length; i++) {
			digit = parseInt(ean_str.charAt(i));
			if(oddp) {
				odd += digit * 3;
			} else {
				even += digit
			}
			oddp = !oddp;
		}
		digit = 10 - ((odd + even) % 10);
		return (digit == 10 ? "0" : String(digit));
	}
			
	Interpreter.prototype._isBooklandEAN = function(code) {
		return (code.indexOf("978") == 0) ||
		       (code.indexOf("979") == 0);
	}

	Interpreter.prototype._isBookland978EAN = function(code) {
		return (code.indexOf("978") == 0);
	}

	Interpreter.prototype.ISBN13 = function(triple) {
		var type = triple[1];
		var code = triple[2];
		var isbn_stub;
		if(type == "IBN") {
			if(code.length == 13) {
				if(this._isBooklandEAN(code)) {
					return code;
				} else {
					throw new InterpreterFailure("non-Bookland EAN code");
				}
			} else {
				throw new InterpreterFailure("malformed Bookland EAN code");
			}
		} else if(type == "IB5") {
			if(code.length == 18) {
				var ean13 = code.substr(0,13);
				if(this._isBooklandEAN(ean13)) {
					return ean13;
				} else {
					throw new InterpreterFailure("non-Bookland EAN+5 code");
				}
			} else {
				throw new InterpreterFailure("malformed Bookland EAN+5 code");
			}
		} else {
			// speaking of things that aren't IBN or IB5...
			// we can't figure out ISBN13's (thus ISBN10's) from UPC+5s (UA5)
			// without a nice table of correlations between UPC supplier
			// prefixes and ISBN publisher numbers. So if you try to scan a
			// cheap paperback such as you would buy at the grocery store
			// before 2005, you might well fail.
			// See http://www.eblong.com/zarf/bookscan/
			// and http://www.bisg.org/pi/barcode_considerations.html

			throw new InterpreterFailure("not interpretable as ISBN: "+triple[1]+" "+triple[2]);
		}
	}


	Interpreter.prototype.ISBN10 = function(triple) {
		// if getISBN13 bombs, let the exception propagate
		var isbn13 = this.ISBN13(triple);
		var isbn_stub;
		if(this._isBookland978EAN(isbn13)) {
			isbn_stub = isbn13.substr(3,9);
			return isbn_stub + this._checkDigit_ISBN10(isbn_stub);
		} else {
			throw new InterpreterFailure("ISBN13 not interpretable as ISBN10: "+isbn13);
		}
	}

	Interpreter.prototype.any = function(triple) {
		// return the contents of the barcode only. 
		// do not try to interpret or validate
		return triple[2];
	}

function InterpreterFailure(text) {
	Error.apply(this, [text,text]);
	this.name = "InterpreterFailure";
}
InterpreterFailure.prototype = new Error;
InterpreterFailure.prototype.constructor = InterpreterFailure;

// reference links:
// CueCat barcode symbology support
// http://osiris.978.org/~brianr/cuecat/files/cuecat-0.0.8/SUPPORTED_BARCODES

// ISBN hyphenation
// http://www.isbn.org/standards/home/isbn/international/hyphenation-instructions.asp

