/**
 * @author       Joshua Ross <joshualross@ymail.com>
 * @class        Provides a shell like interface
 * @requires     YAHOO
 * @requires     DOMDom
 */
var Shell = function()
{
    /** @type string */
    var user = 'guest';
    /** @type string */
    var host = 'yahoosh.org';
    /** @type string */
    var id   = 'command';
    /** @type array */
    var history = [];
    /** @type integer */
    var historyIdx = 0;
    /** @type string */
    var lastCommand = '';
    /** @type string */
    var lastSearch = '';
    /** @type string */
    var appid = 'ygF1mCfV34Gx7U3xH5w376.AmxfQypOl16wbN6rIO0TJsCBmobDA9OBR0PqtDat3TVGotpip';
    /** @type integer */
    var start = 1;
    /** @type integer */
    var numResults = 4;
    /** @type array */
    var urls = [];

    /**
     * Define the commands that are accepted by this shell
     *
     * @type object
     */
    var commands = {
        'usage': {
            'service': null,
            'description': 'Display this text'
        },
        'help': {
            'service': null,
            'description': 'Display this text'
        },
        'history': {
            'service': null,
            'description': 'Displays history of entered commands'
        },
        'web': {
            'service': 'http://search.yahooapis.com/WebSearchService/V1/webSearch',
            'description': 'Perform a web search',
            'display': [
                {'a[@href="<$ClickUrl>",@title="<$Title>",@target="_blank"]': '#<$Title>'},
                '#<$Summary>',
                '#<$DisplayUrl>'
            ],
            'params': [
                'ClickUrl',
                'Title',
                'Summary',
                'DisplayUrl'
            ]
        },
        'news': {
            'service': 'http://search.yahooapis.com/NewsSearchService/V1/newsSearch',
            'description': 'Search latest news stories',
            'display': [
                {'a[@href="<$ClickUrl>",@title="<$Title>",@target="_blank"]': '#<$Title>'},
                '#<$Summary>',
                [
                 	'#<$Url>',
                 	{'em': '#<$NewsSource>'}
                ]
            ],
            'params': [
                'ClickUrl',
                'Title',
                'Summary',
                'Url',
                'NewsSource'
            ]
        },
        'image': {
            'service': 'http://search.yahooapis.com/ImageSearchService/V1/imageSearch',
            'description': 'Search for an image file',
            'display': [
                {'a[@href="<$ClickUrl>",@title="<$Title>",@target="_blank"]': [
                   '#<$Title>',
                   {'em': '#<$Width>x<$Height>'}
        	    ]},
                {'a[@href="<$ClickUrl>",@title="<$Title>",@target="_blank"]': 'img[@src="<$ThumbnailUrl>",@alt="<$Title"]'},
                '#<$RefererUrl>'
            ],
            'params': [
                'Width',
                'Height',
                'ClickUrl',
                'Title',
                {'Thumbnail': 'Url'},
                'RefererUrl'
        	]
        }
    }

    /**
     * Creates a new Shell
     * @constructor
     * @return Shell
     */
    var construct = function()
    {

    }();



    /**
     * This method activates the input by focusing in on it
     * @private
     * @return boolean
     */
    function activate()
    {
        var node = YAHOO.util.Dom.get(id);
        var dim  = YAHOO.util.Dom.getRegion(node);

        if (node) {
            if ('hidden' !== node.type && (dim.top || dim.left || dim.bottom || dim.right)) {
                setTimeout(function() {
                    node.focus();
                }, 1);

                if (YAHOO.lang.isFunction(node.select)) {
                    setTimeout(function() {node.select();}, 1);
                }
                return true;
            }
        }

        return false;

    }

    /**
     * This method clears the input
     *
     * @private
     * @return void
     */
    function clearCommand()
    {

        var elm = YAHOO.util.Dom.get(id);
        unfocusPrompt(elm.value);
        commandPrompt();
    }

    /**
     * This method gets the command and executes it
     *
     * @private
     * @return void
     */
    function enterCommand()
    {

        var elm = YAHOO.util.Dom.get(id);
        unfocusPrompt(elm.value);

        call(elm.value);
    }

    /**
     * This method unfocuses the current command and changes it from an
     * input to a span
     *
     * @private
     * @param  value string
     * @return void
     */
    function unfocusPrompt(value)
    {
        document.getElementById(id).parentNode.innerHTML = '<span>' + value + '</span>';
    }


    /**
     * Parses the input and calls the correct command
     *
     * @private
     * @param input
     * @return void
     */
    function call(input)
    {
        //add the command to the history
        history[history.length] = input;
        historyIdx = history.length;

        /*
         * Find the command in the input by splitting on the spaces and 
         * then taking the first element of the array
         */
        if (!YAHOO.lang.isArray(input)) {
            input = input.split(' ');
        }
        var cmd = input.shift();

        /*
         * Parse the cmd to see if it is a number, which would trigger the
         * opening of that result url in a new window
         */
        if (parseInt(cmd)) {
            return open(cmd);
        }

        //empty command -> usage
        if (!cmd) {
            return commandPrompt();
        }

        //lastCommand check
        if (!commands[cmd] && commands[lastCommand]) {
            //shift the cmd back onto the input
            input.unshift(cmd);
            cmd = lastCommand;
        }

        //if still undefined
        if (!commands[cmd]) {
            return usage(cmd);
        }

        //shell command?
        if (!commands[cmd].service) {
            return usage();
        }

        /*
         * If the last word on the input is an integer then parse it
         * and use that as the number of results to retrieve.
         * However, if the number of strings in the input are 1 or
         * less than ignore it because the user is searching for
         * that number... who knows y?
         */
        var n = null;
        if (input.length > 1 && parseInt(input[input.length - 1])) {
            n = parseInt(input.pop());
        }

        lastSearch = input.toString()
        lastCommand = cmd;

        return setScript(commands[cmd].service, input.toString(), n);

    }

    /**
     * Opens a new window to the url represented by results[idx]
     *
     * @private
     * @param idx integer
     * @return void
     */
    function open(idx)
    {

        if (!urls[idx]) {
            //TODO output message that result is not found

        }

        /*
         * Prompt before we open the new window.  If not then we lose focus 
         * and when the user returns to the shell there is no prompt
         */
        commandPrompt();

        if (urls[idx]) {
            window.open(urls[idx]);
        }

        return;

    }

    /**
     * This method injects the dynamic script tag at the bottom of the document
     *
     * @private
     * @param string url
     * @param string search
     * @param integer count
     * @return void
     */
    function setScript(url, search, count)
    {
        if (!count) {
            count = numResults;
        }

        var source = url + '?appid=' + appid + '&query=' + search + '&results=' + count + '&output=json&callback=Shell.render';
        DOMDom.append('script[@src="' + source + '",@type="text/javascript"]', document.body);
    }


    /**
     * This method displays the results
     *
     * @private
     * @param  results array
     * @param  cmd string
     * @return void
     */
    function display(results, cmd)
    {
        urls = [];

        //if we have results
        if (results.length) {

            for (var i = 0; i < results.length; i++) {

                var compiled = DOMDom.compile({
                    'table': {
                        'tbody': [
                            {
                                'tr': [
                                    {'td': '#' + (i+1)},
                                    {'td.title': commands[cmd].display[0]}
                                ]
                            },
                            {
                                'tr': [
                                    {'td': '#'},
                                    {'td.summary': commands[cmd].display[1]}
                                ]
                            },
                            {
                                'tr': [
                                    {'td': '#'},
                                    {'td.link': commands[cmd].display[2]}
                                ]
                            }
                        ]
                    }
                });

                /*
                 * create an object of the parameters
                 */
                var params = {};
                for (var j=0; j<commands[cmd].params.length; j++) {
                	/*
                	 * If the parameter is an object then parse it
                	 */
                	if ('object' == typeof(commands[cmd].params[j])){
                		for (p in commands[cmd].params[j]) {
                			var tmp = results[i][p][commands[cmd].params[j][p]];
                			params[p + commands[cmd].params[j][p]] = results[i][p][commands[cmd].params[j][p]];
                		}
                	} else {
                		params[commands[cmd].params[j]] = results[i][commands[cmd].params[j]];
                	}
                }
                DOMDom.append(compiled, 'inner', params);

                //store the url
                urls[i+1] = results[i]['ClickUrl'];

            }

            DOMDom.append({
                'div': '#To open a result, enter the number at the prompt'
                }, 'inner');
        } else {
           DOMDom.append({
                'div': '#No results for search query: ' + lastSearch
                }, 'inner');

        }

        commandPrompt();

    }



    /**
     * Display the usage for the yahoo shell
     *
     * If the parameter is not empty then show an unknown command error and
     * then the usage
     *
     * @private
     * @param  cmd string
     * @return void
     */
    function usage(cmd)
    {
        if (cmd) {
            DOMDom.append({
                'div': '#Unknown command: '+ cmd + '  Type \'help\' for usage'
            }, 'inner');
        }


        var xhtml = {
            'table': {
                'thead': {
                    'tr': [
                        {'th': '#Command'},
                        {'th': '#Description'},
                    ]
                },
                'tbody': []
            }
        };

        var i = 0;
        for (cmd in commands) {
            xhtml.table.tbody[i] = {'tr': [{'td': '#' + cmd}, {'td': '#' + commands[cmd].description}]};
            i++;
        }

        DOMDom.append({
            'div': [
                '#Usage: command SEARCHSTRING [NUMRESULTS]<br />',
                '#Example: web yahoo developer network 5'
            ]}, 'inner');
        DOMDom.append(xhtml, 'inner');

        commandPrompt();

    }

    /**
     * Display an error
     *
     * @private
     * @param  title string
     * @param  message string
     * @return void
     */
    function displayError(title, message)
    {
        DOMDom.append({
            'div': [
                '#Yahoo service returned errors:<br />',
                '#'+ title + ' ' + message,
            ]}, 'inner');
    }

    /**
     * This method adds a key listener for the enter button in the input that
     * is the command line
     *
     * @private
     * @return void
     */
    function addEnterListener()
    {
        var kl = new YAHOO.util.KeyListener(
            id,
            {keys: YAHOO.util.KeyListener.KEY.ENTER},
            enterCommand,
            YAHOO.util.KeyListener.KEYUP
        );
        kl.enable();

    }

    /**
     * This method adds a key listener for the cntl + c key combination on the
     * input that is the command line
     *
     * @private
     * @return void
     */
    function addCancelListener()
    {
        var kl = new YAHOO.util.KeyListener(
            id,
            {ctrl: true, keys: 67},
            clearCommand,
            YAHOO.util.KeyListener.KEYUP
        );
        kl.enable();

    }

    /**
     * This method adds a key listener for the down arrow on the
     * input that is the command line
     *
     * @private
     * @return void
     */
    function addDownArrowListener()
    {
        var kl = new YAHOO.util.KeyListener(
            id,
            {keys: 38},
            nextHistory,
            YAHOO.util.KeyListener.KEYUP
        );
        kl.enable();

    }

    /**
     * This method adds a key listener for the up arrow on the
     * input that is the command line
     *
     * @private
     * @return void
     */
    function addUpArrowListener()
    {
        var kl = new YAHOO.util.KeyListener(
            id,
            {keys: 38},
            previousHistory,
            YAHOO.util.KeyListener.KEYUP
        );
        kl.enable();

    }

    /**
     * This method changes the value of the prompt to the previous entry
     * in the search history
     *
     * @private
     * @return void
     */
    function previousHistory()
    {
        if (history.length && historyIdx >= 0) {
            var elm = YAHOO.util.Dom.get(id);
            elm.value = history[historyIdx--];
        }

    }

    /**
     * This method changes the value of the prompt to the next entry
     * in the search history
     *
     * @private
     * @return void
     */
    function nextHistory()
    {
        if (history.length && historyIdx < history.length) {
            var elm = YAHOO.util.Dom.get(id);
            elm.value = history[historyIdx++];
        }

    }


    /**
     * This method adds a key listener for a click anywhere in the window so
     * that the focus remains on the input
     *
     * @private
     * @return void
     */
    function addClickListener()
    {
        YAHOO.util.Event.addListener(window.document, 'click', activate, this, true);

    }

    /**
     * This method creates a prompt by first saving off the previous command
     * and then activating the new prompt and adding the events to the page
     *
     * @private
     * @return void
     */
    function commandPrompt()
    {
        DOMDom.append( {
            'table.prompt': {
                'tbody': {
                    'tr': [
                        { 'td':
                           {'label': [
                               {'span.user': '#' + user},
                               '#@',
                               {'span.host': '#' + host},
                               '#:/' + lastCommand + '> '
                           ]}
                        },
                        { 'td': 'input#' + id + '[type="text"][tabindex="1"]' }
                    ]
                }
            }
        }, 'inner');


        activate();
        addEnterListener();
        addCancelListener();
        addClickListener();
        addUpArrowListener();
        addDownArrowListener();

    }

    return {

        /**
         * This method displays the usage and the the command prompt
         *
         * @public
         * @return void
         */
        initPrompt: function()
        {
            usage();
            //commandPrompt();
        },


        /**
         * This method handles the rendering of the query results
         *
         * @public
         * @param data object
         * @return void
         */
        render: function(data)
        {
            if (data.Error) {

                displayError(data.Error.Title, data.Error.Message);
                usage();

            } else {
                cmd = data.ResultSet.type;
                if (!cmd) {
                    cmd = lastCommand;
                }
                display(data.ResultSet.Result, cmd);
            }
        }
    }
}();
