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

Purpose- 
This file contains the implementation of the Responsive Framework which contains all information to construct UI and handle operations.
*/

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, maxerr: 50, continue: true */
/*global GridModel, UIModel, LayoutOps, DebugUtils, DwResponsiveLayoutConstants, ResponsiveFrameworkData, DwJSUtility, docWindowObj, ResponsiveLayoutConstants, DWConstants */

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

var ResponsiveFramework = (
    function (GridModel, UIModel, LayoutOps) {
        "use strict";
        
        // define private variables here.
        var _frameworkVersion = -1,
            _uiModel = null,
            _gridModel = null,
            _layoutOps = null;

        /*
            function:ResponsiveFrameworkImpl() - Abstract Class
            Arguments: frameworkVersion of the responsive document.
            Return: object.
        */
        var ResponsiveFrameworkImpl = function (frameworkVersionNum) {
            _frameworkVersion = frameworkVersionNum;
            this.initializeConstants(); // let this be first method called
            this.createGridModel();
            this.createUIModel();
            // create LayoutOps after the Grid & UI Models are created.
            this.createLayoutOps();
        };

        ResponsiveFrameworkImpl.prototype.getAllClasses = function () {
            return _gridModel.getAllClasses();
        };

        ResponsiveFrameworkImpl.prototype.getRowClass = function () {
            return _gridModel.getRowClass();
        };
        ResponsiveFrameworkImpl.prototype.getContainerClass = function () {
            return _gridModel.getContainerClass();
        };
        ResponsiveFrameworkImpl.prototype.getFluidContainerClass = function () {
            return _gridModel.getFluidContainerClass();
        };
        
        ResponsiveFrameworkImpl.prototype.getHideClass = function () {
            return _layoutOps.getHideClass();
        };
        /*
            function:rediscoverElements() - Rediscover elements and Populate the UI Model again.
            Arguments:
            Return:
        */
        ResponsiveFrameworkImpl.prototype.rediscoverElements = function (inUnhideMode) {
            this.populateUIModel(inUnhideMode);
        };
        
        /*
            function:hideElement() - Hide the given Element
            Arguments:
            Return:
        */
        ResponsiveFrameworkImpl.prototype.hideElement = function (domElement) {
            if (domElement) {
                _layoutOps.hideElement(domElement);
            }
        };

        /*
            function:unhideElement() - Unhide the given Element
            Arguments:
            Return:
        */
        ResponsiveFrameworkImpl.prototype.unhideElement = function (domElement) {
            if (domElement) {
                _layoutOps.unhideElement(domElement);
            }
        };

        /*
            function:resizeTo() - Resize the passed element to the number of columns specified.
            Arguments: Current DOM Element being edited, new columns size, commit to Dw
            Return:
        */
        ResponsiveFrameworkImpl.prototype.resizeTo = function (domElement, newColumn, commit) {
            _layoutOps.resizeTo(domElement, newColumn, commit);
        };
        
        /*
            function:shiftTo() - Resize the offset of passed element to the new column specified.
            Arguments: Current DOM Element being edited, new column indicating offset position, commit to Dw
            Return:
        */
        ResponsiveFrameworkImpl.prototype.shiftTo = function (domElement, newColumn, commit) {
            _layoutOps.shiftTo(domElement, newColumn, commit);
        };
        
        /*
            function: getAllInfoForResponsiveInsert - required while Inserting
            Arguments: none
            Return: All the class names to be applied, read from the xml file.
        */
        ResponsiveFrameworkImpl.prototype.getAllInfoForResponsiveInsert = function () {
            var viewport = _gridModel.getIndexOfActiveViewport(),
                columnRegex = ResponsiveFrameworkData.frameworkXML.getElementsByTagName(ResponsiveLayoutConstants.DESCRIPTORS.RESIZE_CLASS_REGEX),
                columnclassToBeApplied = columnRegex[viewport].textContent,
                rowClass = this.getRowClass(),
                maxNumOfCols = parseInt(_gridModel.getNumOfColumns(), 10);
            
            return { columnClass: columnclassToBeApplied,
                    rowClass: rowClass,
                    numOfCols: maxNumOfCols
                   };
        };
		
		/*
            function: getGridSizesWithPrefix - 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
        */
        ResponsiveFrameworkImpl.prototype.getGridSizesWithPrefix = function () {
			return _gridModel.getGridClassPrefixesWithSizeRange();
        };

        /*
            function: addNewRow() - Add new row adjacent to the current selection
            Arguments: Current DOM Element
            Return:
        */
        ResponsiveFrameworkImpl.prototype.addNewRow = function (domElement) {
            _layoutOps.addNewRow(domElement);
        };
        
        /*
            function: addNewColumn() - Add new column adjacent to the current selection
            Arguments: Current DOM Element
            Return:
        */
        ResponsiveFrameworkImpl.prototype.addNewColumn = function (domElement) {
            _layoutOps.addNewColumn(domElement);
        };
        /*
            function:initializeConstants(): Initalize all the required constants to the object.
            Arguments: none
            Return:
        */
        ResponsiveFrameworkImpl.prototype.initializeConstants = function () {
            this.blockElemRegex = new RegExp("(^div$|^ul$|^ol$|^li$|^header$|^hgroup$|^nav$|^aside$|^article$|^section$|^footer$|^h1$|^h2$|^h3$|^h4$|^h5$|^h6$|^p$|^figure$|^address$|^audio$|^blockquote$|^canvas$|^dd$|^dl$|^fieldset$|^figcaption$|^form$|^output$|^pre$|^video$)", "gi"); //list picked from: https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements
        };

        ResponsiveFrameworkImpl.prototype.clearHiddenElementChanges = function () {
            var hiddenElems = docWindowObj.document.querySelectorAll("[data-isHidden]"),
                i = 0,
                styleVal = "";
            for (i = 0; i < hiddenElems.length; i++) {
                hiddenElems[i].removeAttribute("data-isHidden");
                styleVal = hiddenElems[i].getAttribute("data-styleVal");
                hiddenElems[i].removeAttribute("data-styleVal");
                if (styleVal !== null) {
                    hiddenElems[i].style.cssText = styleVal;
                }
            }
        };

        /*
            function:populateUIModel(): populates the UI Model
            Arguments: none
            Return:
        */
        ResponsiveFrameworkImpl.prototype.populateUIModel = function (inUnhideMode) {
            
            this.clearHiddenElementChanges();
            
            var uiModelData = {containerArr: [], rowArr: [], columnArr: [], hiddenArr: []},
                i = 0;

            uiModelData.containerArr = this.identifyContainerElements();
            // body will always be the last element in the list of containers
            // all other fluid elements need to be directly identified within the body since
            // it is not necessary to have fluid elements within a container
            
            if (uiModelData.containerArr && uiModelData.containerArr.length) {
                var rowArr = this.identifyRows(uiModelData.containerArr[uiModelData.containerArr.length - 1]);
                if (rowArr && rowArr.length) {
                    uiModelData.rowArr = uiModelData.rowArr.concat(rowArr);
                }
                var columnArr = this.identifyColumns(uiModelData.containerArr[uiModelData.containerArr.length - 1]);
                if (columnArr && columnArr.length) {
                    uiModelData.columnArr = uiModelData.columnArr.concat(columnArr);
                }
            }

            //lastly identify the hidden elements since they apply new css to elements which will cause wrong identification of columns or rows
            uiModelData.hiddenArr = this.identifyHiddenElements(inUnhideMode);
            
            _uiModel.setContainers(uiModelData.containerArr);
            _uiModel.setRows(uiModelData.rowArr);
            _uiModel.setColumns(uiModelData.columnArr);
            _uiModel.setHiddenElements(uiModelData.hiddenArr);

            _uiModel.injectDesignTimeCSS();
        };
        
        /*
            function:createLayoutOps(): constructs and populates the Layout operations map. This should be called after the GridModel is created.
            Arguments: none
            Return:
        */
        ResponsiveFrameworkImpl.prototype.createLayoutOps = function () {
            // this should be called after the GridModel is created.
            _layoutOps = new LayoutOps(_gridModel);
        };
        
        /*
            function:createUIModel(): constructs and populates the UI Model
            Arguments: none
            Return:
        */
        ResponsiveFrameworkImpl.prototype.createUIModel = function () {
            _uiModel = new UIModel();
            // populate it when required
        };
        
        /*
            function:createGridModel(): constructs and populates the Grid Model
            Arguments: none
            Return:
        */
        ResponsiveFrameworkImpl.prototype.createGridModel = function () {
            _gridModel = new GridModel();
            // this should be a one time thing. Can populate now itself.
            _gridModel.populateGridModel();
        };
        
        /*
            function:getMaxNumOfColumns(): returns the number of columns identified i nthe grid framework
            Arguments: none
            Return:
        */
        ResponsiveFrameworkImpl.prototype.getMaxNumOfColumns = function () {
            return _gridModel.getNumOfColumns();
        };
        
        /*
            function:supportsFeature() - Returns the state of support of feature available
            Arguments: featureID
            Return: Boolean.
        */
        ResponsiveFrameworkImpl.prototype.supportsFeature = function (featureID) {
            return _layoutOps.supportsFeature(featureID);
        };

        /**
        PURE VIRTUAL FUNCTIONS
        */

        /*
            function:getFrameworkType() - pure virtual function
            Arguments: none
            Return: none.
        */
        ResponsiveFrameworkImpl.prototype.getFrameworkType = function () {
            DebugUtils.log("ResponsiveFrameworkImpl::getFrameworkType()=0; pure virtual function call");
        };
        
        /*
            function:identifyContainerElements()  - pure virtual function
            Arguments: none
            Return:
        */
        ResponsiveFrameworkImpl.prototype.identifyContainerElements = function () {
            DebugUtils.log("ResponsiveFrameworkImpl::identifyContainerElements()=0; pure virtual function call");
        };

        /*
            function:identifyRows()  - pure virtual function
            Arguments: gridContainer, containerToLookwithin, iswithinColumn
            Return:
        */
        ResponsiveFrameworkImpl.prototype.identifyRows = function (parentContainer) {
            DebugUtils.log("ResponsiveFrameworkImpl::identifyRows()=0; pure virtual function call");
        };

        /*
            function:identifyColumns()  - pure virtual function
            Arguments: gridContainer, parentRow
            Return:
        */
        ResponsiveFrameworkImpl.prototype.identifyColumns = function (parentContainer) {
            DebugUtils.log("ResponsiveFrameworkImpl::identifyColumns()=0; pure virtual function call");
        };
        
        ResponsiveFrameworkImpl.prototype.isAValidColumn = function (colElem) {
            DebugUtils.log("ResponsiveFrameworkImpl::isAValidColumn()=0; pure virtual function call");
        };
        
        ResponsiveFrameworkImpl.prototype.isAValidRow = function (rowElem) {
            DebugUtils.log("ResponsiveFrameworkImpl::isAValidRow()=0; pure virtual function call");
        };
        /*
            function:getUIModel() - returns the UI Model
            Arguements:
            Return: UIModel
        */
        ResponsiveFrameworkImpl.prototype.getUIModel = function () {
            return _uiModel;
        };
        
        /*
            function:getGridModel() - returns the Grid Model
            Arguements:
            Return: GridModel
        */
        ResponsiveFrameworkImpl.prototype.getGridModel = function () {
            return _gridModel;
        };

        /*
            function:constructGridForActiveElement() - construct the grid for the current element
            Arguments: Current DOM Element being edited
            Return: currentGrid: {
                        left: -1,
                        right: -1,
                        width: -1,
                        numCols: -1,
                        columns: [], // [leftOfCol, ...]
                        columnWidth: [] // [1 column width, 2 columnWidth]
                    }
        */
        ResponsiveFrameworkImpl.prototype.constructGridForActiveElement = function (domElement) {
            return _gridModel.constructGridForActiveElement(domElement);
        };

        /*
            function: canHideElement - required for context menu. Verifies if the element is contained within a hidden element or not
            Arguments: element to be checked
            Return: bool
        */
        ResponsiveFrameworkImpl.prototype.canHideElement = function (domElement) {
            if (_layoutOps) {
                return _layoutOps.canHideElement(domElement);
            }
            return false;
        };

        /*
            function: isElementHidden - required for context menu. Verifies if the element is hidden
            Arguments: element to be checked
            Return: bool
        */
        ResponsiveFrameworkImpl.prototype.isElementHidden = function (domElement) {
            if (_layoutOps) {
                return _layoutOps.isElementHidden(domElement);
            }
            return false;
        };
        
        return ResponsiveFrameworkImpl;
    }(GridModel, UIModel, LayoutOps)
);

var BootstrapFramework = (
    function (ResponsiveFramework) {
        "use strict";
        
        // define private variables here.
        
        /*
            function:BootstrapFrameworkImpl() - Constructor
            Arguments: frameworkVersion of the responsive document.
            Return: object.
        */
        var BootstrapFrameworkImpl = function (frameworkVersionNum) {
            ResponsiveFramework.call(this, frameworkVersionNum);
        };
        
        BootstrapFrameworkImpl.prototype = Object.create(ResponsiveFramework.prototype);
        BootstrapFrameworkImpl.prototype.constructor = BootstrapFrameworkImpl;
        
        /*
            function:getFrameworkType()
            Arguments: none
            Return: frameworkType.
        */
        BootstrapFrameworkImpl.prototype.getFrameworkType = function () {
            return DwResponsiveLayoutConstants.FRAMEWORK.BOOTSTRAP;
        };

        /*
            function:identifyContainerElements()
            Arguments: none
            Return: constructs and populates the UI Model
        */
        BootstrapFrameworkImpl.prototype.identifyContainerElements = function () {
            var containerClass = this.getContainerClass(),
                fluidContainerClass = this.getFluidContainerClass(),
                containerArr = [],
                i = 0,
                containerElems,
                curDocument = docWindowObj.document,
                createContainerElem = function (domElement, fluidContainer) {
                    containerArr.push({
                        domElement: domElement,
                        containerElement: true,
                        fluidContainer: fluidContainer
                    });
                };

            if (containerClass) {
                containerElems = curDocument.getElementsByClassName(containerClass);
                if (containerElems.length > 0) {
                    for (i = 0; i < containerElems.length; i++) {
                        if (!containerElems[i].hasAttribute(DWConstants.DWUniqueId)) {
                            continue;
                        }
                        createContainerElem(containerElems[i], false);
                    }
                }
            }
            if (fluidContainerClass) {
                containerElems = curDocument.getElementsByClassName(fluidContainerClass);
                if (containerElems.length > 0) {
                    for (i = 0; i < containerElems.length; i++) {
                        if (!containerElems[i].hasAttribute(DWConstants.DWUniqueId)) {
                            continue;
                        }
                        createContainerElem(containerElems[i], true);
                    }
                }
            }
            
            // body is always a valid container.
            // by default assume fluid container, since body would span completely
            createContainerElem(curDocument.body, true);
            
            return containerArr;
        };

        /*
            function:isAValidRow() - verifies if the incoming element is a valid row
            Arguments: element to be evaluated
            Return: boolean - true if valid a row element else false
        */
        BootstrapFrameworkImpl.prototype.isAValidRow = function (rowElem) {
            if (!rowElem || !rowElem.classList) {
                return false;
            }
            var rowClass = this.getRowClass();
            var visbileProp = DwJSUtility.getComputedCssPropValFromElem(rowElem, "display", "string", true);
            if (rowElem.classList.contains(rowClass)
                    && rowElem.hasAttribute(DWConstants.DWUniqueId)
                    && visbileProp !== "none"
                    && rowElem.offsetHeight > 0) {
                return true;
            }
            return false;

        };
        /*
            function:identifyRows()
            Arguments: parentContainer in which rows are to be found
            Return: populates the array containing all the columns
        */
        BootstrapFrameworkImpl.prototype.identifyRows = function (parentContainer) {
            var rowClass = this.getRowClass(),
                rowElems,
                rowArr = [],
                i = 0;
            if (rowClass) {
                rowElems = parentContainer.domElement.getElementsByClassName(rowClass);
                if (rowElems.length > 0) {
                    for (i = 0; i < rowElems.length; i++) {
                        if (this.isAValidRow(rowElems[i])) {
                            rowArr.push({
                                domElement: rowElems[i],
                                rowElement: true
                            });
                        }
                    }
                }
            }
            return rowArr;
        };

        /*
            function:isAValidColumn() - verifies if the incoming element is a valid column
            Arguments: element to be evaluated
            Return: boolean - true if valid a row element else false
        */
        BootstrapFrameworkImpl.prototype.isAValidColumn = function (colElem) {
            if (!colElem || !colElem.classList || !colElem.parentNode) {
                return false;
            }

            var allClasses = this.getAllClasses(),
                rowClass = this.getRowClass(),
                containerClass = this.getContainerClass(),
                fluidContainerClass = this.getFluidContainerClass(),
                i = 0;
            
            // should be a block level element
            if (!colElem.tagName.match(this.blockElemRegex)) {
                return false;
            }

            if (!colElem.hasAttribute(DWConstants.DWUniqueId)) {
                return false;
            }

            if (fluidContainerClass) {
                if (colElem.classList.contains(fluidContainerClass)) {
                    // this element should not be a fluid container
                    return false;
                }
            }
            if (containerClass) {
                if (colElem.classList.contains(containerClass)) {
                    // this element should not be a container
                    return false;
                }
            }

            if (colElem.classList.contains(rowClass)) {
                // this element should not be a row
                return false;
            }

            // Parent of a column should be a block level element before checking for width
            if (!colElem.parentNode.tagName.match(this.blockElemRegex) && colElem.parentNode.tagName.toLowerCase().indexOf("body") !== 0) {
                return false;
            }
            
            // check if the element is visible
            var visbileProp = DwJSUtility.getComputedCssPropValFromElem(colElem, "display", "string", true);
            if (visbileProp === "none") {
                return false;
            }

            // if a block level element and is of 100% width - it would be a supported element
            var widthProp = DwJSUtility.getComputedCssPropValFromElem(colElem, "width", "int", true);
            var widthPropParent = DwJSUtility.getComputedCssPropValFromElem(colElem.parentNode, "width", "int", true);
            if (widthProp === widthPropParent && widthProp !== 0) {
                return true;
            }

            if (colElem.offsetHeight === 0) {
                return false;
            }
            
            // else check for framework classes that could have been applied
            for (i = 0; i < allClasses.length; i++) {
                if (colElem.classList.contains(allClasses[i])) {
                    return true;
                }
            }
            return false;
        };
        /*
            function:identifyColumns()
            Arguments: parentContainer in which columns are to be found
            Return: populates the array containing all the columns
        */
        BootstrapFrameworkImpl.prototype.identifyColumns = function (parentContainer) {
            var columnArr = [],
                i = 0,
                allClasses = [],
                colElems,
                pushIdentifiedColumn = function (domElement, parentContainer) {
                    columnArr.push({
                        domElement: domElement,
                        columnElement: true
                    });
                };

            colElems = parentContainer.domElement.getElementsByTagName("*");

            for (i = 0; i < colElems.length; i++) {
                if (colElems[i]) {
                    if (this.isAValidColumn(colElems[i])) {
                        pushIdentifiedColumn(colElems[i], parentContainer);
                    }
                }
            }
            return columnArr;
        };

        /*
            function:identifyHiddenElements()
            Arguments: none
            Return: populates the array containing all the hidden elements
        */
        BootstrapFrameworkImpl.prototype.identifyHiddenElements = function (inUnhideMode) {
            var className = this.getHideClass(),
                hiddenElems = docWindowObj.document.getElementsByClassName(className),
                hiddenArr = [],
                i = 0,
                pushHiddenElement = function (domElement) {
                    hiddenArr.push({
                        domElement: domElement,
                        hiddenElement: true
                    });
                };
            
            for (i = 0; i < hiddenElems.length; i++) {
                var visbileProp = DwJSUtility.getComputedCssPropValFromElem(hiddenElems[i], "display", "string", true);
                if (visbileProp !== "none") {
                    continue;
                }
                if (inUnhideMode) {
                    hiddenElems[i].setAttribute("data-isHidden", "true");
                    hiddenElems[i].setAttribute("data-styleVal", hiddenElems[i].style.cssText);
                    var displayProp = hiddenElems[i].tagName.match(this.blockElemRegex) ? "block" : "initial";
                    hiddenElems[i].style.setProperty("display", displayProp, "important");
                }
                pushHiddenElement(hiddenElems[i]);
            }
            return hiddenArr;
        };

        return BootstrapFrameworkImpl;
    }(ResponsiveFramework)
);