'use strict';

Ox.basename = function(str) {
    /*
    fixme: deprecate
    >>> Ox.basename("foo/bar/foo.bar")
    "foo.bar"
    >>> Ox.basename("foo.bar")
    "foo.bar"
    */
    return str.replace(/^.*[\/\\]/g, '');
};

/*@
Ox.char <f> Alias for String.fromCharCode
@*/
// fixme: add some mapping? like Ox.char(9, 13) or Ox.char([9, 13])?
Ox.char = String.fromCharCode;

/*@
Ox.clean <f> Remove leading, trailing and double whitespace from a string
    > Ox.clean("foo  bar")
    "foo bar"
    > Ox.clean(" foo  bar ")
    "foo bar"
    > Ox.clean(" foo \n bar ")
    "foo\nbar"
    > Ox.clean(" foo\tbar ")
    "foo bar"
@*/
Ox.clean = function(str) {
    return Ox.map(str.split('\n'), function(str) {
        return Ox.trim(str.replace(/\s+/g, ' '));
    }).join('\n');
};

/*@
Ox.endsWith <f> Checks if a string ends with a given substring
    If the substring is a string literal (and not a variable),
    <code>/sub$/.test(str)</code> or <code>!!/sub$/(str)</code>
    is shorter than <code>Ox.ends(str, sub)</code>.
    > Ox.endsWith('foobar', 'bar')
    true
@*/
Ox.endsWith = function(str, sub) {
    // fixme: rename to ends
    return str.substr(str.length - sub.length) == sub;
};

/*@
Ox.highlight <f> Highlight matches in a string
    > Ox.highlight('foobar', 'foo', 'match')
    '<span class="match">foo</span>bar'
@*/
// fixme: with regexp, special chars would have to be escaped
Ox.highlight = function(txt, str, classname) {
    return str && str.length ? txt.replace(
        new RegExp('(' + str + ')', 'ig'),
        '<span class="' + classname + '">$1</span>'
    ) : txt;
};

/*@
Ox.isValidEmail <f> Tests if a string is a valid e-mail address
    (str) -> <b> True if the string is a valid e-mail address
    str <s> Any string
    > Ox.isValidEmail("foo@bar.com")
    true
    > Ox.isValidEmail("foo.bar@foobar.co.uk")
    true
    > Ox.isValidEmail("foo@bar")
    false
    > Ox.isValidEmail("foo@bar..com")
    false
@*/
Ox.isValidEmail = function(str) {
    return !!/^[0-9A-Z\.\+\-_]+@(?:[0-9A-Z\-]+\.)+[A-Z]{2,6}$/i.test(str);
};

/*@
Ox.pad <f> Pad a string to a given length
    > Ox.pad(1, 2)
    "01"
    > Ox.pad("abc", -6, ".")
    "abc..."
    > Ox.pad("foobar", -3, ".")
    "foo"
    > Ox.pad("abc", -6, "123456")
    "abc123"
    > Ox.pad("abc", 6, "123456")
    "456abc"
@*/
Ox.pad = function(str, len, pad) {
    // fixme: slighly obscure signature
    // fixme: weird for negative numbers
    var pos = len / (len = Math.abs(len));
    str = str.toString().substr(0, len);
    pad = Ox.repeat(pad || '0', len - str.length);
    str = pos == 1 ? pad + str : str + pad;
    str = pos == 1 ?
        str.substr(str.length - len, str.length) :
        str.substr(0, len);
    return str;
};

/*@
Ox.parsePath <f> Returns the components of a path
    (str) -> <o> Path
        extension <s> File extension
        filename <s> Filename
        pathname <s> Pathname
    > Ox.parsePath('/foo/bar/foo.bar')
    {extension: 'bar', filename: 'foo.bar', pathname: '/foo/bar/'}
    > Ox.parsePath('foo/')
    {extension: '', filename: '', pathname: 'foo/'}
    > Ox.parsePath('foo')
    {extension: '', filename: 'foo', pathname: ''}
    > Ox.parsePath('.foo')
    {extension: '', filename: '.foo', pathname: ''}
@*/
Ox.parsePath = function(str) {
    var matches = /^(.+\/)?(.+?(\..+)?)?$/.exec(str);
    return {
        pathname: matches[1] || '',
        filename: matches[2] || '',
        extension: matches[3] ? matches[3].substr(1) : ''
    };
}

/*@
Ox.parseSRT <f> Parses an srt subtitle file
    (str) -> <o> Parsed subtitles
        in <n> In point (sec)
        out <n> Out point (sec)
        text <s> Text
    str <s> Contents of an srt subtitle file
    > Ox.parseSRT('1\n01:02:00,000 --> 01:02:03,400\nHello World')
    [{'in': 3720, out: 3723.4, text: 'Hello World'}]
@*/
Ox.parseSRT = function(str, fps) {
    return str.replace(/\r\n/g, '\n').replace(/\n+$/, '').split('\n\n')
        .map(function(block) {
            var lines = block.split('\n'), points;
            lines.shift();
            points = lines.shift().split(' --> ').map(function(point) {
                return point.replace(',', ':').split(':')
                    .reduce(function(prev, curr, i) {
                        return prev + parseInt(curr, 10) *
                            [3600, 60, 1, 0.001][i];
                    }, 0);
            });
            if (fps) {
                points = points.map(function(point) {
                    return Math.round(point * fps) / fps;
                });
            }
            return {
                'in': points[0],
                out: points[1],
                text: lines.join('\n')
            };
        });
};

Ox.parseUserAgent = function(str) {
    var names = {
            chromeframe: 'Internet Explorer (Chrome Frame)',
            'iPhone OS': 'iOS',
            Fennec: 'Mobile Firefox',
            Mobile: 'Mobile Safari',
            MSIE: 'Internet Explorer',
        },    
        regexps = {
            browser: [
                /(chromeframe)\/(\d+)/,
                /(Chrome)\/(\d+)/,
                /(Fennec)\/(\d+)/,
                /(Firefox)\/(\d+)/,
                /(MSIE)\/(\d+)/,
                /(Opera)\/.+Version\/(\d+)/,
                /Version\/(\d+).+(Mobile)\/.+Safari/,
                /Version\/(\d+).+(Safari)/
            ],
            system: [
                /(iPhone OS) (\d+)/,
                / (Linux) /,
                /(Mac OS X) (10.\d)/,
                /(Windows) (NT \d\.\d)/
            ]
        },
        userAgent = {
            browser: {name: '', version: ''},
            system: {name: '', version: ''}
        },
        versions = {
            '10.3': 'Panther',
            '10.4': 'Tiger',
            '10.5': 'Leopard',
            '10.6': 'Snow Leopard',
            '10.7': 'Lion',
            'NT 5.0': '2000',
            'NT 5.1': 'XP',
            'NT 5.2': '2003',
            'NT 6.0': 'Vista',
            'NT 6.1': '7'
        }
    Ox.forEach(regexps, function(regexps, key) {
        regexps.forEach(function(regexp) {
            var matches = str.match(regexp), name, swap, version;
            if (matches) {
                matches[2] = matches[2] || '';
                swap = matches[1].match(/^\d+$/);
                name = matches[swap ? 2 : 1];
                version = matches[swap ? 1 : 2].replace('_', '.');
                userAgent[key] = {
                    name: names[name] || name,
                    version: versions[version] || version
                };
                return false;
            }
        });
    });
    return userAgent;
};

/*@
Ox.repeat <f> Repeat a value multiple times
    Works for arrays, numbers and strings
    > Ox.repeat(1, 3)
    "111"
    > Ox.repeat("foo", 3)
    "foofoofoo"
    > Ox.repeat([1, 2], 3)
    [1, 2, 1, 2, 1, 2]
    > Ox.repeat([{k: "v"}], 3)
    [{k: "v"}, {k: "v"}, {k: "v"}]
@*/
Ox.repeat = function(val, num) {
    var ret;
    if (Ox.isArray(val)) {
        ret = [];
        num >= 1 && Ox.loop(num, function() {
            ret = Ox.merge(ret, val);
        });
    } else {
        ret = num >= 1 ? new Array(num + 1).join(val.toString()) : '';
    }
    return ret;
};

Ox.reverse = function(str) {
    /*
    Ox.reverse("foo")
    oof
    */
    return str.toString().split('').reverse().join('');
};

/*@
Ox.startsWith <f> Checks if a string starts with a given substring
    If the substring is a string literal (and not a variable),
    <code>/^sub/.test(str)</code> or <code>!!/^sub/(str)</code>
    is shorter than <code>Ox.starts(str, sub)</code>.
    > Ox.startsWith('foobar', 'foo')
    true
@*/
Ox.startsWith = function(str, sub) {
    // fixme: rename to starts
    return str.substr(0, sub.length) == sub;
};

/*@
Ox.stripTags <f> Strips HTML tags from a string
    > Ox.stripTags('f<span>o</span>o')
    'foo'
@*/
Ox.stripTags = function(str) {
    return str.replace(/<.*?>/g, '');
};

/*@
Ox.toCamelCase <f> Takes a string with '-', '/' or '_', returns a camelCase string
    > Ox.toCamelCase('foo-bar-baz')
    'fooBarBaz'
    > Ox.toCamelCase('foo/bar/baz')
    'fooBarBaz'
    > Ox.toCamelCase('foo_bar_baz')
    'fooBarBaz'
@*/
Ox.toCamelCase = function(str) {
    return str.replace(/[\-\/_][a-z]/g, function(str) {
        return str[1].toUpperCase();
    });
};

/*@
Ox.toDashes <f> Takes a camelCase string, returns a string with dashes
    > Ox.toDashes('fooBarBaz')
    'foo-bar-baz'
@*/
Ox.toDashes = function(str) {
    return str.replace(/[A-Z]/g, function(str) {
        return '-' + str.toLowerCase();
    });
};

/*@
Ox.toSlashes <f> Takes a camelCase string, returns a string with slashes
    > Ox.toSlashes('fooBarBaz')
    'foo/bar/baz'
@*/
Ox.toSlashes = function(str) {
    /*
    */
    return str.replace(/[A-Z]/g, function(str) {
        return '/' + str.toLowerCase();
    });
};

/*@
Ox.toTitleCase <f> Returns a string with capitalized words
    > Ox.toTitleCase('foo')
    'Foo'
    > Ox.toTitleCase('Apple releases iPhone, IBM stock plummets')
    'Apple Releases iPhone, IBM Stock Plummets'
@*/
Ox.toTitleCase = function(str) {
    return Ox.map(str.split(' '), function(val) {
        var sub = val.substr(1),
            low = sub.toLowerCase();
        if (sub == low) {
            val = val.substr(0, 1).toUpperCase() + low;
        }
        return val;
    }).join(' ');
};

/*@
Ox.toUnderscores <f> Takes a camelCase string, returns string with underscores
    > Ox.toUnderscores('fooBarBaz')
    'foo_bar_baz'
@*/
Ox.toUnderscores = function(str) {
    return str.replace(/[A-Z]/g, function(str) {
        return '_' + str.toLowerCase();
    });
};

Ox.trim = function(str) { // is in jQuery, and in JavaScript itself
    /*
    Ox.trim(" foo ")
    "foo"
    */
    return str.replace(/^\s+|\s+$/g, '');
};

/*@
Ox.truncate <f> Truncate a string to a given length
    (string, length) <s> Truncated string
    (string, length, position) -> <s> Truncated string
    (string, length, placeholder) -> <s> Truncated string
    (string, length, position, placeholder) -> <s> Truncated string
    > Ox.truncate('anticonstitutionellement', 16)
    'anticonstitut...'
    > Ox.truncate('anticonstitutionellement', 16, '...', 'left')
    '...utionellement'
    > Ox.truncate('anticonstitutionellement', 16, '>')
    'anticonstitutio>'
    > Ox.truncate('anticonstitutionellement', 16, '...', 'center')
    'anticon...lement'
@*/
Ox.truncate = function(str, len, pad, pos) {
    var pad = pad || '...',
        pos = pos || 'right',
        strlen = str.length,
        padlen = pad.length,
        left, right;
    if (strlen > len) {
        if (pos == 'left') {
            str = pad + str.substr(padlen + strlen - len);
        } else if (pos == 'center') {
            left = Math.ceil((len - padlen) / 2);
            right = Math.floor((len - padlen) / 2);
            str = str.substr(0, left) + pad + str.substr(-right);
        } else if (pos == 'right') {
            str = str.substr(0, len - padlen) + pad;
        }
    }
    return str;
};

/*@
Ox.words <f> Splits a string into words, removing punctuation
    (string) -> <[s]> Array of words
    string <s> Any string
    > Ox.words('Let\'s "split" array-likes into key/value pairs--okay?')
    ["let's", "split", "array-likes", "into", "key", "value", "pairs", "okay"]
@*/
Ox.words = function(str) {
    var arr = str.toLowerCase().split(/\b/),
        chr = "-'",
        len = arr.length,
        startsWithWord = /\w/.test(arr[0]);
    arr.forEach(function(v, i) {
        // find single occurrences of "-" or "'" that are not at the beginning
        // or end of the string, and join the surrounding words with them
        if (
            i > 0 && i < len - 1
            && v.length == 1 && chr.indexOf(v) > -1
        ) {
            arr[i + 1] = arr[i - 1] + arr[i] + arr[i + 1];
            arr[i - 1] = arr[i] = '';
        }
    });
    // remove elements that have been emptied above
    arr = arr.filter(function(v) {
        return v.length;
    });
    // return words, not spaces or punctuation
    return arr.filter(function(v, i) {
        return i % 2 == !startsWithWord;
    });
}

/*@
Ox.wordwrap <f> Wrap a string at word boundaries
    > Ox.wordwrap("Anticonstitutionellement, Paris s'eveille", 25, '<br/>')
    "Anticonstitutionellement, <br/>Paris s'eveille"
    > Ox.wordwrap("Anticonstitutionellement, Paris s'eveille", 16, '<br/>')
    "Anticonstitution<br/>ellement, Paris <br/>s'eveille"
    > Ox.wordwrap('These are short words', 16, '<br/>', true)
    'These are <br/>short words'
@*/
Ox.wordwrap = function(str, len, sep, bal, spa) {
    // fixme: bad API, sep/bal/spa should be in options object
    var str = str === null ? '' : str.toString(),
        len = len || 80,
        sep = sep || '<br/>',
        bal = bal || false,
        spa = Ox.isUndefined(spa) ? true : spa,
        words = str.split(' '),
        lines;
    if (bal) {
        // balance lines: test if same number of lines
        // can be achieved with a shorter line length
        lines = Ox.wordwrap(str, len, sep, false).split(sep);
        if (lines.length > 1) {
            // test shorter line, unless
            // that means cutting a word
            var max = Ox.max(Ox.map(words, function(word) {
                return word.length;
            }));
            while (len > max) {
                len--;
                if (Ox.wordwrap(str, len, sep, false).split(sep).length > lines.length) {
                    len++;
                    break;
                }
            }
        }
    }
    lines = [''];
    Ox.forEach(words, function(word) {
        if ((lines[lines.length - 1] + word + ' ').length <= len + 1) {
            // word fits in current line
            lines[lines.length - 1] += word + ' ';
        } else {
            if (word.length <= len) {
                // word fits in next line
                lines.push(word + ' ');
            } else {
                // word is longer than line
                var chr = len - lines[lines.length - 1].length;
                lines[lines.length - 1] += word.substr(0, chr);
                for (var pos = chr; pos < word.length; pos += len) {
                    lines.push(word.substr(pos, len));
                }
                lines[lines.length - 1] += ' ';
            }
        }
    });
    if (!spa) {
        lines = Ox.map(lines, function(line) {
            return Ox.trim(line);
        });
    }
    return Ox.trim(lines.join(sep));    
};