﻿//          Functions to align web-page elements to a grid by setting their top, left, height, and width.  
//          
//          The grid is defined by horizontal and vertical alignment lines.  Elements are assigned to start and end at specific lines.
//          Alignment lines are identified by numbers (allowing that new lines may be added between existing lines without needing to 
//          reassign existing element layouts.  The grid does not need to be completely filled by elements.
//
//          Algorithm:
//          For each alignment_line, in id sequence 
//                    line size (the distance from preceding height) = the max of, for each element ending by that line, 
//                            the size (height or width) of the element minus the size of any preceding alignment lines that element spans.
//
//          Current limitations:
//              Elements must be absolutely positioned for setting of top and left to take effect.
//              The arrays of alignment line specifications must be sorted by their identifying number.
//              The functions modify the alignment lines and element specifications passed-in.  Non-reentrant.
//
//          Author: Andrew Layman, 2009-02-10.  Updated slightly 2010-01-22.
//
//          New BSD License:
//
//          Copyright (c) 2009, Andrew Layman
//          All rights reserved.
//
//          Redistribution and use in source and binary forms, with or without
//          modification, are permitted provided that the following conditions are met:
//              * Redistributions of source code must retain the above copyright
//                  notice, this list of conditions and the following disclaimer.
//              * Redistributions in binary form must reproduce the above copyright
//                  notice, this list of conditions and the following disclaimer in the
//                  documentation and/or other materials provided with the distribution.
//              * Neither the name Andrew Layman nor the
//                  name of Microsft may be used to endorse or promote products
//                  derived from this software without specific prior written permission.
//
//          THIS SOFTWARE IS PROVIDED BY Andrew Layman ''AS IS'' AND ANY
//          EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//          WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//          DISCLAIMED. IN NO EVENT SHALL Andrew Layman BE LIABLE FOR ANY
//          DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
//          (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//          LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
//          ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
//          (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//          SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


var function_adder = {
            function_chain_maker: function (first,second) { // produce a function that will invoke first and then second.
                        return function() {
                                      if (typeof first === "function") {
                                          first();
                                      }
                                      if (typeof second === "function") {
                                          second();
                                      }
                                      return;
                          };
            }
};

var layout_helper = {
    place_per_lines: function (document,
                            alignment_lines,        // sequence of alignment lines and the min and max size of the distance from the preceding alignment line 
                            placement,              // collection of element ids to place and their position
                            get_pos,                // function(placement[i]) to get a {start,end} object value from placement[i] 
                            get_element_size,       // function(x) to return the size of element x
                            set_element_size,       // function(x) to set the size of element x
                            set_element_start       // function(x) to set the start of element x
                            ) {
        var i, j, k, e, h, start, p, gpp, max_size;

        // TO DO: Sort the placement elements by ending position.

        alignment_lines[0].height = 0;                  // the first line has no distance from a preceding one.
        for (i = 1; i < alignment_lines.length; i++) {        //alignment lines, except the first one
            max_size = alignment_lines[i].min;
            for (j = 0; j < placement.length; j++) {          // for each placement of an element ...
                p = placement[j];
                gpp = get_pos(p);
                if (gpp.end === alignment_lines[i].id) {   	// placement ends on this line
                    e = document.getElementById(p.id);
                    if (e) {										// ignore errors caused by mismatched names.
                        h = get_element_size(e);
                        for (k = i - 1; k > 0 && gpp.start < alignment_lines[k].id; k--) { // lines spanned by placement
                            h = Math.max(0, h - alignment_lines[k].size);
                        }
                        max_size = Math.max(max_size, h);
                    }
                }
            }
            alignment_lines[i].size = Math.min(max_size/*.value()*/, alignment_lines[i].max);
        }

        // For each element to be placed, compute its target start position as the sum of the sizes of the lines that precede it,
        //  and size as the sum of the heights of the alignment lines it 
        //  spans or ends on.  That is, the sum of the heights of all alignment lines where its id is
        //  > placement[j].row_pos.start and <= placement[j].row_pos.end .

        // TO DO: Consider whether to speed this up, say by having each alignment hold a colletion of affected placements or visa versa.

        for (j = 0; j < placement.length; j++) {          // for each placement of an element ...
            p = placement[j];
            gpp = get_pos(p);
            h = 0;
            start = 0;
            for (i = 1; i < alignment_lines.length && alignment_lines[i].id <= gpp.end; i++) {        //alignment lines, except the first one 
                if (gpp.start < alignment_lines[i].id) {
                    h = h + alignment_lines[i].size;
                } else {
                    start = start + alignment_lines[i].size;
                }
            }
            get_pos(p).start = start;
            get_pos(p).size = h;
        }

        // For each placement of an element, set the element's size and start
        for (j = 0; j < placement.length; j++) {
            p = placement[j];
            gpp = get_pos(p);
            e = document.getElementById(p.id);
            if (e) {
                set_element_start(e, gpp.start);
                set_element_size(e, gpp.size);
            }
        }

        return;
    },


    place_elements: function (document, h_lines, v_lines, placement) {
        // Do width before height since setting width can affect height.
        layout_helper.place_per_lines(
                        document,
                        h_lines,
                        placement,
                        function (x) { return x.row_pos; },
                        layout_helper.getHeight,
                        layout_helper.setHeight,
                        layout_helper.setTop
                    );

        layout_helper.place_per_lines(
                        document,
                        v_lines,
                        placement,
                        function (x) { return x.col_pos; },
                        layout_helper.getWidth,
                        layout_helper.setWidth,
                        layout_helper.setLeft
                    );

    },


    // Get or set heights, widths and starting top and left of elements.    
    // TO DO: take padding and margin into account.

    getHeight: function (e) {
        if (e.offsetHeight) {
            return e.offsetHeight;
        }
        else if (e.style.pixelHeight) {
            return e.style.pixelHeight;
        }
        else {
            return 0;
        }
    },

    setHeight: function (e, height) {
        var eh;
        eh = layout_helper.getHeight(e);
        if (eh !== height) {
            e.style.pixelHeight = height;
            eh = layout_helper.getHeight(e);    // Verify that it took and adjust if off.
            if (eh !== height) {
                e.style.pixelHeight = height + (height - eh);
            }
        }
        return;
    },

    setTop: function (e, top) {
        e.style.pixelTop = top;
    },

    getWidth: function (e) {
        if (e.offsetHeight) {
            return e.offsetWidth;
        }
        else if (e.style.pixelWidth) {
            return e.style.pixelWidth;
        }
        else {
            return 0;
        }
    },

    setWidth: function (e, width) {
        var ew;
        ew = layout_helper.getWidth(e);
        if (ew !== width) {
            e.style.pixelWidth = width;
            ew = layout_helper.getWidth(e);
            if (ew !== width) {
                e.style.pixelWidth = width + (width - ew);
            }
        }
        return e;
    },

    setLeft: function (e, left) {
        e.style.pixelLeft = left;
    }

};

// Add laying out the elements to the end of the list of things that need to be done on load

window.onload = function_adder.function_chain_maker(
        window.onload,
        function () {
            if (document.getElementById) {
                layout_helper.place_elements(document, h_lines, v_lines, element_places);
            }
        }
    );


