
/*
 * Copyright 2011 Yahoo! Inc.
 * 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 of the Yahoo! Inc. nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 YAHOO! INC. 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.
 */

YUI.add('api-list', function (Y) {

var Lang   = Y.Lang,
    YArray = Y.Array,

    APIList = Y.namespace('APIList'),

    classesNode    = Y.one('#api-classes'),
    inputNode      = Y.one('#api-filter'),
    modulesNode    = Y.one('#api-modules'),
    tabviewNode    = Y.one('#api-tabview'),

    tabs = APIList.tabs = {},

    filter = APIList.filter = new Y.APIFilter({
        inputNode : inputNode,
        maxResults: 1000,

        on: {
            results: onFilterResults
        }
    }),

    search = APIList.search = new Y.APISearch({
        inputNode : inputNode,
        maxResults: 100,

        on: {
            clear  : onSearchClear,
            results: onSearchResults
        }
    }),

    tabview = APIList.tabview = new Y.TabView({
        srcNode  : tabviewNode,
        panelNode: '#api-tabview-panel',
        render   : true,

        on: {
            selectionChange: onTabSelectionChange
        }
    }),

    focusManager = APIList.focusManager = tabviewNode.plug(Y.Plugin.NodeFocusManager, {
        circular   : true,
        descendants: '#api-filter, .yui3-tab-panel-selected .api-list-item a, .yui3-tab-panel-selected .result a',
        keys       : {next: 'down:40', previous: 'down:38'}
    }).focusManager,

    LIST_ITEM_TEMPLATE =
        '<li class="api-list-item {typeSingular}">' +
            '<a href="{rootPath}{typePlural}/{name}.html">{displayName}</a>' +
        '</li>';

// -- Init ---------------------------------------------------------------------

// Duckpunch FocusManager's key event handling to prevent it from handling key
// events when a modifier is pressed.
Y.before(function (e, activeDescendant) {
    if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
        return new Y.Do.Prevent();
    }
}, focusManager, '_focusPrevious', focusManager);

Y.before(function (e, activeDescendant) {
    if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
        return new Y.Do.Prevent();
    }
}, focusManager, '_focusNext', focusManager);

// Create a mapping of tabs in the tabview so we can refer to them easily later.
tabview.each(function (tab, index) {
    var name = tab.get('label').toLowerCase();

    tabs[name] = {
        index: index,
        name : name,
        tab  : tab
    };
});

// Switch tabs on Ctrl/Cmd-Left/Right arrows.
tabviewNode.on('key', onTabSwitchKey, 'down:37,39');

// Focus the filter input when the `/` key is pressed.
Y.one(Y.config.doc).on('key', onSearchKey, 'down:83');

// Keep the Focus Manager up to date.
inputNode.on('focus', function () {
    focusManager.set('activeDescendant', inputNode);
});

// Update all tabview links to resolved URLs.
tabview.get('panelNode').all('a').each(function (link) {
    link.setAttribute('href', link.get('href'));
});

// -- Private Functions --------------------------------------------------------
function getFilterResultNode() {
    return filter.get('queryType') === 'classes' ? classesNode : modulesNode;
}

// -- Event Handlers -----------------------------------------------------------
function onFilterResults(e) {
    var frag         = Y.one(Y.config.doc.createDocumentFragment()),
        resultNode   = getFilterResultNode(),
        typePlural   = filter.get('queryType'),
        typeSingular = typePlural === 'classes' ? 'class' : 'module';

    if (e.results.length) {
        YArray.each(e.results, function (result) {
            frag.append(Lang.sub(LIST_ITEM_TEMPLATE, {
                rootPath    : APIList.rootPath,
                displayName : filter.getDisplayName(result.highlighted),
                name        : result.text,
                typePlural  : typePlural,
                typeSingular: typeSingular
            }));
        });
    } else {
        frag.append(
            '<li class="message">' +
                'No ' + typePlural + ' found.' +
            '</li>'
        );
    }

    resultNode.empty(true);
    resultNode.append(frag);

    focusManager.refresh();
}

function onSearchClear(e) {

    focusManager.refresh();
}

function onSearchKey(e) {
    var target = e.target;

    if (target.test('input,select,textarea')
            || target.get('isContentEditable')) {
        return;
    }

    e.preventDefault();

    inputNode.focus();
    focusManager.refresh();
}

function onSearchResults(e) {
    var frag = Y.one(Y.config.doc.createDocumentFragment());

    if (e.results.length) {
        YArray.each(e.results, function (result) {
            frag.append(result.display);
        });
    } else {
        frag.append(
            '<li class="message">' +
                'No results found. Maybe you\'ll have better luck with a ' +
                'different query?' +
            '</li>'
        );
    }


    focusManager.refresh();
}

function onTabSelectionChange(e) {
    var tab  = e.newVal,
        name = tab.get('label').toLowerCase();

    tabs.selected = {
        index: tab.get('index'),
        name : name,
        tab  : tab
    };

    switch (name) {
    case 'classes': // fallthru
    case 'modules':
        filter.setAttrs({
            minQueryLength: 0,
            queryType     : name
        });

        search.set('minQueryLength', -1);

        // Only send a request if this isn't the initially-selected tab.
        if (e.prevVal) {
            filter.sendRequest(filter.get('value'));
        }
        break;

    case 'everything':
        filter.set('minQueryLength', -1);
        search.set('minQueryLength', 1);

        if (search.get('value')) {
            search.sendRequest(search.get('value'));
        } else {
            inputNode.focus();
        }
        break;

    default:
        // WTF? We shouldn't be here!
        filter.set('minQueryLength', -1);
        search.set('minQueryLength', -1);
    }

    if (focusManager) {
        setTimeout(function () {
            focusManager.refresh();
        }, 1);
    }
}

function onTabSwitchKey(e) {
    var currentTabIndex = tabs.selected.index;

    if (!(e.ctrlKey || e.metaKey)) {
        return;
    }

    e.preventDefault();

    switch (e.keyCode) {
    case 37: // left arrow
        if (currentTabIndex > 0) {
            tabview.selectChild(currentTabIndex - 1);
            inputNode.focus();
        }
        break;

    case 39: // right arrow
        if (currentTabIndex < (Y.Object.size(tabs) - 2)) {
            tabview.selectChild(currentTabIndex + 1);
            inputNode.focus();
        }
        break;
    }
}

}, '3.4.0', {requires: [
    'api-filter', 'api-search', 'event-key', 'node-focusmanager', 'tabview'
]});
