var Preferences = {
    // Units = px.
    sidebarResizerWidth: 3,
};

// Sets up the BookmarksView page and acts as an interface with to JS
// controller.
var BookmarksView = {
    pageLoaded: function()
    {
        // Give the window focus so that subsequent focus events within the
        // window will be taken up.
        window.focus();
        
        this.setupPage();
        
        // In the case where a request to add a new folder has arrived before
        // the entire page is finished loaded (such as when the user selects
        // Add New Folder from the Bookmarks Menu), save the request until the
        // end of setting up the page.
        this._unprocessedAddNewFolderRequest = false;
        
        BookmarksViewController.loaded();
        
        this.rootBookmark = BookmarksViewController.rootBookmark;
        
        this.flowViewController = new FlowViewController(BookmarksViewController.flowView);
        this.listViewController = new BookmarksDataGridController(this.listViewContainerElement, this.flowViewController,
            BookmarksViewController.activateBookmark.bind(BookmarksViewController));
        this.sidebarController = new SidebarController(this.rootBookmark, this.listViewController, this.flowViewController,
            this.setTitle.bind(this));
        
        // FIXME: Change the style of the Find Banner based on whether the
        // system is capable of including the Flow View.
        this.findBannerController = new FindBannerController(BookmarksViewController.findBanner);
        
        // Keep track of the last focused component for the cases where the focus
        // happens on a part of an element without any text to select (causing
        // window.getSelection to return null).
        this._lastFocusedComponent = this.Component.None;
        // Default focus is on sidebar.
        this.focusSidebar();
        
        if (this._unprocessedAddNewFolderRequest)
            this.handleAddNewFolderRequest();
    },

    setupPage: function()
    {
        // Set up the Flow View
                
        // Resizer between the Flow View and the List View of the contents.
        this.contentViewsResizerElement = document.getElementById("contentResizerHorizontal");
        this.flowViewElement = document.getElementById("flowView");
        if (BookmarksViewController.shouldIncludeFlowView) {
            // Show the Flow View and the resizer.
            this.flowViewElement.removeStyleClass("flowViewHidden");
            this.flowViewElement.addStyleClass("flowViewVisible");
            this.contentViewsResizerElement.style.display = "inline";
        }
    
        // Set up Resizers
        
        // Sidebar Resizer
        this.sidebarResizerElement = document.getElementById("sidebarResizerVertical");
        
        this.sidebarElement = document.getElementById("sidebar");
        this.sidebarElement.component = this.Component.Sidebar;
        this.contentContainerElement = document.getElementById("contentContainer");
        
        this.addBarElement = document.getElementById("addbar");
        this.addToSidebarContainerElement = document.getElementById("addToSidebarContainer");
        this.addToSidebarContainerElement.component = this.Component.Sidebar;
        this.addToListViewContainerElement = document.getElementById("addToListViewContainer");
        this.addToListViewContainerElement.component = this.Component.ListView;
        
        // Bind (curry) all the parameters except the maximum width (which will vary
        // with the window size) and the point to which it is being dragged.
        this._updateSidebarResizerPosition = Dragger.updateElementPosition.bind(this, this.sidebarResizerElement,
            Direction.HORIZONTAL, [this.sidebarElement, this.addToSidebarContainerElement],
            [this.contentContainerElement, this.addToListViewContainerElement],
            Preferences.sidebarResizerWidth,
            BookmarksViewController.sidebarMinWidth + BookmarksViewController.spaceBetweenSidebarAndContentsView);
        
        this.sidebarResizerElement.addEventListener("mousedown", this.startSidebarDragging.bind(this), false);
        
        this.setSidebarWidth(document.width - this.ratioAdjustedContentsViewWidth());
        
        // Resizer between the Flow View and the List View of the contents.
        this.contentViewsResizerElement = document.getElementById("contentResizerHorizontal");

        this.upperContentsContainerElement = document.getElementById("upperContentsContainer");
        this.upperContentsContainerElement.component = this.Component.None;
        
        this.listViewContainerElement = document.getElementById("listViewContainer");
        this.listViewContainerElement.component = this.Component.ListView;
        
        this.findBannerElement = document.getElementById("findBanner");
        
        this.contentViewsResizerElement.addEventListener("mousedown", this.startFlowViewDragging.bind(this), false);

        this.setInitialUpperContentsHeight();
    },
    
    handleResize: function()
    {   
        this.setSidebarWidth(document.width - this.ratioAdjustedContentsViewWidth());
        
        // Adjust the heights of the Flow View and the List View.
        
        if (!BookmarksViewController.shouldIncludeFlowView)
            return;

        var roomForBothFlowAndListViews = this.hasRoomForBothFlowViewAndListView();
        if (!roomForBothFlowAndListViews && !this.flowViewCollapsed && !this.listViewCollapsed) {
            // Follow the behavior of Native implementation to try to collapse
            // the List View first when resizing the window if there isn't
            // enough room for both Views.
            if (this.hasRoomForFlowView())
                this.collapseListView();
            else
                this.collapseFlowView();
            return;
        }
        
        // Even if the window is resized to the point where it is only 10 or 20
        // pixels high, there should still only be one view collapsed.
        console.assert(!this.flowViewCollapsed || !this.listViewCollapsed,
            "Both the Flow View and the List View are incorrectly collapsed at the same time.");
        
        if (this.listViewCollapsed) {
            // Make sure the Flow View resizes correctly (so that resizer and
            // the Find Banner are always shown, collapsing the Flow View if
            // there isn't enough room to show it).
            if (!this.hasRoomForFlowView()) {
                this.collapseFlowView();
                this.listViewCollapsed = false;
                return;
            }
            this.setUpperContentsHeight(document.height - this.addBarElement.offsetHeight);
            return;
        }
        
        // Otherwise, try to maintain the last ratio that was saved into preferences
        this.setUpperContentsHeight(this.ratioAdjustedUpperContentsHeight());
    },
    
    hasRoomForBothFlowViewAndListView: function()
    {
        var findBannerHeight = this.findBannerElement.offsetHeight;
        var availableSpace = (this.upperContentsContainerElement.offsetHeight - findBannerHeight)
            + this.listViewContainerElement.offsetHeight;
        return availableSpace >= BookmarksViewController.flowViewMinHeight + this.contentViewsResizerElement.offsetHeight
            + BookmarksViewController.listViewMinHeight;
    },
    
    // @return (bool) Whether or not there is room in the Contents View for the
    //      Flow View, not regarding the current height of the Flow View or the
    //      List View.
    hasRoomForFlowView: function()
    {
        var availableSpace = document.height - this.addBarElement.offsetHeight;
        return availableSpace >= this.findBannerElement.offsetHeight
            + BookmarksViewController.flowViewMinHeight + this.contentViewsResizerElement.offsetHeight;
    },
    
    // @return (integer) The height the upper contents container should be,
    //      based on the saved preference for the Flow View to List View Height
    //      ratio.
    ratioAdjustedUpperContentsHeight: function()
    {
        var findBannerHeight = this.findBannerElement.offsetHeight;
        var savedFlowViewToListViewRatio = BookmarksViewController.flowViewToListViewHeightRatio;
        var availableSpace = (this.upperContentsContainerElement.offsetHeight - findBannerHeight)
            + this.listViewContainerElement.offsetHeight;
        var desiredFlowViewAndResizerHeight = (availableSpace * savedFlowViewToListViewRatio) / (savedFlowViewToListViewRatio + 1);
        return findBannerHeight + desiredFlowViewAndResizerHeight;
    },
    
    // @return (integer) The width the Contents View should be, based on the
    //      saved preference for the Contents View to Sidebar width ratio.
    ratioAdjustedContentsViewWidth: function()
    {
        var savedContentsViewToSidebarRatio = BookmarksViewController.contentsViewToSidebarWidthRatio;
        return (document.width * savedContentsViewToSidebarRatio) / (savedContentsViewToSidebarRatio + 1);
    },
    
    // Sets the initial height of the upper contents (the Find Banner, Flow
    // View, and resizer) and fill the rest of the Contents View the List
    // View with the List View.
    setInitialUpperContentsHeight: function()
    {
        this.listViewCollapsed = false;
        this.flowViewCollapsed = false;
        
        if (!BookmarksViewController.shouldIncludeFlowView) {
            this.collapseFlowView();
            return;
        }

        // If the document is too small to show both the List View and the Flow
        // View, show whichever had more of the screen the last time the
        // Bookmarks View was open and collapse the other one.
        if (!this.hasRoomForBothFlowViewAndListView()) {
            if (BookmarksViewController.flowViewToListViewHeightRatio < 1 || !this.hasRoomForFlowView())
                this.collapseFlowView();
            else
                this.collapseListView();
            return;       
        }

        var upperContentsHeight = this.ratioAdjustedUpperContentsHeight();
        if (upperContentsHeight < this.findBannerElement.offsetHeight
                + this.contentViewsResizerElement.offsetHeight + BookmarksViewController.flowViewMinHeight) {
            this.collapseFlowView();
            return;
        }

        var listViewHeight = (document.height - upperContentsHeight) - this.addBarElement.offsetHeight;
        if (listViewHeight < BookmarksViewController.listViewMinHeight) {
            this.collapseListView();
            return;
        }
        
        this.setUpperContentsHeight(upperContentsHeight);
    },
    
    setSidebarWidth: function(width)
    {
        // Safari does not allow windows to get below a certain width.
        console.assert(document.width >= BookmarksViewController.contentsViewMinWidth
            + BookmarksViewController.spaceBetweenSidebarAndContentsView + BookmarksViewController.sidebarMinWidth,
            "The document's width was allowed to get too small: %d", document.width);
        var contentsViewWidth = document.width - width;
        var maxSidebarWidth = document.width - BookmarksViewController.contentsViewMinWidth;
 
        if (width < BookmarksViewController.sidebarMinWidth)
            width = BookmarksViewController.sidebarMinWidth;
        else if (contentsViewWidth < BookmarksViewController.contentsViewMinWidth)
            width = maxSidebarWidth;
        
        // This space between the Sidebar and the Contents View is factored
        // into the Sidebar by its border.
        width -= BookmarksViewController.spaceBetweenSidebarAndContentsView;
 
        this._updateSidebarResizerPosition(maxSidebarWidth, width);
        this.updateContentsViewToSidebarWidthRatio();
    },
    
    // Sets the heights of the upper contents (the Find Banner, Flow View, and
    // resizer) to the given value, and fill the rest of the Contents View with
    // the List View.
    //
    // @param height (integer) Height for the upper contents.
    // @param flowViewHeight (integer) (option) If defined, the height to give
    //      the Flow View element. If not defined, the Flow View element will
    //      get resized to fit within the upper contents container.
    setUpperContentsHeight: function(height, flowViewHeight)
    {
        this.upperContentsContainerElement.style.height = height + "px";
        this.listViewContainerElement.style.top = height + "px";
        this.updateFlowViewToListViewHeightRatio();
        
        if (typeof flowViewHeight === "undefined")
            return;
            
        this.flowViewElement.style.height = flowViewHeight + "px";
    },
    
    updateFlowViewToListViewHeightRatio: function()
    {
        var flowViewHeight = this.upperContentsContainerElement.offsetHeight - this.findBannerElement.offsetHeight;
        var listViewHeight = this.listViewContainerElement.offsetHeight;
        if (listViewHeight == 0) {
            BookmarksViewController.flowViewToListViewHeightRatio = 1;
            return;
        }
        BookmarksViewController.flowViewToListViewHeightRatio = flowViewHeight / listViewHeight;
    },
    
    updateContentsViewToSidebarWidthRatio: function()
    {
        var sidebarWidth = this.sidebarElement.offsetWidth;
        var contentsViewWidth = document.width - sidebarWidth;
        console.assert(sidebarWidth >= BookmarksViewController.sidebarMinWidth,
            "The sidebar width is too small: %d", sidebarWidth);
        console.assert(contentsViewWidth >= BookmarksViewController.contentsViewMinWidth,
            "The contents view width is too small: %d", contentsViewWidth);
        BookmarksViewController.contentsViewToSidebarWidthRatio = contentsViewWidth / sidebarWidth;
    },
    
    collapseFlowView: function()
    {
        this.setUpperContentsHeight(this.findBannerElement.offsetHeight, 0);
        this.flowViewCollapsed = true;
    },
    
    expandFlowView: function()
    {
        var height = this.findBannerElement.offsetHeight + this.contentViewsResizerElement.offsetHeight
                + BookmarksViewController.flowViewMinHeight;
        this.setUpperContentsHeight(height, BookmarksViewController.flowViewMinHeight);
        this.flowViewCollapsed = false;
    },
    
    collapseListView: function()
    {
       this.setUpperContentsHeight(document.height - this.addBarElement.offsetHeight);
       this.listViewCollapsed = true;
    },
    
    expandListView: function()
    {
        var height = document.height - (this.addBarElement.offsetHeight + BookmarksViewController.listViewMinHeight);
        this.setUpperContentsHeight(height);
        this.listViewCollapsed = false;
    },
        
    startSidebarDragging: function(event)
    {
        Dragger.elementDragStart(this.sidebarResizeElement, this.sidebarDragging.bind(this),
            this.endDragging.bind(this), event, "col-resize");
    },
    
    sidebarDragging: function(event)
    {
        this.setSidebarWidth(event.pageX);
        event.preventDefault();
    },
    
    startFlowViewDragging: function(event)
    {
        var verticalOffsetOfResizerBottom = totalTopOffset(this.contentViewsResizerElement)
            + this.contentViewsResizerElement.offsetHeight;
        this.distanceFromMouseToResizerBottom = verticalOffsetOfResizerBottom - event.pageY;
        Dragger.elementDragStart(this.contentViewsResizerElement, this.flowViewDragging.bind(this),
                this.endDragging.bind(this), event, "row-resize");
    },

    flowViewDragging: function(event)
    {
        // Take into account the threshold at which the List View should
        // disappear and the entire Contents View should be filled with the
        // Flow View, and the separate threshold at which the Flow View should
        // disappear (but the Find Banner should remain) the List View should
        // take up most of the Contents View.

        var findBannerHeight = this.findBannerElement.offsetHeight;
        var addBarHeight = this.addBarElement.offsetHeight;
        var flowViewMinHeight = BookmarksViewController.flowViewMinHeight;
        var resizerHeight = this.contentViewsResizerElement.offsetHeight;
        
        // Check first to make sure there is room to expand the Flow View if it
        // is collapsed. Following the Native implementation, it is always
        // possible to expand the List View if it is collapsed.
        var availableSpace = document.height - (findBannerHeight + addBarHeight);
        if (this.flowViewCollapsed && availableSpace < addBarHeight + flowViewMinHeight + findBannerHeight)
            return;

        var splitPoint = event.pageY + this.distanceFromMouseToResizerBottom;
        var distanceFromFlowViewTop = splitPoint - findBannerHeight;

        if (distanceFromFlowViewTop < (flowViewMinHeight + resizerHeight)) {
            var flowViewCollapseExpandDistance = flowViewMinHeight / 2;
            
            if (this.flowViewCollapsed) {
                if (distanceFromFlowViewTop <= flowViewCollapseExpandDistance)
                    return;
                this.expandFlowView();
                return;
            } else {
                if (distanceFromFlowViewTop > flowViewCollapseExpandDistance)
                    return;
                this.collapseFlowView();
                return;
            }
        }
        
        var distanceFromListViewBottom = (document.height - addBarHeight) - splitPoint;
        
        if (distanceFromListViewBottom < BookmarksViewController.listViewMinHeight) {
            var listViewCollapseExpandDistance = BookmarksViewController.listViewMinHeight / 2;
            
            if (this.listViewCollapsed) {
                if (distanceFromListViewBottom <= listViewCollapseExpandDistance)
                    return;
                this.expandListView();
                return;
            } else {
                if (distanceFromListViewBottom > listViewCollapseExpandDistance)
                    return;
                this.collapseListView();
                return;
            }
        }

        // Otherwise, this is a normal resize that shouldn't result in either
        // the List View or the Flow View collapsing.
        this.setUpperContentsHeight(splitPoint);
        
        event.preventDefault();
    },

    endDragging: function(event)
    {
        Dragger.elementDragEnd(event);
    },
    
    handleAddNewFolderRequest: function()
    {
        if (!this.sidebarController || !this.listViewController) {
            // The page hasn't finished setting itself up yet. Defer
            // handling of this request until it has.
            this._unprocessedAddNewFolderRequest = true;
            return;
        }
        
        if (this.focusedComponent() == this.Component.Sidebar)
            this.addNewFolderToSidebar();
        else
            this.addNewFolderToListView();
        
        this._unprocessedAddNewFolderRequest = false;
    },
         
    addNewFolderToSidebar: function()
    {
        if (!this.sidebarController)
            return;
        
        this.sidebarController.addNewFolder();
        this.focusSidebar();
    },
    
    addNewFolderToListView: function()
    {
        if (!this.listViewController)
            return;
            
        this.listViewController.addNewFolder();
        this.focusListView();
    },
    
    // FIXME: Consider consolidating these methods into a bookmarkNotification
    // method that takes as a parameter an enum value dictating the type of 
    // notification.
    
    handleBookmarkAddedNotification: function(bookmark)
    {
        if (!this.sidebarController || !this.listViewController)
            return;
            
        this.sidebarController.handleBookmarkAddedNotification(bookmark);
        this.listViewController.handleBookmarkAddedNotification(bookmark);
    },
    
    handleBookmarkRemovedNotification: function(bookmark)
    {
        if (!this.sidebarController || !this.listViewController)
            return;

        this.sidebarController.handleBookmarkRemovedNotification(bookmark);
        this.listViewController.handleBookmarkRemovedNotification(bookmark);
    },
    
    handleBookmarkChangedNotification: function(bookmark)
    {
        if (!this.sidebarController || !this.listViewController)
            return;
        
        this.sidebarController.handleBookmarkChangedNotification(bookmark);
        this.listViewController.handlebookmarkChangedNotification(bookmark);
    },
    
    handleBookmarkIconUpdatedNotification: function(url)
    {
        if (!this.sidebarController || !this.listViewController)
            return;

        this.sidebarController.handleBookmarkIconUpdatedNotification(url);
        this.listViewController.handleBookmarkIconUpdatedNotification(url);
    },
    
    focusSidebar: function()
    {
        if (!this.sidebarController)
            return;
        
        this.sidebarController.focus();
        
        // If the mouse event causing the focus to happen did not happen over
        // an element with text to be selected, then the window selection will be
        // null. Hence, the lastFocusedComponent needs to be set explicitly here.
        this._lastFocusedComponent = this.Component.Sidebar;
    },
    
    focusListView: function()
    {
        if (!this.listViewController)
            return;
        
        this.listViewController.focus();
        
        // If the mouse event causing the focus to happen did not happen over
        // an element with text to be selected, then the window selection will be
        // null. Hence, the lastFocusedComponent needs to be set explicitly here.
        this._lastFocusedComponent = this.Component.ListView;
    },
    
    // Returns the component that currently has focus.
    focusedComponent: function()
    {
        var focusNode = window.getSelection().focusNode;
        if (!focusNode) {
            // If the user has not selected anything, then there will not be a
            // selected node. There is no way to programmatically select a node
            // (focus doesn't give it selection) without visibly selecting the
            // text, so rely on the user not having selected any other component.
            if (this._lastFocusedComponent == this.Component.None) {
                // Assume that the user has not interacted with the page yet
                // (otherwise something would be selected), so focus should be
                // on the sidebar.
                this._lastFocusedComponent = this.Component.Sidebar;
            }
            return this._lastFocusedComponent;
        }
        
        // Search up the DOM Tree to find the containing component.
        var component = focusNode.component;
        while (!component && focusNode) {
            focusNode = focusNode.parentNode;
            if (focusNode)
                component = focusNode.component;
        }
        
        if (!component)
            component = this._lastFocusedComponent;
        else
            this._lastFocusedComponent = component;
            
        return component;
    },
    
    flowViewIndexForBookmark: function(bookmark)
    {
        if (!this.listViewController)
            return -1;
        var rowIndex = this.listViewController.rowIndexForBookmark(bookmark);
        console.assert(rowIndex >= 0, "The bookmark with URL '%s' could not be found in the DataGrid.", bookmark.URLString);
        return this.listViewController.flowViewIndexForRowIndex(rowIndex);
    },
    
    bookmarkForFlowViewIndex: function(flowViewIndex)
    {
        var rowIndex = this.listViewController.rowIndexForFlowViewIndex(flowViewIndex);
        console.assert(rowIndex >= 0, "The row index that corresponds to the flow index %i could not be found in the DataGrid.", flowViewIndex);
        return this.listViewController.bookmarkForRowIndex(rowIndex);
    },
    
    handleBookmarkSelectedInFlowView: function(flowViewIndex)
    {
        if (!this.listViewController)
            return -1;
        var rowIndex = this.listViewController.rowIndexForFlowViewIndex(flowViewIndex);
        console.assert(rowIndex >= 0, "The row index that corresponds to the flow index %i could not be found in the DataGrid.", flowViewIndex);
        this.listViewController.selectBookmark(this.listViewController.bookmarkForRowIndex(rowIndex));
    },
    
    // Checks the formatting on the title before setting it to the document
    // title. By setting the document.title to an non-empty string, this method
    // causes the native code to grab the title via the title method below.
    // FIXME: Don't allow the titles of the Bookmarks to ever be changed to an
    // empty or invalid string.
    setTitle: function(title)
    {
        if (title.trimWhitespace() == "") {
            document.title = BookmarksViewController.defaultTitle;
            return;
        }
        
        document.title = title;
    },
    
    title: function()
    {
        if (!this.sidebarController)
            return "";
        
        // FIXME: <rdar://problem/7132606> Make the title reflect what is
        // currently being searched for if the user is in the middle of a
        // search.
        return this.sidebarController.currentlySelectedBookmark().title;
    },
    
    currentlySelectedBookmark: function()
    {
        if (!this.sidebarController || !this.listViewController)
            return null;
        
        var bookmarkToCut = this.sidebarController.currentlySelectedBookmark();
        if (this.focusedComponent() == this.Component.ListView)
            bookmarkToCut = this.listViewController.currentlySelectedBookmark();
        
        return bookmarkToCut;
    },
    
    canCut: function()
    {
        var bookmarkToCut = this.currentlySelectedBookmark();
        if (!bookmarkToCut)
            return false;
        
        // FIXME: When proxies are added, take those into account (e.g. you
        // can't cut a Bonjour item).
        if (bookmarkToCut.isBookmarksBar || bookmarkToCut.isBookmarksMenu)
            return false;
            
        return true;
    },
    
    onCut: function()
    {
        var bookmarkToCut = this.currentlySelectedBookmark();
        bookmarkToCut.parent.removeChild(bookmarkToCut);
    },
    
    Component: { None: 0, Sidebar: 1, ListView: 2 }
};

