/**
 * Portable utilities for IRC.
 */

(function() {
'use strict';

var IrcUtils = angular.module('IrcUtils', []);

IrcUtils.service('IrcUtils', [function() {
    /**
     * Escape a string for usage in a larger regexp
     * @param str String to escape
     * @return Escaped string
     */
     var escapeRegExp = function(str) {
         return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
     };

    /**
     * Get a new version of a nick list, sorted by last speaker
     *
     * @param nickList Original nick list
     * @return Sorted nick list
     */
    var _ciNickList = function(nickList) {

        var newList = _(nickList).sortBy(function(nickObj) {
            return -nickObj.spokeAt;
        });
        newList = _(newList).pluck('name');

        return newList;
    };

    /**
     * Completes a single nick.
     *
     * @param candidate What to search for
     * @param nickList Array of current nicks sorted for case insensitive searching
     * @return Completed nick (null if not found)
     */
    var _completeSingleNick = function(candidate, nickList) {
        var foundNick = null;

        nickList.some(function(nick) {
            if (nick.toLowerCase().indexOf(candidate.toLowerCase()) === 0) {
                // found!
                foundNick = nick;
                return true;
            }
            return false;
        });

        return foundNick;
    };

    /**
     * Get the next nick when iterating nicks.
     *
     * @param iterCandidate First characters to look at
     * @param currentNick Current selected nick
     * @param nickList Array of current nicks sorted for case insensitive searching
     * @return Next nick (may be the same)
     */
    var _nextNick = function(iterCandidate, currentNick, nickList) {
        var matchingNicks = [];
        var at = null;
        var lcIterCandidate = iterCandidate.toLowerCase();
        var lcCurrentNick = currentNick.toLowerCase();

        // collect matching nicks
        for (var i = 0; i < nickList.length; ++i) {
            var lcNick = nickList[i].toLowerCase();
            if (lcNick.indexOf(lcIterCandidate) === 0) {
                matchingNicks.push(nickList[i]);
                if (lcCurrentNick === lcNick) {
                    at = matchingNicks.length - 1;
                }
            }
            /* Since we aren't sorted any more torhve disabled this:
            else if (matchingNicks.length > 0) {
                // end of group, no need to check after this
                //break;
            }
            */
        }

        if (at === null || matchingNicks.length === 0) {
            return currentNick;
        } else {
            ++at;
            if (at === matchingNicks.length) {
                // cycle
                at = 0;
            }
            return matchingNicks[at];
        }
    };

    /**
     * Nicks tab completion.
     *
     * @param text Plain text (no colors)
     * @param caretPos Current caret position (0 means before the first character)
     * @param iterCandidate Current iteration candidate (null if not iterating)
     * @param nickList Array of current nicks
     * @param suf Custom suffix (at least one character, escaped for regex)
     * @param addSpace Whether to add a space after nick completion in the middle
     * @return Object with following properties:
     *      text: new complete replacement text
     *      caretPos: new caret position within new text
     *      foundNick: completed nick (or null if not possible)
     *      iterCandidate: current iterating candidate
     */
    var completeNick = function(text, caretPos, iterCandidate, nickList, suf, addSpace) {
        var doIterate = (iterCandidate !== null);
        if (suf === undefined) {
            suf = ':';
        }
        // addSpace defaults to true
        var addSpaceChar = (addSpace === undefined || addSpace === 'on') ? ' ' : '';

        // new nick list to search in
        var searchNickList = _ciNickList(nickList);

        // text before and after caret
        var beforeCaret = text.substring(0, caretPos);
        var afterCaret = text.substring(caretPos);

        // default: don't change anything
        var ret = {
            text: text,
            caretPos: caretPos,
            foundNick: null,
            iterCandidate: null
        };

        // iterating nicks at the beginning?
        var m = beforeCaret.match(new RegExp('^([a-zA-Z0-9_\\\\\\[\\]{}^`|-]+)' + suf + ' $'));

        var newNick = null;
        if (m) {
            if (doIterate) {
                // try iterating
                newNick = _nextNick(iterCandidate, m[1], searchNickList);
                if (suf.endsWith(' ')) {
                    beforeCaret = newNick + suf;
                } else {
                    beforeCaret = newNick + suf + ' ';
                }
                return {
                    text: beforeCaret + afterCaret,
                    caretPos: beforeCaret.length,
                    foundNick: newNick,
                    iterCandidate: iterCandidate
                };
            } else {
                // if not iterating, don't do anything
                return ret;
            }
        }

        // nick completion in the beginning?
        m = beforeCaret.match(/^([a-zA-Z0-9_\\\[\]{}^`|-]+)$/);
        if (m) {
            // try completing
            newNick = _completeSingleNick(m[1], searchNickList);
            if (newNick === null) {
                // no match
                return ret;
            }
            if (suf.endsWith(' ')) {
                beforeCaret = newNick + suf;
            } else {
                beforeCaret = newNick + suf + ' ';
            }
            if (afterCaret[0] === ' ') {
                // swallow first space after caret if any
                afterCaret = afterCaret.substring(1);
            }
            return {
                text: beforeCaret + afterCaret,
                caretPos: beforeCaret.length,
                foundNick: newNick,
                iterCandidate: m[1]
            };
        }

        // iterating nicks in the middle?
        m = beforeCaret.match(/^(.* )([a-zA-Z0-9_\\\[\]{}^`|-]+) $/);
        if (m) {
            if (doIterate) {
                // try iterating
                newNick = _nextNick(iterCandidate, m[2], searchNickList);
                beforeCaret = m[1] + newNick + addSpaceChar;
                return {
                    text: beforeCaret + afterCaret,
                    caretPos: beforeCaret.length,
                    foundNick: newNick,
                    iterCandidate: iterCandidate
                };
            } else {
                // if not iterating, don't do anything
                return ret;
            }
        }

        // nick completion elsewhere in the middle?
        m = beforeCaret.match(/^(.* )([a-zA-Z0-9_\\\[\]{}^`|-]+)$/);
        if (m) {
            // try completing
            newNick = _completeSingleNick(m[2], searchNickList);
            if (newNick === null) {
                // no match
                return ret;
            }
            beforeCaret = m[1] + newNick + addSpaceChar;
            if (afterCaret[0] === ' ') {
                // swallow first space after caret if any
                afterCaret = afterCaret.substring(1);
            }
            return {
                text: beforeCaret + afterCaret,
                caretPos: beforeCaret.length,
                foundNick: newNick,
                iterCandidate: m[2]
            };
        }

        // completion not possible
        return ret;
    };

    return {
        'completeNick': completeNick
    };
}]);
})();
