(function ($) {

var GridMap = function (c) {
    var map = [];

    // TODO: finalize y line
    var fy = 0;

    function findx(y, n) {
        if (map[y] === undefined) {
            arrayfill(y);
            return 0;
        }

        if (n === undefined) {
            n = 0;
        }

        for (var x = n; x < c; x++) {
            if (map[y][x] == '0') {
                return x;
            }
        }

        return -1;
    }

    function arrayfill(y) {
        map[y] = new Array(c);
        for (var i = 0; i < c; i++) { map[y][i] = '0'; }
    };

    this.height = function () {
        return map.length;
    };

    this.get = function (w, h) {
        if (c < w) {
            return null;
        }

        // TODO: much better algorithm

        var py = 0;
        var px = 0;
        var ex, ey, y, x, a;
        while (true) {
            px = 0;

            while (true) {
                px = findx(py, px);
                if (px < 0) {
                    px = -1;
                    break;
                }

                if (c < px + w) {
                    break;
                }

                ex = px + w;
                ey = py + h;
                a = true;
                for (y = py; y < ey; y++) {
                    if (map[y] === undefined) {
                        continue;
                    }

                    for (x = px; x < ex; x++) {
                        if (map[y][x] == '1') {
                            px = x + 1;
                            a = false;
                            break;
                        }
                    }

                    if (!a) break;
                }

                if (a) {
                    return {x:px, y:py};
                }
            }

            py++;
        }
    };

    this.getWormholes = function () {
        // TODO: combine holes.
        var holes = [];
        var len = map.length;
        for (var y = 0; y < len; y++) {
            for (var x = 0; x < c; x++) {
                if (map[y][x] == '0') {
                    holes.push({x:x, y:y});
                }
            }
        }
        return holes;
    };

    this.set = function (sx, sy, w, h) {
        var ex = sx + w;
        var ey = sy + h;
        var y, x;
        for (y = sy; y < ey; y++) {
            if (!map[y]) {
                arrayfill(y);
            }

            for (x = sx; x < ex; x++) {
                map[y][x] = '1';
            }
        }
    };

    this.dump = function () {
        var s = '';
        var len = map.length;
        for (var y = 0; y < len; y++) {
            if (!map[y]) {
                arrayfill(y);
            }

            for (var x = 0; x < c; x++) {
                s += map[y][x];
            }

            s += "\n";
        }
        return s;
    };
};

function arrange(_) {
    //$.benchmark.start();  
    
    var d = _.data('oreoregrid');

    // number of columns
    var cols = Math.floor(($(window).width() - d.margin - d.sideMargin) / d.gridSize);

    var map = new GridMap(cols);

    var convwh = function (e) {
        if (e.parent().size() <= 0) {
            _.append(e);
        }

        var cssw, cssh;
        if (e.has('.module')) {
            cssw = e.outerWidth();
            cssh = e.outerHeight();
        }
        else {
            cssw = parseInt(e.get(0).style.width);
            cssh = parseInt(e.get(0).style.height);
        }
        var w = (cssw + d.margin) / d.gridSize;
        var h = (cssh + d.margin) / d.gridSize;
        return [w, h];
    };

    var sete = function (e, x, y, w, h) {
        e.css({
            left: x * d.gridSize,
            top: y * d.gridSize,
            position: 'absolute'
        });

        map.set(x, y, w, h);
    };

    d.fixedElements.each(function (idx, e) {
        var wh = convwh(e);

        var x = parseInt(e.attr('data-grid-x'));
        var y = parseInt(e.attr('data-grid-y'));

        if (x < 0) {
            x = cols + x;
        }

        sete(e, x, y, wh[0], wh[1]);
    });

    d.elements.each(function (idx, e) {
        var wh = convwh(e);

        var pos = map.get(wh[0], wh[1]);

        if (pos == null) {
            // XXX: 対策を考える。
            return false;
        }

        sete(e, pos.x, pos.y, wh[0], wh[1]);
    });

    /*
    var holes = map.getWormholes();
    $(holes).each(function (idx, r) {
        var e = $('<div class="spacer" />');
        e.css({
            backgroundColor: '#ddd',
            width: d.columnWidth,
            height: d.columnWidth,
            left: r.x * d.gridSize,
            top: r.y * d.gridSize,
            position: 'absolute'
        });
        _.append(e);
        map.set(r.x, r.y, 1, 1);
    });
    */

    _.css({
        width: d.gridSize * cols - d.margin,
        height: d.gridSize * map.height()
    });

    //console.log(map.dump());
    //$.benchmark.end();  
};

var methods = {
    init: function (opts) {
        var _ = $(this);

        var d = {};
        d.margin = opts.margin;
        d.columnWidth = opts.columnWidth;
        d.gridSize = opts.margin + opts.columnWidth;
        d.sideMargin = opts.sideMargin;

        d.fixedElements = $([]);
        d.elements = $([]);

        _.css({
            position: 'relative'
        });

        _.data('oreoregrid', d);
    },

    append_fixed: function (els) {
        var _ = $(this);
        var d = _.data('oreoregrid');

        $(els).each(function () {
            d.fixedElements.push(this);
        });
    },

    append_box: function (els) {
        var _ = $(this);
        var d = _.data('oreoregrid');

        $(els).each(function () {
            d.elements.push(this);
        });
    },

    arrange: function (opts) {
        arrange($(this));
    }
};

$.fn.oreoregrid = function (method) {
    if (methods[method]) {
        return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
    }
    else if (typeof method === 'object' || !method) {
        return methods.init.apply(this, arguments);
    }
    else {
        $.error('Method ' +  method + ' does not exist on jQuery.oreoregrid');
    }    
};

$.fn.extend({
    _oreoregrid: function (opts) {
        /*
        var map = new GridMap(10);
        map.set(1, 1, 3, 3);
        map.set(0, 0, 10, 1);
        map.get(1, 1);
        map.get(2, 2);
        map.get(3, 3);
        map.get(6, 6);
        map.get(1, 2);
        map.get(1, 3);
        map.get(7, 7);
        map.get(10, 10);
        map.get(11, 11);
        console.log(map.dump());
        */

        var _ = $(this);

        var d = {};
        d.margin = opts.margin;
        d.columnWidth = opts.columnWidth;
        d.gridSize = opts.margin + opts.columnWidth;
        d.sideMargin = opts.sideMargin;
        d.fixedElements = $(opts.fixedElements);
        d.elements = $(opts.elements);

        _.css({
            position: 'relative'
        });

        _.data('oreoregrid', d);

        arrange(_);

        $(window).bind('smartresize.oreoregrid', function() {
            arrange(_);
        });
    }
});

})(jQuery);

