(function($) {

    // where can be 'under' (default), 'over', 'unshift', 'push'
    $.fn.canvas = function(where) {     // reuse if where is not there
        //console.log( 'canvas, iterating DOM elements');
        $(this).each(function() {
            var $this = $(this);
            //console.log( 'canvas, working DOM element,', $this);

            var w = $this.width();
            var h = $this.height();
            //console.log( 'canvas, dimensions [w=' + w + ',h=' + h + ']');

            if (! where) where = 'under';
            //console.log( 'canvas, where to put is [' + where + ']');

            // first, remove all former canvas objects in this div
            $this.find('.cnvsWrapper').remove();
            $this.find('.cnvsCanvas').remove();
            var $canvas = $('<canvas class="cnvsCanvas" style="position:absolute;top:0px;left:0px;width:100%;height:100%;" width="' + w + '" height="' + h + '"></canvas>');


            // if under or over, will have to wrap current contents
            if (where == 'under' || where == 'over') {
                $this.wrapInner('<div class="cnvsWrapper" style="position:absolute;top:0px;left:0px;width:100%;height:100%;border:0px;padding:0px;margin:0px;"></div>');
            }
            if (where == 'under' || where == 'unshift') {
                $this.prepend($canvas);
            }
            if (where == 'over' || where == 'push') {
                $this.append($canvas);
            }

            // if in IE, initialize the canvas tag
            if ($.browser.msie) {
                var canvas = G_vmlCanvasManager.initElement($canvas.get(0));
                $canvas = $(canvas);
            }

            this.cnvs = canvasObject($canvas, w, h);
            return this;
        });
        return this;
    }
    $.fn.uncanvas = function() {
        $(this).each(function() {
            this.cnvs.getTag().remove();
            this.cnvs = null;
        });
        return this;
    }
    $.fn.hidecanvas = function() {
        $(this).each(function() {
            this.cnvs.getTag().hide();
        });
        return this;
    }
    $.fn.showcanvas = function() {
        $(this).each(function() {
            this.cnvs.getTag().show();
        });
        return this;
    }
    // call back with cnvs object, thus, allowing raw access
    $.fn.canvasraw = function(callback) {
        $(this).each(function() {
            if (callback) eval(callback)(this.cnvs);
        });
    }
    // fills in the array with settings for each canvas
    // list( hash( 'width', 'height', 'tag' (jquery), 'context'))
    $.fn.canvasinfo = function(info) {
        $(this).each(function() {
            info[ info.length] = {};
            info[ info.length - 1].width = this.cnvs.w;
            info[ info.length - 1].height = this.cnvs.h;
            info[ info.length - 1].tag = this.cnvs.$tag;
            info[ info.length - 1].context = this.cnvs.c;
        });
    }

    // canvas operations (literaly the canvas functions)
    $.fn.style = function(style) {
        $(this).each(function() {
            this.cnvs.style(style);
            return this;
        });
        return this;
    }
    $.fn.beginPath = function() {
        $(this).each(function() {
            this.cnvs.beginPath();
            return this;
        });
        return this;
    }
    $.fn.closePath = function() {
        $(this).each(function() {
            this.cnvs.closePath();
            return this;
        });
        return this;
    }
    $.fn.stroke = function() {
        $(this).each(function() {
            this.cnvs.stroke();
            return this;
        });
        return this;
    }
    $.fn.fill = function() {
        $(this).each(function() {
            this.cnvs.fill();
            return this;
        });
        return this;
    }
    $.fn.moveTo = function(coord) {
        $(this).each(function() {
            this.cnvs.moveTo(coord);
            return this;
        });
        return this;
    }
    $.fn.arc = function(coord, settings, style) {
        $(this).each(function() {
            this.cnvs.arc(coord, settings, style);
            return this;
        });
        return this;
    }
    $.fn.arcTo = function(coord1, coord2, settings, style) {
        $(this).each(function() {
            this.cnvs.arcTo(coord1, coord2, settings, style);
            return this;
        });
        return this;
    }
    $.fn.bezierCurveTo = function(ref1, ref2, end, style) {
        $(this).each(function() {
            this.cnvs.bezierCurveTo(ref1, ref2, end, style);
            return this;
        });
        return this;
    }
    $.fn.quadraticCurveTo = function(ref1, end, style) {
        $(this).each(function() {
            this.cnvs.quadraticCurveTo(ref1, end, style);
            return this;
        });
        return this;
    }
    $.fn.clearRect = function(coord, settings) {
        $(this).each(function() {
            this.cnvs.clearRect(coord, settings);
            return this;
        });
        return this;
    }
    $.fn.strokeRect = function(coord, settings, style) {
        $(this).each(function() {
            this.cnvs.fillRect(coord, settings, style);
            return this;
        });
        return this;
    }
    $.fn.fillRect = function(coord, settings, style) {
        $(this).each(function() {
            this.cnvs.strokeRect(coord, settings, style);
            return this;
        });
        return this;
    }
    $.fn.rect = function(coord, settings, style) {
        $(this).each(function() {
            this.cnvs.rect(coord, settings, style);
            return this;
        });
        return this;
    }
    $.fn.lineTo = function(end, style) {
        $(this).each(function() {
            this.cnvs.lineTo(end, style);
            return this;
        });
        return this;
    }

    // atomic operations
    $.fn.polygon = function(start, blocks, settings, style) {
        $(this).each(function() {
            this.cnvs.atomPolygon(start, blocks, settings, style);
        });
    }

    function canvasObject($canvas, width, height) {
        var cnvs = {};
        cnvs.w = width;
        cnvs.h = height;
        cnvs.$tag = $canvas;
        cnvs.c = $canvas.get(0).getContext('2d');
        cnvs.laststyle = {        // default settings
            'fillStyle'                    : 'rgba( 0, 0, 0, 0.2)',
            'strokeStyle'            : 'rgba( 0, 0, 0, 0.5)',
            'lineWidth'                    : 5
        };

        // functions (all angles are in degrees)
        cnvs.getContext = function() {
            return this.c;
        }
        cnvs.getTag = function() {
            return this.$tag;
        }
        cnvs.deg2rad = function(deg) {
            return 2 * 3.14159265 * ( deg / 360);
        }
        cnvs.style = function(style) {
            if (style) this.laststyle = style;
            //console.log( 'cnvs.style, applying style,', this.laststyle);
            for (var name in this.laststyle) this.c[ name] = this.laststyle[ name];
        }

        // raw canvas functions (see Apple reference)
        cnvs.beginPath = function() {
            this.c.beginPath();
        }
        cnvs.closePath = function() {
            this.c.closePath();
        }
        cnvs.stroke = function() {
            this.c.stroke();
        }
        cnvs.fill = function() {
            this.c.fill();
        }
        cnvs.moveTo = function(coord) {
            this.c.moveTo(coord[ 0], coord[ 1]);
        }
        cnvs.arc = function(coord, settings, style) {
            // provide default settings
            settings = $.extend(
            {
                'radius'                : 50,
                'startAngle'        : 0,
                'endAngle'            : 360,
                'clockwise'        : true
            },
                    settings
                    );
            if (style) this.style(style);
            this.c.arc(
                    coord[ 0], coord[ 1], settings.radius,
                    this.deg2rad(settings.startAngle),
                    this.deg2rad(settings.endAngle),
                    settings.clockwise ? 1 : 0
                    );
        }
        cnvs.arcTo = function(coord1, coord2, settings, style) {
            // provide default settings
            settings = $.extend(
            {
                'radius'                : 50
            },
                    settings
                    );
            if (style) this.style(style);
            this.c.arcTo(
                    coord1[ 0], coord1[ 1],
                    coord2[ 0], coord2[ 1],
                    settings.radius
                    );
        }
        cnvs.bezierCurveTo = function(ref1, ref2, end, style) {
            // provide default settings
            if (style) this.style(style);
            this.c.bezierCurveTo(
                    ref1[ 0], ref1[ 1],
                    ref2[ 0], ref2[ 1],
                    end[ 0], end[ 1]
                    );
        }
        cnvs.quadraticCurveTo = function(ref1, end, style) {
            // provide default settings
            if (style) this.style(style);
            this.c.quadraticCurveTo(
                    ref1[ 0], ref1[ 1],
                    end[ 0], end[ 1]
                    );
        }
        cnvs.clearRect = function(coord, settings, style) {
            // provide default settings
            if (! coord) coord = [ 0, 0];
            settings = $.extend(
            {
                'width'            : this.w,
                'height'            : this.h
            },
                    settings
                    );
            this.c.clearRect(
                    coord[ 0], coord[ 1], settings.width, settings.height
                    );
        }
        cnvs.fillRect = function(coord, settings, style) {
            // provide default settings
            settings = $.extend(
            {
                'width'            : 100,
                'height'            : 50
            },
                    settings
                    );
            if (style) this.style(style);
            this.c.fillRect(
                    coord[ 0], coord[ 1], settings.width, settings.height
                    );
        }
        cnvs.strokeRect = function(coord, settings, style) {
            // provide default settings
            settings = $.extend(
            {
                'width'            : 100,
                'height'            : 50
            },
                    settings
                    );
            if (style) this.style(style);
            this.c.strokeRect(
                    coord[ 0], coord[ 1], settings.width, settings.height
                    );
        }
        cnvs.rect = function(coord, settings, style) {
            // provide default settings
            settings = $.extend(
            {
                'width'            : 100,
                'height'            : 50
            },
                    settings
                    );
            if (style) this.style(style);
            this.c.rect(
                    coord[ 0], coord[ 1], settings.width, settings.height
                    );
        }
        cnvs.lineTo = function(end, style) {
            if (style) this.style(style);
            this.c.lineTo(end[ 0], end[ 1]);
        }

        // extra (atomic) actions, fix some of canvas's shortcoming
        cnvs.path = function(blocks) {
            for (var i = 0; i < blocks.length; i++) {
                var arg1 = null;
                var arg2 = null;
                var arg3 = null;
                var arg4 = null;
                if (blocks[ i].length >= 2) arg1 = blocks[ i][ 1];
                if (blocks[ i].length >= 3) arg2 = blocks[ i][ 2];
                if (blocks[ i].length >= 4) arg3 = blocks[ i][ 3];
                if (blocks[ i].length >= 5) arg4 = blocks[ i][ 4];
                if (blocks[ i][ 0] == 'moveTo') this.moveTo(arg1);
                if (blocks[ i][ 0] == 'arc') this.arc(arg1, arg2, arg3);
                if (blocks[ i][ 0] == 'arcTo') this.arcTo(arg1, arg2, arg3, arg4);
                if (blocks[ i][ 0] == 'bezierCurveTo') this.bezierCurveTo(arg1, arg2, arg3, arg4);
                if (blocks[ i][ 0] == 'quadraticCurveTo') this.quadraticCurveTo(arg1, arg2, arg3);
                if (blocks[ i][ 0] == 'lineTo') this.lineTo(arg1, arg2);
            }
        }
        cnvs.atomPolygon = function(start, blocks, settings, style) {
            settings = $.extend(
            {
                'fill'                    : false,
                'stroke'            : true,
                'close'            : false
            },
                    settings
                    );
            this.style(style);
            if (settings.stroke) {        // fill first
                this.beginPath();
                this.moveTo(start);
                this.path(blocks);
                if (settings.close) {
                    this.moveTo(start);
                    this.closePath();
                }
                this.c.fillStyle = 'rgba( 0, 0, 0, 0)';
                this.stroke();
            }
            this.style(style);
            if (settings.fill) {        // fill first
                this.beginPath();
                this.moveTo(start);
                this.path(blocks);
                if (settings.close) {
                    this.moveTo(start);
                    this.closePath();
                }
                this.c.strokeStyle = 'rgba( 0, 0, 0, 0)';
                this.fill();
            }
            this.style(style);
        }

        return cnvs;
    }

})(jQuery)
