(function() {
    var setEventHandler = (function() {
	if (window.attachEvent) {
	    return function(obj, evt, func) {
		obj.attachEvent('on' + evt, func);
	    };
	} else if (window.addEventListener) {
	    return function(obj, evt, func, capture) {
		obj.addEventListener(evt, func, capture);
	    };
	} else {
	    return function(){};
	}
    })();

    window.jCquard = {
	ready: function(func) {
	    if (defaultCard.imageLoaded) {
		func();
	    } else {
		setEventHandler(defaultCard.image, "load", func, false);
	    }
	},
	setDefaultCard: function(cardDefinition) {
	    defaultCard = cardDefinition;
	    defaultCard.imageLoaded = false;
	    defaultCard.imageLoadCallback = function() {
		defaultCard.imageLoaded = true;
	    };
    
	    defaultCard.image = new Image();
	    setEventHandler(defaultCard.image, "load", defaultCard.imageLoadCallback, false);
	    defaultCard.image.src = defaultCard.imageSrc;
	}
    };

    var defaultCard = {
	imageLoaded: false
    };
    jCquard.setDefaultCard({
	imageSrc: "defaultCard.png", // Credit: Mark Wathieu, http://flickr.com/photos/marcwathieu/2511328608/
	chadWidth: 4,
	chadHeight: 7,
	columnCount: 80,
	rowCount: 10,
	columnPos: [
	    22, 28, 33, 39, 45, 51, 56, 62, 68, 73,
	    79, 85, 90, 96, 102, 108, 113, 119, 125, 130,
	    136, 141, 147, 153, 158, 164, 170, 175, 181, 187,
	    193, 198, 204, 210, 215, 221, 227, 232, 238, 244,
	    250, 255, 261, 267, 272, 278, 284, 289, 295, 301,
	    306, 312, 318, 324, 329, 335, 341, 346, 352, 358,
	    363, 369, 375, 380, 386, 392, 398, 403, 409, 415,
	    420, 426, 432, 437, 443, 449, 454, 460, 466, 471
	],
	rowPos: [
	    54, 71, 87, 104, 120, 136, 153, 169, 186, 202
	]
    });
  
    jCquard.Card = function(options) {
	if (this instanceof jCquard.Card) {
	    this.init(options);
	} else {
	    return new jCquard.Card(options);
	}
    };

    jCquard.Card.prototype.init = function(options) {
	var options = options || {};
	this.title = options.title || "Untitled";

	var id = "jcq-" + ((new Date).getTime()).toString(32);
	this.getId = function(){return id;};

	var punches = [];
	for (col = 0; col<defaultCard.columnCount; col++) {
	    punches[col] = [];
	    for (row = 0; row < defaultCard.rowCount; row++) {
		punches[col][row] = 0;
	    }
	}

	this.punchHole = function(column, row, noRender) {
	    punches[column][row] = 1;  
	    if (!noRender)
		try {
		    this.render();
		} catch (e) {}

	    return this;
	};

	this.isPunched = function(column, row) {
	    return punches[column][row];
	};

	if (options.image) {
	    this.setValueFromImage(options.image);
	}
    };
	
    jCquard.Card.prototype.render = function(){
	if (!window.CanvasRenderingContext2D)
	    throw new Error("Canvas is not supported by this browser");

	var canvas = document.getElementById(this.getId());

	if (!canvas) {
	    canvas = document.createElement("canvas");
	    canvas.height = defaultCard.image.height;
	    canvas.width = defaultCard.image.width;
	    canvas.id = this.getId();
	}

	var ctx = canvas.getContext('2d');

	ctx.drawImage(defaultCard.image,0,0);

	ctx.fillStyle = "#01224d";

	if (ctx.fillText) {
	    ctx.font = "10px monospace";
	    ctx.fillText(this.title, 30, 30);
	} else if (ctx.mozDrawText) {
	    ctx.mozTextStyle = "10px monospace";
	    ctx.translate(30, 30);
	    ctx.mozDrawText(this.title);
	    ctx.translate(-30,-30);
	}
 
	for (var col=0; col< defaultCard.columnCount; col++) {
	    for (var row=0; row< defaultCard.rowCount; row++) {  
		if (this.isPunched(col, row))
		    ctx.clearRect(defaultCard.columnPos[col], defaultCard.rowPos[row], defaultCard.chadWidth, defaultCard.chadHeight);
	    }
	}

	return canvas;
    };

    jCquard.Card.prototype.punchValueAt = function(value, column, noRender) {
	if (typeof value == "string")
	    value = value.charCodeAt(0);
	
	for (var row=0; row < defaultCard.rowCount; row++) {
	    if ((value & 1<<row) == (1<<row))
		this.punchHole(column, row, true);
	}

	if (!noRender)
	    try {
		this.render();
	    } catch (e) {}

	return this;
    };
	
    jCquard.Card.prototype.getValueAt = function(column) {
	var value = 0;

	for (var row=0; row < defaultCard.rowCount; row++) {
	    if (this.isPunched(column, row))
		value += 1<<row;
	}

	return value;
    };

    jCquard.Card.prototype.punchStringAt = function(str, column) {
	if (str.length == 0)
	    return this;

	var isArray = str instanceof Array;

	for (var col=column; col < defaultCard.columnCount; col++) {
	    if (col-column > str.length)
		break;
	    this.punchValueAt((isArray?str[col-column]:str.charCodeAt(col-column)), col, true);
	}

	try {
	    this.render();
	} catch (e) {}

	if (col == defaultCard.columnCount && col - column < str.length) {
	    return isArray ? str.slice(col - column) : str.substr(col - column);
	} else {
	    return this;	    
	}
    };

    jCquard.Card.prototype.getStringAt = function(column, format) {
	var returnArray = (format && format == 'array') || false;
	var value = returnArray ? [] : "";

	for (var col=column; col < defaultCard.columnCount; col++) {
	    var currentByte = this.getValueAt(col);
	    if (!currentByte)
		break;
	    if (returnArray) {
		value.push(currentByte);
	    } else {
		value += String.fromCharCode(currentByte);
	    }
	}

	return value;
    };

    jCquard.Card.prototype.getColumnCount = function() {return defaultCard.columnCount;};
    jCquard.Card.prototype.getRowCount = function() {return defaultCard.rowCount;};

    jCquard.Card.prototype.setValueFromImage = function(image) {
	var srcCanvas = document.createElement('canvas');
	srcCanvas.height = image.height;
	srcCanvas.width = image.width;
	var srcCtx = srcCanvas.getContext('2d');
	srcCtx.drawImage(image, 0, 0);
	for (var col=0; col<defaultCard.columnCount; col++) {
	    for (var row=0; row<defaultCard.rowCount; row++) {
		var pixelData = srcCtx.getImageData(defaultCard.columnPos[col], defaultCard.rowPos[row], 1, 1).data;
		if (pixelData[3] == 0) { // If the alpha byte is 0
		    this.punchHole(col, row, true);
		}
	    }
	}
    };
})();
