/*	
 Copyright 2015 Adobe Systems Incorporated.  All rights reserved. 

Purpose- 
This file contains the model required to render the grid columns.
*/

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, maxerr: 50, continue: true, regexp: true */
/*global DebugUtils, unloadResponsiveLayoutExtension, docWindowObj, ResponsiveFrameworkData, ResponsiveLayoutConstants, DwJSUtility*/

/* AdobePatentID="P5361-US". */

var GridModel = (
    function () {
        "use strict";
        
        // define private variables here.
        var _numOfColumns = -1,
            _mediaQueryBreakPoints = [],
            _numOfBreakPoints = -1,
            _allClasses = [],
            _columnClasses = [],
            _offsetClasses  = [],
            _columnPerc = [], // columnPerc = [ [1st region classes with perc], [2nd region classes with perc] .... ]
            _offsetPerc = [], // _offsetPerc = [ [1st region classes with perc], [2nd region classes with perc] .... ]
            _rowClass = null,
            _containerClass = null,
            _fluidContainerClass = null,
			_gridClassSizePrefixes = null; // _gridClassSizePrefixes = [xs-0-767;sm-768-991;...]

        var constructRowClass = function () {
            _rowClass = ResponsiveFrameworkData.frameworkXML.getElementsByTagName(ResponsiveLayoutConstants.DESCRIPTORS.ROW_CLASS)[0].textContent;
            return _rowClass;
        };
        
        var constructContainerClass = function () {
            var containerClassElem = ResponsiveFrameworkData.frameworkXML.getElementsByTagName(ResponsiveLayoutConstants.DESCRIPTORS.GRID_CONTAINER_CLASS);
            if (containerClassElem.length > 0) {
                _containerClass = containerClassElem[0].textContent;
            } else {
                _containerClass = "";
            }
            return _containerClass;
        };
        
        var constructFluidContainerClass = function () {
            var fluidContainerClassElem = ResponsiveFrameworkData.frameworkXML.getElementsByTagName(ResponsiveLayoutConstants.DESCRIPTORS.GRID_FLUID_CONTAINER_CLASS);
            if (fluidContainerClassElem.length > 0) {
                _fluidContainerClass = fluidContainerClassElem[0].textContent;
            } else {
                _fluidContainerClass = "";
            }
            return _fluidContainerClass;
        };
        /*
            function:GridModelImpl() - Constructor
            Arguments: none
            Return: object.
        */
        var GridModelImpl = function () {
            /**
                Assert the validity of the XML for the Grid Model
                Mobile First Frameworks are being supported.
                No. of breakpoints should be equal to the number of media queries identified
            */
        };

        /*
            function:getNumOfColumns() - returns the stored num of columns in the grid structure
            Arguments: none.
            Return: number; number of columns.
        */
        GridModelImpl.prototype.getNumOfColumns = function () {
            return _numOfColumns;
        };
        
        /*
            function:getAllClasses() - returns the array of classes that are used by this responsive framework
            Arguments: none.
            Return: Array[] of all classes
        */
        GridModelImpl.prototype.getAllClasses = function () {
            if (_allClasses.length === 0) {
                this.constructAllClasses();
            }
            return _allClasses;
        };

        /*
            function:getRowClass() - returns the row class applicable for the framework
            Arguments: none.
            Return: string: row class
        */
        GridModelImpl.prototype.getRowClass = function () {
            if (_rowClass === null) {
                _rowClass = constructRowClass();
            }
            return _rowClass;
        };
        /*
            function:getContainerClass() - returns the container class applicable for the framework
            Arguments: none.
            Return: string: container class
        */
        GridModelImpl.prototype.getContainerClass = function () {
            if (_containerClass === null) {
                _containerClass = constructContainerClass();
            }
            return _containerClass;
        };
        /*
            function:getFluidContainerClass() - returns the fluid container class applicable for the framework
            Arguments: none.
            Return: string: fluid container class
        */
        GridModelImpl.prototype.getFluidContainerClass = function () {
            if (_fluidContainerClass === null) {
                _fluidContainerClass = constructFluidContainerClass();
            }
            return _fluidContainerClass;
        };
        /*
            function:getColumnClasses() - returns the array of classes that are used to form columns in the responsive framework
            Arguments: none.
            Return: Array[][] of all Column classes
        */
        GridModelImpl.prototype.getColumnClasses = function () {
            if (_columnClasses.length === 0) {
                this.constructAllClasses();
            }
            return _columnClasses;
        };
        
        /*
            function:getOffsetPerc() - returns the array of offset classes along with the margin percentage
            Arguements: none
            Return: Array [][] of offset classes along wwith the margin percentage
        */
        GridModelImpl.prototype.getOffsetPerc = function () {
            return _offsetPerc;
        };
        
        /*
            function:getColumnClassesForViewport() - returns the array of classes that are used to form columns in the responsive framework for the viewport
            Arguments: index of media query interested in
            Return: Array[] of all Column classes
        */
        GridModelImpl.prototype.getColumnClassesForViewport = function (index) {
            if (_columnPerc.length === 0) {
                this.constructAllClasses();
            }
            if (index < _columnPerc.length) {
                return _columnPerc[index];
            }
            return [];
        };
        
        /*
            function:getOffsetClassesForViewport() - returns the array of classes that are used to form offset percentage for column indexes in the responsive framework for the viewport
            Arguements: index of media query interested in
            Return: Array[] of all offset percentage classes
        */
        GridModelImpl.prototype.getOffsetClassesForViewport = function (index) {
            if (_offsetPerc.length === 0) {
                this.constructAllClasses();
            }
            if (index < _offsetPerc.length) {
                return _offsetPerc[index];
            }
            return [];
        };

        /*
            function:setNumOfColumns() - stores the num of columns defined in the frameowrk css
            Arguments: numOfColumns.
            Return: none.
        */
        GridModelImpl.prototype.setNumOfColumns = function (numOfColumns) {
            _numOfColumns = numOfColumns;
        };

        /*
            function:getColClassesForRowInsertion() - New columns are to be 50% for the given viewport
            Arguments:
            Return: string: class names separated by space
        */
        GridModelImpl.prototype.getColClassesForRowInsertion = function () {
            var activeViewport = this.getIndexOfActiveViewport(),
                numOfColumns = this.getNumOfColumns();
            return _columnPerc[activeViewport][Math.floor(numOfColumns / 2) - 1]["class"];
        };

        /*
            function:getNumOfBreakpoints() - returns the stored num of breakpoints.
            Arguments: none.
            Return: number; num of breakpoints
        */
        GridModelImpl.prototype.getNumOfBreakpoints = function () {
            return _numOfBreakPoints;
        };

        /*
            function:getIndexOfActiveViewport() - Using the viewport size the affecting media query is identified
            Arguments: none.
            Return: returns the index of the media query (in mobile first fashion) that is currently affecting the viewport
        */
        GridModelImpl.prototype.getIndexOfActiveViewport = function () {
            /* Identify the current viewport size and idenitfy the range of media query it falls into*/
            var screenWidth = parseInt(docWindowObj.innerWidth, 10),
                i = -1;
            while (i++ < _mediaQueryBreakPoints.length) {
                if (screenWidth < _mediaQueryBreakPoints[i] || i === _mediaQueryBreakPoints.length) {
                    break;
                }
            }

            return i;
        };

        /*
            function:setBreakpoints() - Stores the information related to the media queries in the frameowrk, the breakpoints and their values
            Arguments: num of breakpoints, the array that holds the value of those breakpoints
            Return: none.
        */
        GridModelImpl.prototype.setBreakpoints = function (numOfBreakPoints, arrMediaQueryBreakPoints) {
            if (numOfBreakPoints > 0 && arrMediaQueryBreakPoints.length > 0 && arrMediaQueryBreakPoints.length >= numOfBreakPoints) {
                _numOfBreakPoints = numOfBreakPoints;
                // there might be more breakpoints but we are not interested in them
                // since these are mobile first frameworks we will take the first two
                _mediaQueryBreakPoints = arrMediaQueryBreakPoints.slice(0, numOfBreakPoints);
            }
        };

        /*
            function:calculateNumOfColumns() - figures out the number of columns from the framework css
            Arguments: none
            Return: none.
        */
        GridModelImpl.prototype.calculateNumOfColumns = function () {
            var columnRegex = ResponsiveFrameworkData.frameworkXML.getElementsByTagName(ResponsiveLayoutConstants.DESCRIPTORS.RESIZE_CLASS_REGEX)[0].textContent;
            columnRegex = columnRegex.split(" ")[0];
            if (columnRegex) {
                // construct the regex
                var searchRegex = new RegExp(columnRegex, "g"),
                    matches = ResponsiveFrameworkData.frameworkCSS.match(searchRegex),
                    uniqueMatch = matches.filter(DwJSUtility.filterUnique),
                    extractedNums = uniqueMatch.join(" ").match(/\d+/g).sort(DwJSUtility.sortByNumber);
                return parseInt(extractedNums[extractedNums.length - 1], 10);
            }
        };

        /*
            function:extractNumOfBreakpoints(): Fetch the Num OF breakpoints in the framework
            Arguments: none
            Return: Number
        */
        GridModelImpl.prototype.extractNumOfBreakpoints = function () {
            var numBreakpoints = ResponsiveFrameworkData.frameworkXML.getElementsByTagName(ResponsiveLayoutConstants.DESCRIPTORS.NUM_BREAKPOINTS)[0].textContent;
            return parseInt(numBreakpoints, 10);
        };

        /*
            function:extractBreakpoints() - figures out the breakpoint values
            Arguments: none
            Return: none.
        */
        GridModelImpl.prototype.extractBreakpoints = function () {
            var mediaQueryRegex = ResponsiveFrameworkData.frameworkXML.getElementsByTagName(ResponsiveLayoutConstants.DESCRIPTORS.MEDIA_QUERY_REGEX)[0].textContent;
            if (mediaQueryRegex) {
                // construct the regex
                var searchRegex = new RegExp(mediaQueryRegex, "g"),
                    matches = ResponsiveFrameworkData.frameworkCSS.match(searchRegex),
                    uniqueMatch = matches.filter(DwJSUtility.filterUnique),
                    extractedNums = uniqueMatch.join(" ").match(/\d+\.?\d*/g).sort(DwJSUtility.sortByNumber),
                    i = 0;
                while (i++ < extractedNums.length) {
                    extractedNums[i - 1] = parseFloat(extractedNums[i - 1], 10);
                }
                return extractedNums;
            }
            return [];
        };
		
		GridModelImpl.prototype.extractGridClassPrefixes = function() {
			var gridSizePrefixes = ResponsiveFrameworkData.frameworkXML.getElementsByTagName(ResponsiveLayoutConstants.DESCRIPTORS.GRIDCLASS_SIZES_PREFIX)[0].textContent;
			return gridSizePrefixes;
		};
		
		/*
            function: getGridClassPrefixesWithSizeRange - For adding menu items to Scrubber context menu and Window Size menu for Bootstrap pages
            Arguments: none
            Returns: Bootstrap breakpoints value with the size prefix as a string eg. xs-0-767;sm-768-991
        */
		GridModelImpl.prototype.getGridClassPrefixesWithSizeRange = function() {
			if(_gridClassSizePrefixes === null) {
				var numBreakpoints = this.extractNumOfBreakpoints();
				var breakpoints = this.extractBreakpoints().slice(0, numBreakpoints);
				var gridSizePrefixes = this.extractGridClassPrefixes().split(",", numBreakpoints + 1);
				var i;
				var startWidthInRange = 0;
					
				_gridClassSizePrefixes = "";
				var startWidthInRange = 0;
				for(i = 0; i < numBreakpoints; ++i) {
					_gridClassSizePrefixes = _gridClassSizePrefixes + gridSizePrefixes[i] + "-" + startWidthInRange + "-" + (breakpoints[i] - 1) + ";";
					startWidthInRange = breakpoints[i];
				}
				_gridClassSizePrefixes = _gridClassSizePrefixes + gridSizePrefixes[i] + "-" + startWidthInRange + "-" + 0; //the end range in the last entry will be 0 to denote full size or max size
			}
			return _gridClassSizePrefixes;
		};

        /*
            function:populateGridModel(): populates the Grid Model
            Arguments: none
            Return:
        */
        GridModelImpl.prototype.populateGridModel = function () {
            var numColumns = this.calculateNumOfColumns();
            this.setNumOfColumns(numColumns);
            var numBreakPoints = this.extractNumOfBreakpoints();
            var arrBreakPoints = this.extractBreakpoints();
            this.setBreakpoints(numBreakPoints, arrBreakPoints);
            this.constructAllClasses();
        };

        GridModelImpl.prototype.constructWidthClassesAndGridPerc = function () {
            var columnRegex = ResponsiveFrameworkData.frameworkXML.getElementsByTagName(ResponsiveLayoutConstants.DESCRIPTORS.RESIZE_CLASS_REGEX),
                self = this,
                i,
                j,
                classesGenerated = [],
                allClasses = [],
                generateClassNames = function (regex) {
                    var numOfColumns = self.getNumOfColumns(),
                        classes = [];
                    for (j = 0; j < numOfColumns; j++) {
                        var className = regex.replace('[\\d]+', j + 1);
                        classes.push(className);
                        // form regex to find percentage in the class.
                        var widthRegex = className + '\\s*\\{[\\s\\w:;\\d.-]*width\\s*:\\s*(\\d*.?\\d*)';
                        widthRegex = new RegExp(widthRegex, "g");
                        var matches = ResponsiveFrameworkData.frameworkCSS.match(widthRegex);
                        if (matches && matches.length > 0) {
                            var widthProp = matches[0].match(/\s*width\s*:\s*(\d*.?\d*$)/g);
                            var perc = parseFloat(widthProp[0].match(/(\d*.?\d*$)/g), 10);
                            _columnPerc[i][j] = {"class": className, width: perc};
                        }
                    }
                    return classes;
                };
            
            for (i = 0; i < columnRegex.length; i++) {
                _columnPerc[i] = [];
                classesGenerated = generateClassNames(columnRegex[i].textContent);
                classesGenerated = classesGenerated.join(" ").split(" ");
                allClasses = allClasses.concat(classesGenerated);
            }
            allClasses = allClasses.filter(DwJSUtility.filterUnique);
            return allClasses;
        };

        GridModelImpl.prototype.constructOffsetClasses = function () {
            var columnOffsetRegex = ResponsiveFrameworkData.frameworkXML.getElementsByTagName(ResponsiveLayoutConstants.DESCRIPTORS.OFFSET_CLASS_REGEX),
                self = this,
                i,
                j,
                classesGenerated = [],
                allClasses = [],
                generateClassNames = function (regex) {
                    var numOfColumns = self.getNumOfColumns(),
                        classes = [];
                    for (j = 0; j < numOfColumns; j++) {
                        var className = regex.replace('[\\d]+', j + 1);
                        classes.push(className);
                        // form regex to find percentage in the class.
                        var widthRegex = className + '\\s*\\{[\\s\\w:;\\d.-]*margin-left\\s*:\\s*(\\d*.?\\d*)';
                        widthRegex = new RegExp(widthRegex, "g");
                        var matches = ResponsiveFrameworkData.frameworkCSS.match(widthRegex);
                        if (matches && matches.length > 0) {
                            var marginProp = matches[0].match(/\s*margin-left\s*:\s*(\d*.?\d*$)/g);
                            var perc = parseFloat(marginProp[0].match(/(\d*.?\d*$)/g), 10);
                            _offsetPerc[i][j] = {"class": className, margin: perc};
                        }
                    }
                    return classes;
                };

            for (i = 0; i < columnOffsetRegex.length; i++) {
                _offsetPerc[i] = [];
                classesGenerated = generateClassNames(columnOffsetRegex[i].textContent);
                classesGenerated = classesGenerated.join(" ").split(" ");
                allClasses = allClasses.concat(classesGenerated);
            }
            allClasses = allClasses.filter(DwJSUtility.filterUnique);
            return allClasses;
        };

        GridModelImpl.prototype.constructAllClasses = function () {
            _columnClasses = _columnClasses.concat(this.constructWidthClassesAndGridPerc());
            _allClasses = _allClasses.concat(_columnClasses);
            _offsetClasses = _offsetClasses.concat(this.constructOffsetClasses());
            _allClasses = _allClasses.concat(_offsetClasses);
            _allClasses = _allClasses.filter(DwJSUtility.filterUnique);
            _rowClass = constructRowClass();
            _containerClass = constructContainerClass();
            _fluidContainerClass = constructFluidContainerClass();
        };
        
        /*
            function:constructGridForActiveElement() - construct the grid for the current element
            Arguments: Current DOM Element being edited
            Return: currentGrid: {
                        left: -1,
                        right: -1,
                        numCols: -1,
                        columns: [], // [leftOfCol, ...]
                        columnWidth: [] // [1 column width, 2 columnWidth]
                    }
        */
        GridModelImpl.prototype.constructGridForActiveElement = function (domElement) {
            var elemRect = domElement.getBoundingClientRect(),
                leftProp = elemRect.left,
                rightProp = elemRect.right,
                numOfCols = this.getNumOfColumns(),
                viewport = this.getIndexOfActiveViewport(),
                i = 0,
                paddingLeft = DwJSUtility.getComputedCssPropValFromElem(domElement, "padding-left", "int", true),
                paddingRight = DwJSUtility.getComputedCssPropValFromElem(domElement, "padding-right", "int", true),
                width = elemRect.width - paddingLeft - paddingRight;
            var currentGrid = {
                    left: leftProp,
                    right: rightProp,
                    width: width,
                    numCols: numOfCols,
                    columns: [], // [leftOfCol, ...]
                    columnWidth: [] // [1 column width, 2 columnWidth]
                };
            
            while (i < numOfCols) {
                if (i === 0) {
                    currentGrid.columns.push(leftProp);
                } else {
                    currentGrid.columns.push(leftProp + _offsetPerc[viewport][i - 1].margin * width / 100);
                }
                currentGrid.columnWidth.push(width * _columnPerc[viewport][i].width / 100);
                i++;
            }
            return currentGrid;
        };

        return GridModelImpl;
    }()
);