---
-- lodash for lua
-- @module lodash
-- @author Ted Moghimi
-- @license MIT

-- 20200120; added _.isEqual ( http://lua-users.org/lists/lua-l/2014-09/msg00463.html )

local _ = {
    _VERSION = '0.03'
}

--- Array
-- @section Array

---
-- Creates an array of elements split into groups the length of size.
-- If collection can’t be split evenly, the final chunk will be the
-- remaining elements.
-- @usage local t = _.chunk({'x', 'y', 'z', 1, 2, 3, 4, true , false}, 4)
-- _.print(t)
-- --> {{"x", "y", "z", 1}, {2, 3, 4, true}, {false}}
--
-- @param array The array to process.
-- @param[opt=1] size The length of each chunk.
-- @return the new array containing chunks.
_.chunk = function (array, size)
    local t = {}
    local size = size == 0 and 1 or size or 1
    local c, i = 1, 1
    while true do
        t[i] = {}
        for j = 1, size do
            _.push(t[i], array[c])
            c = c + 1
        end
        if _.gt(c, #array) then
            break
        end
        i = i + 1
    end
    return t
end

---
-- Creates an array with all falsey values removed. The values false,
-- nil are falsey.
-- @usage local t = _.compact({'x', 'y', nil, 1, 2, 3, false, true , false})
-- _.print(t)
-- --> {"x", "y", 1, 2, 3, true}
--
-- @param array The array to compact
-- @return Returns the new array of filtered values
_.compact = function (array)
    local t = {}
    for k, v in pairs(array) do
        if v then
            _.push(t, v)
        end
    end
    return t
end


---
-- Creates an array of unique array values not included in the other
-- provided arrays.
-- @usage _.print(_.difference({3, 1, 2, 9, 5, 9}, {4, 5}, {9, 1}))
-- --> {3, 2}
--
-- @param array The array to inspect.
-- @param ... The arrays of values to exclude.
-- @return Returns the new array of filtered values.
_.difference = function (array, ...)
    local t = {}
    local c = 1
    local tmp = _.table(...)
    for k, v in ipairs(array) do
        while not _.isNil(tmp[c]) do
            for j, v2 in ipairs(tmp[c]) do
                if v == v2 then goto doubleBreak end
            end
            c = c + 1
        end
        _.push(t, v)
        ::doubleBreak::
        c = 1
    end
    return t
end

---
-- Creates a slice of array with n elements dropped from the beginning.
-- @usage _.print(_.drop({1, 2, 3, 4, 5, 6}, 2))
-- --> {3, 4, 5, 6}
--
-- @param array The array to query.
-- @param[opt=1] n The number of elements to drop.
-- @return Returns the slice of array.
_.drop = function (array, n)
    local n = n or 1
    return _.slice(array, n + 1)
end


local callIteratee = function (predicate, selfArg, ...)
    local result
    local predicate = predicate or _.identity
    if selfArg then
        result = predicate(selfArg, ...)
    else
        result = predicate(...)
    end
    return result
end

---
-- Creates a slice of array with n elements dropped from the end.
-- @usage _.print(_.dropRight({1, 2, 3, 4, 5, 6}, 2))
-- --> {1, 2, 3, 4}
--
-- @param array The array to query.
-- @param[opt=1] n The number of elements to drop.
-- @return Returns the slice of array.
_.dropRight = function (array, n)
    local n = n or 1
    return _.slice(array, nil, #array - n)
end


local dropWhile = function(array, predicate, selfArg, start, step, right)
    local t = {}
    local c = start
    while not _.isNil(array[c]) do
        ::cont::
        if #t == 0 and
            callIteratee(predicate, selfArg, array[c], c, array) then
            c = c + step
            goto cont
        end
        if right then
            _.enqueue(t, array[c])
        else
            _.push(t, array[c])
        end
        c = c + step
    end
    return t
end

---
-- Creates a slice of array excluding elements dropped from the end.
-- Elements are dropped until predicate returns falsey.
-- @usage _.print(_.dropRightWhile({1, 5, 2, 3, 4, 5, 4, 4}, function(n)
--    return n > 3
-- end))
-- --> {1, 5, 2, 3}
--
-- @param array The array to query.
-- @param[opt=_.identity] predicate The function to invoked per iteratin.
-- @param[opt] selfArg The self binding of predicate.
-- @return Return the slice of array.
_.dropRightWhile = function(array, predicate, selfArg)
    return dropWhile(array, predicate, selfArg, #array, -1, true)
end

---
-- Creates a slice of array excluding elements dropped from the beginning.
-- Elements are dropped until predicate returns falsey.
-- @usage _.print(_.dropWhile({1, 2, 2, 3, 4, 5, 4, 4, 2}, function(n)
--    return n < 3
-- end))
-- --> {3, 4, 5, 4, 4, 2}
--
-- @param array The array to query.
-- @param[opt=_.idenitity] predicate The function invoked per iteration.
-- @param[opt] selfArg The self binding of predicate.
-- @return Return the slice of array.
_.dropWhile = function(array, predicate, selfArg)
    return dropWhile(array, predicate, selfArg, 1, 1)
end


---
-- Push value to first element of array
---
--@Usage local array = {1, 2, 3, 4}
-- _.enqueue(array, 5)
-- _print(array)
-- --> {5, 1, 2, 3, 4}
--
-- @param array; Array to modify
-- @param: value; Value to fill first element of Array with
-- @return array
_.enqueue = function (array, value)
    return table.insert(array, 1, value)
end

---
-- Fills elements of array with value from start up to, including, end.
-- @usage local array = {1, 2, 3, 4}
-- _.fill(array, 'x', 2, 3)
-- _.print(array)
-- --> {1, "x", "x", 4}
--
-- @param array The array to fill.
-- @param value The value to fill array with.
-- @param[opt=1] start The start position.
-- @param[opt=#array] stop The end position.
-- @return Returns array.
_.fill = function (array, value, start, stop)
    local start = start or 1
    local stop = stop or #array
    for i=start, stop, start > stop and -1 or 1 do
        array[i] = value
    end
    return  array
end


local findIndex = function(array, predicate, selfArg, start, step)
    local c = start
    while not _.isNil(array[c]) do
        if callIteratee(predicate, selfArg, array[c], c, array) then
            return c
        end
        c = c + step
    end
    return -1
end

---
-- This method is like [_.find](#_.find) except that it returns the index of the
-- first element predicate returns truthy for instead of the element itself.
-- @usage _.print(_.findIndex({{a = 1}, {a = 2}, {a = 3}, {a = 2}, {a = 3}}, function(v)
--     return v.a == 3
-- end))
-- --> 3
--
-- @param array The array to search.
-- @param[opt=_.idenitity] predicate The function invoked per iteration.
-- @param[opt] selfArg The self binding of predicate.
-- @return Returns the index of the found element, else -1.
_.findIndex = function (array, predicate, selfArg)
    return findIndex(array, predicate, selfArg, 1, 1)
end

---
-- This method is like [_.findIndex](#_.findIndex) except that it iterates over
-- elements of collection from right to left.
-- @usage _.print(_.findLastIndex({{a = 1}, {a = 2}, {a = 3}, {a = 2}, {a = 3}}, function(v)
--     return v.a == 3
-- end))
-- --> 5
--
-- @param array The array to search.
-- @param[opt=_.idenitity] predicate The function invoked per iteration.
-- @param[opt] selfArg The self binding of predicate.
-- @return Returns the index of the found element, else -1.
_.findLastIndex = function (array, predicate, selfArg)
    return findIndex(array, predicate, selfArg, #array, -1)
end


---
-- Gets the first element of array.
-- @usage _.print(_.first({'w', 'x', 'y', 'z'}))
-- --> w
--
-- @param array The array to query.
-- @return Returns the first element of array.
_.first = function (array)
    return array[1]
end

---
-- Flattens a nested array.
-- If isDeep is true the array is recursively flattened, otherwise
-- it’s only flattened a single level.
-- @usage _.print(_.flatten({1, 2, {3, 4, {5, 6}}}))
-- --> {1, 2, 3, 4, {5, 6}}
-- _.print(_.flatten({1, 2, {3, 4, {5, 6}}}, true))
-- --> {1, 2, 3, 4, 5, 6}
--
-- @param array The array to flatten.
-- @param isDeep Specify a deep flatten
-- @return Returns the new flattened array.
_.flatten = function(array, isDeep)
    local t = {}
    for k, v in ipairs(array) do
        if _.isTable(v) then
            local childeren
            if isDeep then
                childeren = _.flatten(v)
            else
                childeren = v
            end
            for k2, v2 in ipairs(childeren) do
                _.push(t, v2)
            end
        else
            _.push(t, v)
        end
    end
    return t
end

---
-- Recursively flattens a nested array.
-- @usage _.print(_.flattenDeep({1, 2, {3, 4, {5, 6}}}))
-- --> {1, 2, 3, 4, 5, 6}
--
-- @param array The array to flatten.
-- @return Returns the new flattened array.
_.flattenDeep = function (array)
    return _.flatten(array, true)
end

---
-- Gets the index at which the first occurrence of value is found in array.
-- @usage _.print(_.indexOf({2, 3, 'x', 4}, 'x'))
-- --> 3
--
-- @param array The array to search.
-- @param value The value to search for.
-- @param[opt=1] fromIndex The index to search from.
-- @return  Returns the index of the matched value, else -1.
_.indexOf = function (array, value, fromIndex)
    return _.findIndex(array, function(n)
        return n == value
    end)
end
--

---
-- Gets all but the last element of array.
-- @usage _.print(_.initial({1, 2, 3, 'a'}))
-- --> {1, 2, 3}
--
-- @param array The array to query.
-- @return Returns the slice of array.
_.initial = function (array)
    return _.slice(array, nil, #array - 1)
end
--

---
-- Creates an array of unique values that are included in all of the
-- provided arrays.
-- @usage _.print(_.intersection({1, 2}, {4, 2}, {2, 1}))
-- --> {2}
--
-- @param The arrays to inspect.
-- @return Returns the new array of shared values.
_.intersection = function (...)
    local tmp = _.table(...)
    local first = table.remove(tmp, 1)
    local t = {}
    for i, v in ipairs(first) do
        local notFound = false
        for i2, v2 in ipairs(tmp) do
            if _.indexOf(v2, v) == -1 then
                notFound = true
                break
            end
        end
        if not notFound then
            _.push(t, v)
        end
    end
    return t
    -- body
end


---
-- Gets the last element of array.
-- @usage _.print(_.last({'w', 'x', 'y', 'z'}))
-- --> z
--
-- @param array The array to query.
-- @return Returns the last element of array.
_.last = function(array)
    return array[#array]
end

---
-- This method is like [_.indexOf](#_.indexOf) except that it iterates
--  over elements of array from right to left.
-- @usage _.print(_.lastIndexOf({2, 3, 'x', 4, 'x', 5}, 'x'))
-- --> 5
--
-- @param array The array to search.
-- @param value The value to search for.
-- @param[opt=#array] fromIndex The index to search from.
-- @return  Returns the index of the matched value, else -1.
_.lastIndexOf = function (array, value, fromIndex)
   return _.findLastIndex(array, function(n)
        return n == value
    end)
end


---
-- Removes all provided values from array.
-- @usage local array = {1, 2, 3, 4, 5, 4, 1, 2, 3}
-- _.pull(array, 2, 3)
-- _.print(array)
-- --> {1, 4, 5, 4, 1}
-- @param array The array to modify.
-- @param ... The values to remove.
-- @return Returns array
_.pull = function(array, ...)
    local i = 1
    while not _.isNil(array[i]) do
        for k, v in ipairs(_.table(...)) do
            if array[i] == v then
                table.remove(array, i)
                goto cont
            end
        end
        i = i + 1
        ::cont::
    end
    return array
end

_.push = function (array, value)
    return table.insert(array, value)
end

---
-- Removes elements from array corresponding to the given indexes and
-- returns an array of the removed elements. Indexes may be specified
-- as an array of indexes or as individual arguments.
-- @usage local array = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
-- local t = _.pullAt(array, 4, 9, 8)
-- _.print(array)
-- --> {"a", "b", "c", "e", "f", "g", "j"}
-- _.print(t)
-- --> {"d", "h", "i"}
--
-- @param array The array to modify.
-- @param ... The indexes of elements to remove
-- @return Returns the new array of removed elements.
_.pullAt = function (array, ...)
    local t = {}
    local tmp = _.table(...)
    table.sort(tmp, function(a, b)
        return _.gt(a, b)
    end)
    for i, index in ipairs(tmp) do
        _.enqueue(t, table.remove(array, index))
    end
    return t
end

---
-- Removes all elements from array that predicate returns truthy for
-- and returns an array of the removed elements.
-- @usage local array = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 1, 2, 3, 5, 4}
-- local t = _.remove(array, function(value)
--     return value > 4
-- end)
-- _.print(array)
-- --> {1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}
-- _.print(t)
-- --> {5, 6, 7, 5, 6, 5}
--
-- @param array The array to modify.
-- @param predicate The function invoked per iteration.
-- @return Returns the new array of removed elements
_.remove = function(array, predicate)
    local t = {}
    local c = 1
    local predicate = predicate or _.identity
    while not _.isNil(array[c]) do
        if predicate(array[c], c, array) then
            _.push(t, table.remove(array, c))
            goto cont
        end
        c = c + 1
        ::cont::
    end
    return t
end


---
-- Gets all but the first element of array.
-- @usage _.print(_.rest({1, 2, 3, 'a'}))
-- --> {2, 3, 'a'}
-- @param array The array to query.
-- @return Returns the slice ofa array.
_.rest = function (array)
    return _.slice(array, 2, #array)
end


---
-- Reverses the array so the first element becomes the last, the second
-- element becomes the second to last, and so on.
-- @usage _.print(_.reverse({1, 2, 3, 'a', 'b'}))
-- --> {'b', 'a', 3, 2, 1}
--
-- @param array The array to mutate.
-- @return Returns the new reversed array.
_.reverse = function (array)
    local t = {}
    for i, v in ipairs(array) do
        _.enqueue(t, v)
    end
    return t
end


---
-- Creates a slice of array from start up to, including, end.
-- @usage _.print(_.slice({1, 2, 3, 4, 5, 6}, 2, 3))
-- --> {2, 3}
--
-- @param array The array to slice.
-- @param[opt=1] start The start position.
-- @param[opt=#array] stop The end position
-- @return Returns the slice of array.
_.slice = function (array, start, stop)
    local start = start or 1
    local stop = stop or #array
    local t = {}
    for i=start, stop do
        t[i - start + 1] = array[i]
    end
    return t
end

---
-- Creates a slice of array with n elements taken from the beginning.
-- @usage _.print(_.take({1, 2, 3, 4, 5}, 3))
-- --> {1, 2, 3}
--
-- @param array The array to query.
-- @param[opt=1] n The number of elements to take.
-- @return Returns the slice of array.
_.take = function(array, n)
    local n = n or 1
    return _.slice(array, 1, n)
end


---
-- Creates a slice of array with n elements taken from the end.
-- @usage _.print(_.takeRight({1, 2, 3, 4, 5}, 3))
-- --> {3, 4, 5}
--
-- @param array The array to query.
-- @param[opt=1] n The number of elements to take.
-- @return Returns the slice of array.
_.takeRight = function (array, n)
    local n = n or 1
    return _.slice(array, #array - n +1)
end

local takeWhile = function(array, predicate, selfArg, start, step, right)
    local t = {}
    local c = start
    while not _.isNil(array[c]) do
        if callIteratee(predicate, selfArg, array[c], c, array) then
            if right then
                _.enqueue(t, array[c])
            else
                _.push(t, array[c])
            end
        else
            break
        end
        c = c + step
    end
    return t
end

---
-- Creates a slice of array with elements taken from the end. Elements
-- are taken until predicate returns falsey. The predicate is bound to
-- selfArg and invoked with three arguments: (value, index, array).
-- @usage _.print(_.takeRightWhile({1, 2, 3, 4, 5, 6, 7, 8}, function(n)
--     return n > 4
-- end))
-- --> {5, 6, 7, 8}
--
-- @param array The array to query.
-- @param predicate The function invoked per iteration.
-- @param selfArg The self binding of predicate.
_.takeRightWhile = function (array, predicate, selfArg)
    return takeWhile(array, predicate, selfArg, #array, -1, true)
end

---
-- Creates an array of unique values, from all of the
-- provided arrays.
-- @usage _.print(_.union({1, 2}, {4, 2}, {2, 1}))
-- --> {1, 2, 4}
--
-- @param ... The arrays to inspect
_.union = function (...)
    local tmp = _.table(...)
    local t = {}
    for i, array in ipairs(tmp) do
        for i2, v in ipairs(array) do
            if _.indexOf(t, v) == -1 then
                _.push(t, v)
            end
        end
    end
    return t
end



---
-- Creates a slice of array with elements taken from the beginning. Elements
-- are taken until predicate returns falsey. The predicate is bound to
-- selfArg and invoked with three arguments: (value, index, array).
-- @usage _.print(_.takeWhile({1, 2, 3, 4, 5, 6, 7, 8}, function(n)
--     return n < 5
-- end))
-- --> {1, 2, 3, 4}
--
-- @param array The array to query.
-- @param predicate The function invoked per iteration.
-- @param selfArg The self binding of predicate.
_.takeWhile = function (array, predicate, selfArg)
    return takeWhile(array, predicate, selfArg, 1, 1)
end

---
-- Creates a duplicate-free version of an array in which only the first
-- occurence of each element is kept. If an iteratee function is provided
-- it’s invoked for each element in the array to generate the criterion
-- by which uniqueness is computed. The iteratee is bound to thisArg and
-- invoked with three arguments: (value, index, array).
-- @usage _.print(_.uniq({1, 3, 2, 2}))
-- --> {1, 3, 2}
--_.print(_.uniq({{x=1}, {x=2}, {x=2}, {x=3}, {x=1}}, function(n)
--     return n.x
-- end))
-- --> {{["x"]=1}, {["x"]=2}, {["x"]=3}}
--
-- @param array The array to inspect.
-- @param iteratee The function invoked per iteration.
-- @param selfArg The self binding of predicate.
-- @return Returns the new duplicate-value-free array.
_.uniq = function(array, iteratee, selfArg)
    local t = {}
    local results = {}
    for k, v in ipairs(array) do
        local r = callIteratee(iteratee, selfArg, v, k, array)
        if _.indexOf(results, r) == -1 then
            _.push(t, v)
        end
        _.push(results, r)
    end
    return t
end

---
-- The inverse of _.pairs; this method returns an object composed from
-- arrays of property names and values. Provide either a single two dimensional
-- array, e.g. [[key1, value1], [key2, value2]] or two arrays, one of
-- property names and one of corresponding values.
-- @usage _.print(_.zipObject({{'fred', 30}, {'alex', 20}}))
-- --> {["alex"]=20, ["fred"]=30}
-- _.print(_.zipObject({'fred', 'alex'}, {30, 20}))
-- --> {["alex"]=20, ["fred"]=30}
--
-- @param ... The properties/values
-- @return Returns the new object.
_.zipObject = function (...)
    local tmp = _.table(...)
    local t = {}
    if #tmp == 1 then
        for i, pair in ipairs(tmp[1]) do
            t[pair[1]] = pair[2]
        end
    else
        for i = 1, #tmp[1] do
            t[tmp[1][i]] = tmp[2][i]
        end
    end
    return t
end


---
-- This method is like [_.zip](#_.zip) except that it accepts an array of grouped
-- elements and creates an array regrouping the elements to their pre-zip
-- configuration.
-- @usage local t = _.zip({'a', 'b', 'c'}, {1, 2, 3}, {10, 20, 30})
-- _.print(t)
-- --> {{"a", 1, 10}, {"b", 2, 20}, {"c", 3, 30}}
-- _.print(_.unzip(t))
-- --> {{"a", "b", "c"}, {1, 2, 3}, {10, 20, 30}}
--
-- @param array The array of grouped elements to process.
-- @return Returns the new array of regrouped elements.
_.unzip = function (array)
    return _.zip(_.args(array))
end

---
-- Creates an array excluding all provided values
-- @usage _.print(_.without({1,1, 2, 3, 2, 3, 5, 5, 1, 2},  5, 1))
-- --> {2, 3, 2, 3, 2}
--
-- @param array The array to filter.
-- @param ... The values to exclude.
-- @return Returns the new array of filtered values.
_.without = function (array, ...)
    local t = {}
    for i, v in ipairs(array) do
        local args = _.table(...)
        if _.indexOf(args, v) == -1 then
            _.push(t, v)
        end
    end
    return t
end

---
-- Creates an array of grouped elements, the first of which contains
-- the first elements of the given arrays, the second of which contains
-- the second elements of the given arrays, and so on.
-- @usage local t = _.zip({'a', 'b', 'c'}, {1, 2, 3}, {10, 20, 30})
-- _.print(t)
-- --> {{"a", 1, 10}, {"b", 2, 20}, {"c", 3, 30}}
--
-- @param ... The arrays to process
-- @return Returns the new array of grouped elements.
_.zip = function (...)
    local t = {}
    for i, array in ipairs(_.table(...)) do
        for j, v in ipairs(array) do
            t[j] = t[j] or {}
            t[j][i] = v
        end
    end
    return t
end

--- Collection
-- @section Collection

---
-- Creates an array of elements corresponding to the given keys,
-- or indexes, of collection. Keys may be specified as individual
-- arguments or as arrays of keys.
-- @usage _.print(_.at({'1', '2', '3', '4', a='a', b='b'}, {1, 2}, 'b'))
-- --> {"1", "2", "b"}
--
-- @param collection The collection to iterate over.
-- @param ... The property names or indexes of elements to pick,
-- specified individually or in arrays.
-- @return Return the new array of picked elements.
_.at = function (collection, ...)
    local t = {}
    for k, key in ipairs(_.table(...)) do
        if _.isTable(key) then
            for k, key in ipairs(key) do
                _.push(t, collection[key])
            end
        else
            _.push(t, collection[key])
        end
    end
    return t
end

---
-- Creates an object composed of keys generated from the results of
-- running each element of collection through iteratee. The corresponding
-- value of each key is the number of times the key was returned by
-- iteratee. The iteratee is bound to selfArg and invoked with three arguments:
-- (value, index|key, collection).
--
-- @usage _.print(_.countBy({4.3, 6.1, 6.4}, function(n)
--   return math.floor(n)
-- end))
-- --> {[4]=1, [6]=2}
--
-- @param collection The collection to iterate over. (table|string)
-- @param[opt=_.identity] iteratee The function invoked per iteration.
-- @param[opt] selfArg The self binding of predicate.
-- @return Returns the composed aggregate object.
_.countBy = function (collection, iteratee, selfArg)
    local t = {}
    for k, v in _.iter(collection) do
        local r = _.str(
            callIteratee(iteratee, selfArg, v, k, collection)
        )
        if _.isNil(t[r]) then
            t[r] = 1
        else
            t[r] = t[r] + 1
        end
    end
    return t
end

---
-- Creates an object composed of keys generated from the results of
-- running each element of collection through iteratee. The corresponding
-- value of each key is an array of the elements responsible for generating
-- the key. The iteratee is bound to selfArg and invoked with three arguments:
-- (value, index|key, collection).
-- @usage _.print(_.groupBy({4.3, 6.1, 6.4}, function(n)
--   return math.floor(n)
-- end))
-- --> {[4]={4.3}, [6]={6.1, 6.4}}
--
-- @param collection The collection to iterate over. (table|string)
-- @param[opt=_.identity] iteratee The function invoked per iteration.
-- @param[opt] selfArg The self binding of predicate.
-- @return Returns the composed aggregate object.
_.groupBy = function (collection, iteratee, selfArg)
    local t = {}
    for k, v in _.iter(collection) do
        local r = _.str(
            callIteratee(iteratee, selfArg, v, k, collection)
        )
        if _.isNil(t[r]) then
            t[r] = {v}
        else
            _.push(t[r], v)
        end
    end
    return t
end

---
-- Creates an object composed of keys generated from the results of
-- running each element of collection through iteratee. The corresponding
-- value of each key is the last element responsible for generating the key.
-- The iteratee function is bound to selfArg and invoked with three arguments:
-- (value, index|key, collection).
-- @usage local keyData = {
--     {dir='l', a=1},
--     {dir='r', a=2}
-- }
-- _.print('40.indexBy          :', _.indexBy(keyData, function(n)
--     return n.dir
-- end))
-- --> {["l"]={[a]=1, [dir]="l"}, ["r"]={[a]=2, [dir]="r"}}
--
-- @param collection The collection to iterate over. (table|string)
-- @param[opt=_.identity] iteratee The function invoked per iteration.
-- @param[opt] selfArg The self binding of predicate.
-- @return Returns the composed aggregate object.
_.indexBy = function (collection, iteratee, selfArg)
    local t = {}
    for k, v in _.iter(collection) do
        local r = _.str(
            callIteratee(iteratee, selfArg, v, k, collection)
        )
        t[r] = v
    end
    return t
end

---
-- Checks if predicate returns truthy for all elements of collection.
-- The predicate is bound to selfArg and invoked with three arguments:
-- (value, index|key, collection).
-- @usage _.print(_.every({1, 2, 3, 4, '5', 6}, _.isNumber))
-- --> false
-- _.print(_.every({1, 2, 3, 4, 5, 6}, _.isNumber))
-- --> true
--
-- @param collection The collection to iterate over. (table|string)
-- @param[opt=_.identity] predicate The function invoked per iteration
-- @param[opt] selfArg The self binding of predicate.
_.every = function (collection, predicate, selfArg)
    for k, v in _.iter(collection) do
        if not callIteratee(predicate, selfArg, v, k, collection) then
            return false
        end
    end
    return true
end

local filter = function(collection, predicate, selfArg, reject)
    local t = {}
    for k, v in _.iter(collection) do
        local check = callIteratee(predicate, selfArg, v, k, collection)
        if reject then
            if not check then
                _.push(t, v)
            end
        else
            if check then
                _.push(t, v)
            end
        end
    end
    return t
end

---
-- Iterates over elements of collection, returning an array of all
-- elements predicate returns truthy for. The predicate is bound to
-- selfArg and invoked with three arguments: (value, index|key, collection).
-- @usage _.print(_.filter({1, 2, 3, 4, '5', 6, '7'}, _.isNumber))
-- --> {1, 2, 3, 4, 6}
--
-- @param collection The collection to iterate over. (table|string)
-- @param[opt=_.identity] predicate The function invoked per iteration
-- @param[opt] selfArg The self binding of predicate.
_.filter = function (collection, predicate, selfArg)
    return filter(collection, predicate, selfArg)
end

---
-- Iterates over elements of collection invoking iteratee for each element.
-- The iteratee is bound to selfArg and invoked with three arguments:
-- (value, index|key, collection). Iteratee functions may exit iteration
-- early by explicitly returning false.
--
-- @param collection The collection to iterate over. (table|string)
-- @param[opt=_.identity] predicate The function invoked per iteration
-- @param[opt] selfArg The self binding of predicate.
-- @return Returns collection.
_.forEach = function (collection, predicate, selfArg)
    for k, v in _.iter(collection) do
        callIteratee(predicate, selfArg, v, k, collection)
    end
    return collection
end

---
-- This method is like [_.forEach](#_.forEach) except that it iterates
-- over elements of collection from right to left.
--
-- @param collection The collection to iterate over. (table|string)
-- @param[opt=_.identity] predicate The function invoked per iteration
-- @param[opt] selfArg The self binding of predicate.
-- @return Returns collection.
_.forEachRight = function (collection, predicate, selfArg)
    for k, v in _.iterRight(collection) do
        callIteratee(predicate, selfArg, v, k, collection)
    end
    return collection
end

---
-- Checks if target is in collection.
-- @usage print(_.includes({1, 2, 'x', 3, ['5']=4, x=3, 5}, 'x'))
-- --> true
-- print(_.includes({1, 2, 'x', 3, ['5']=4, x=3, 5}, 'z'))
-- --> false
-- @param collection The collection to search
-- @param target The value to search for.
_.includes = function (collection, target)
    local result = _.find(collection, function (n)
        return n == target
    end)
    return result ~= nil
end

---
-- Invokes method of each element in collection, returning an array of the
-- results of each invoked method. Any additional arguments are provided
-- to each invoked method. func bound to, each element in collection.
-- @usage _.print(_.invoke({'1.first', '2.second', '3.third'}, string.sub, 1, 1))
-- --> {"1", "2", "3"}
--
-- @param collection The collection to iterate over.
-- @param method The method to invoke per iteration.
-- @param ... The arguments to invoke the method with.
-- @return Returns the array of results.
_.invoke = function (collection, method, ...)
    local t = {}
    for k, v in _.iter(collection) do
        _.push(t, callIteratee(method, v, ...))
    end
    return t
end

---
-- Creates an array of values by running each element in collection through
-- iteratee. The iteratee is bound to selfArg and invoked with three
-- arguments: (value, index|key, collection).
-- @usage _.print(_.map({1, 2, 3, 4, 5, 6, 7, 8, 9}, function(n)
--     return n * n
-- end))
-- --> {1, 4, 9, 16, 25, 36, 49, 64, 81}
--
-- @param collection The collection to iterate over. (table|string)
-- @param[opt=_.identity] iteratee The function invoked per iteration
-- @param[opt] selfArg The self binding of predicate.
_.map = function (collection, iteratee, selfArg)
    local t = {}
    for k, v in _.iter(collection) do
        t[k] = callIteratee(iteratee, selfArg, v, k, collection)
    end
    return t
end

---
-- Creates an array of elements split into two groups, the first of
-- which contains elements predicate returns truthy for, while the second
-- of which contains elements predicate returns falsey for. The predicate
-- is bound to selfArg and invoked with three arguments:
-- (value, index|key, collection).
-- @usage _.print(_.partition({1, 2, 3, 4, 5, 6, 7}, function (n)
--     return n > 3
-- end))
-- --> {{4, 5, 6, 7}, {1, 2, 3}}
--
-- @param collection The collection to iterate over. (table|string)
-- @param[opt=_.identity] predicate The function invoked per iteration
-- @param[opt] selfArg The self binding of predicate.
-- @return  Returns the array of grouped elements.
_.partition = function (collection, predicate, selfArg)
    local t = {{}, {}}
    for k, v in _.iter(collection) do
        if callIteratee(predicate, selfArg, v, k, collection) then
            _.push(t[1], v)
        else
            _.push(t[2], v)
        end
    end
    return t
end

---
-- Gets the property value of path from all elements in collection.
-- @usage local users = {
--   { user = 'barney', age = 36, child = {age = 5}},
--   { user = 'fred',   age = 40, child = {age = 6} }
-- }
-- _.print(_.pluck(users, {'user'}))
-- --> {"barney", "fred"}
-- _.print(_.pluck(users, {'child', 'age'}))
-- --> {5, 6}
--
-- @param collection The collection to iterate over.
-- @param path The path of the property to pluck.
_.pluck = function (collection, path)
    local t = {}
    for k, value in _.iter(collection) do
        _.push(t, _.get(value, path))
    end
    return t
end


---
-- Reduces collection to a value which is the accumulated result of
-- running each element in collection through iteratee, where each
-- successive invocation is supplied the return value of the previous.
-- If accumulator is not provided the first element of collection is used
--  as the initial value. The iteratee is bound to selfArg and invoked
-- with four arguments: (accumulator, value, index|key, collection).
-- @usage _.print(_.reduce({1, 2, 3}, function(total, n)
--   return n + total;
-- end))
-- --> 6
-- _.print(_.reduce({a = 1, b = 2}, function(result, n, key)
--     result[key] = n * 3
--     return result;
-- end, {}))
-- --> {["a"]=3, ["b"]=6}
--
-- @param collection The collection to iterate over.
-- @param[opt=_.identity] iteratee The function invoked per iteration.
-- @param[opt=<first element>] accumulator The initial value.
-- @param[opt] selfArg The self binding of predicate.
-- @return Returns the accumulated value.
_.reduce = function (collection, iteratee, accumulator, selfArg)
    local accumulator = accumulator
    for k, v in _.iter(collection) do
        if _.isNil(accumulator) then
            accumulator = v
        else
            accumulator =  callIteratee(iteratee, selfArg, accumulator, v, k, collection)
        end
    end
    return accumulator
end

---
-- This method is like _.reduce except that it iterates over elements
-- of collection from right to left.
-- @usage local array = {0, 1, 2, 3, 4, 5};
-- _.print(_.reduceRight(array, function(str, other)
--   return str .. other
-- end, ''))
-- --> 543210
--
-- @param collection The collection to iterate over.
-- @param[opt=_.identity] iteratee The function invoked per iteration.
-- @param[opt=<first element>] accumulator The initial value.
-- @param[opt] selfArg The self binding of predicate.
-- @return Returns the accumulated value.
_.reduceRight = function (collection, iteratee, accumulator, selfArg)
    local accumulator = accumulator
    for k, v in _.iterRight(collection) do
        if _.isNil(accumulator) then
            accumulator = v
        else
            accumulator =  callIteratee(iteratee, selfArg, accumulator, v, k, collection)
        end
    end
    return accumulator
end

---
-- The opposite of [_.filter](#_.filter); this method returns the elements of
-- collection that predicate does not return truthy for.
-- @usage _.print(_.reject({1, 2, 3, 4, '5', 6, '7'}, _.isNumber))
-- --> {"5", "7"}
--
-- @param collection The collection to iterate over. (table|string)
-- @param[opt=_.identity] predicate The function invoked per iteration
-- @param[opt] selfArg The self binding of predicate.
_.reject = function (collection, predicate, selfArg)
    return filter(collection, predicate, selfArg, true)
end

---
-- Gets a random element or n random elements from a collection.
-- @usage _.print(_.sample({1, 2, 3, a=4, b='x', 5, 23, 24}, 4))
-- --> {5, "x", 1, 23}
-- _.print(_.sample({1, 2, 3, a=4, b='x', 5, 23, 24}))
-- --> 4
--
-- @param collection The collection to sample.
-- @param[opt=1] n The number of elements to sample.
-- @return Returns the random sample(s).
_.sample = function (collection, n)
    local n = n or 1
    local t = {}
    local keys = _.keys(collection)
    for i=1, n do
        local pick = keys[_.random(1, #keys)]
        _.push(t, _.get(collection, {pick}))
    end
    return #t == 1 and t[1] or t
end

---
-- Gets the size of collection by returning its length for array-like
-- values or the number of own enumerable properties for objects.
-- @usage _.print(_.size({'abc', 'def'}))
-- --> 2
-- _.print(_.size('abcdefg'))
-- --> 7
-- _.print(_.size({a=1, b=2,c=3}))
-- --> 3
--
-- @param collection The collection to inspect.
-- @return Returns the size of collection.
_.size = function (collection)
    local c = 0
    for k, v in _.iter(collection) do
        c = c + 1
    end
    return c
end

---
-- Checks if predicate returns truthy for any element of collection.
-- The function returns as soon as it finds a passing value and does
-- not iterate over the entire collection. The predicate is bound to
-- selfArg and invoked with three arguments: (value, index|key, collection).
--
-- @usage _.print(_.some({1, 2, 3, 4, 5, 6}, _.isString))
-- --> false
-- _.print(_.some({1, 2, 3, 4, '5', 6}, _.isString))
-- --> true
-- @param collection The collection to iterate over. (table|string)
-- @param[opt=_.identity] predicate The function invoked per iteration
-- @param[opt] selfArg The self binding of predicate.
-- @return Returns true if any element passes the predicate check, else false.
_.some = function (collection, predicate, selfArg)
    for k, v in _.iter(collection) do
        if callIteratee(predicate, selfArg, v, k, collection) then
            return true
        end
    end
    return false
end

---
-- Creates an array of elements, sorted in ascending order by the
-- results of running each element in a collection through iteratee.
-- The iteratee is bound to selfArg and
-- invoked with three arguments: (value, index|key, collection).
-- @usage local t = {1, 2, 3}
-- _.print(_.sortBy(t, function (a)
--     return math.sin(a)
-- end))
-- --> {1, 3, 2}
-- local users = {
--     { user='fred' },
--     { user='alex' },
--     { user='zoee' },
--     { user='john' },
-- }
-- _.print(_.sortBy(users, function (a)
--     return a.user
-- end))
-- --> {{["user"]="alex"}, {["user"]="fred"}, {["user"]="john"}, {["user"]="zoee"}}
--
-- @param collection The collection to iterate over.
-- @param[opt=_.identity] predicate The function invoked per iteration
-- @param[opt] selfArg The self binding of predicate.
-- @return  Returns the new sorted array.
_.sortBy = function (collection, predicate, selfArg)
    local t ={}
    local empty = true
    local previous
    for k, v in _.iter(collection) do
        if empty then
            _.push(t, v)
            previous = callIteratee(predicate, selfArg, v, k, collection)
            empty = false
        else
            local r = callIteratee(predicate, selfArg, v, k, collection)
            if _.lt(previous, r) then
                table.insert(t, v)
                previous = r
            else
                table.insert(t, #t, v)
            end
        end
    end
    return t
end

---
-- Iterates over elements of collection, returning the first element
-- predicate returns truthy for. The predicate is bound to selfArg and
-- invoked with three arguments: (value, index|key, collection).
-- @usage _.print(_.find({{a = 1}, {a = 2}, {a = 3}, {a = 2}, {a = 3}}, function(v)
--     return v.a == 3
-- end))
-- --> {[a]=3}
--
-- @param collection The collection to search. (table|string)
-- @param predicate The function invoked per iteration
-- @param selfArg The self binding of predicate.
_.find = function (collection, predicate, selfArg)
    for k, v in _.iter(collection) do
        if callIteratee(predicate, selfArg, v, k, collection) then
            return v
        end
    end
end

---
-- This method is like _.find except that it iterates over elements of
-- collection from right to left.
-- @usage _.findLast({{a = 1}, {a = 2}, {a = 3, x = 1}, {a = 2}, {a = 3, x = 2}},
-- function(v)
--     return v.a == 3
-- end))
-- --> {[a]=3, [x]=2}
--
-- @param collection The collection to search. (table|string)
-- @param predicate The function invoked per iteration
-- @param selfArg The self binding of predicate.
_.findLast = function (collection, predicate, selfArg)
    for k, v in _.iterRight(collection) do
        if callIteratee(predicate, selfArg, v, k, collection) then
            return v
        end
    end
end

--- Function
-- @section Function

---
-- This method creates a function that invokes func once it’s called n
--  or more times.
-- @usage local printAfter3 = _.after(3, print)
-- for i = 1, 5 do
--    printAfter3('done', i)
-- end
-- --> done 4
-- --> done 5
--
-- @param n The number of calls before func invoked.
-- @param func The function to restrict.
-- @return Returns the new restricted function.
_.after = function(n, func)
    local i = 1
    return function(...)
        if _.gt(i, n) then
            return func(...)
        end
        i = i + 1
    end
end

---
-- Creates a function that accepts up to n arguments ignoring any
-- additional arguments.
-- @usage local printOnly3 =_.ary(print, 3)
-- printOnly3(1, 2, 3, 'x', 'y', 6)
-- --> 1    2   3
--
-- @param func The function to cap arguments for.
-- @param n the arity cap.
-- @return Returns the new function
_.ary = function(func, n)
    return function(...)
        if n == 1 then
            return func((...))
        else
            local t = _.table(...)
            local first = _.take(t, n)
            return func(_.args(first))
        end
    end
end

---
-- Creates a function that invokes func while it’s called less than n times.
-- Subsequent calls to the created function return the result of the
-- last func invocation.
-- @usage local printBefore3 = _.before(3, print)
-- for i = 1, 10 do
--     printBefore3(i, 'ok')
-- end
-- -->  1   ok
-- -->  2   ok
-- -->  3   ok
--
-- @param n The number of calls at which func is no longer invoked.
-- @param func The function to restrict.
-- @return Returns the new restriced function.
_.before = function (n, func)
    local i = 1
    local result
    return function (...)
        if _.lte(i, n) then
            result = func(...)
        end
        i = i + 1
        return result
    end
end

---
-- Creates a function that runs each argument through a corresponding
-- transform function.
-- @usage local increment = function(...)
--     return _.args(_.map(_.table(...), function(n)
--         return n + 1
--     end))
-- end
-- local pow = function(...)
--     return _.args(_.map(_.table(...), function(n)
--         return n * n
--     end))
-- end
-- local modded = _.modArgs(function(...)
--     print(...)
-- end, {increment, increment}, pow)
-- modded(0, 1, 2)
-- -->  4   9   16
--
-- @param func The function to wrap
-- @param ... The functions to transform arguments, specified as
-- individual functions or arrays of functions.
-- @return Returns the new function.
_.modArgs = function (func, ...)
    local transforms = {}
    for i, v in ipairs( _.table(...)) do
        if _.isFunction(v) then
            _.push(transforms, v)
        elseif _.isTable(v) then
            for k2, v2 in _.iter(v) do
                if _.isFunction(v2) then _.push(transforms, v2) end
            end
        end
    end
    return function(...)
        local args
        for i, transform in ipairs(transforms) do
            if _.isNil(args) then
                args = _.table(transform(...))
            else
                args = _.table(transform(_.args(args)))
            end
        end
        if _.isNil(args) then
            return func(...)
        else
            return func(_.args(args))
        end
    end
end

---
-- Creates a function that negates the result of the predicate func.
-- The func predicate is invoked with arguments of the created function.
-- @usage local isEven = function (n)
--     return n % 2 == 0
-- end
-- local isOdd = _.negate(isEven)
-- _.print(_.filter({1, 2, 3, 4, 5, 6}, isEven))
-- --> {2, 4, 6}
-- _.print(_.filter({1, 2, 3, 4, 5, 6}, isOdd))
-- --> {1, 3, 5}
--
-- @param func The preadicate to negate.
-- @return Returns the new function
_.negate = function (func)
    return function(...)
        return not func(...)
    end
end

---
-- Creates a function that is restricted to invoking func once. Repeat
-- calls to the function return the value of the first call. The func
-- is invoked with arguments of the created function.
-- @usage local createApp = function(version)
--     print('App created with version '..version)
--     return version
-- end
-- local initialize = _.once(createApp)
-- initialize(1.1)
-- initialize(1.1)
-- initialize(1.1)
-- --> App created with version 1.1
-- --> 1.1
-- --> 1.1
-- --> 1.1
--
-- @param func The function to restrict.
-- @return Returns the new function.
_.once = function (func)
    local called = false;
    local result
    return function(...)
        if not called then
            result = func(...)
            called = true
        end
        return result
    end
end


---
-- Creates a function that invokes func with arguments arranged according
-- to the specified indexes where the argument value at the first index
-- is provided as the first argument, the argument value at the second
-- index is provided as the second argument, and so on.
-- @usage local rearged = _.rearg(function(a, b, c)
--   return {a, b, c};
-- end, 2, 1, 3)
-- _.print(rearged('a', 'b', 'c'))
-- --> {"b", "a", "c"}
-- _.print(rearged('b', 'a', 'c'))
-- --> {"a", "b", "c"}
--
-- @param func The function to rearrange arguments for.
-- @param ... The arranged argument indexes, specified as individual
-- indexes or arrays of indexes.
-- @return Returns the new function.
_.rearg = function (func, ...)
    local indexes = {}
    for i, v in ipairs(_.table(...)) do
        if _.isNumber(v) then
            _.push(indexes, v)
        elseif _.isTable(v) then
            for k2, v2 in _.iter(v) do
                if _.isNumber(v2) then _.push(indexes, v2) end
            end
        end
    end
    return function(...)
        local args = _.table(...)
        local newArgs = {}
        for i, index in ipairs(indexes) do
            _.push(newArgs, args[index])
        end
        if #indexes == 0 then
            return func(...)
        else
            return func(_.args(newArgs))
        end
    end
end



--- Lang
-- @section Lang
---

---
-- Cast value to arguments
-- @usage print(_.args({1, 2, 3}))
-- --> 1    2   3
--
-- @param value value to cast
-- @return Returns arguments
_.args = function (value)
    if _.isTable(value) then return table.unpack(value)
    else return table.unpack({value})
    end
end

---
-- Checks if value is greater than other.
-- @usage _.print(_.gt(1, 3))
-- --> false
-- _.print(_.gt(4, 3))
-- --> true
--
-- @param value The value to compare.
-- @param other The other value to compare.
_.gt = function (value, other, ...)
    local value, other = _.cast(value, other)
    if _.isString(value) or _.isNumber(value) then
        return value > other
    elseif _.isFunction(value) then
        return value(...) > other(...)
    end
    return false
end

---
-- Checks if value is greater than other.
-- @usage _.print(_.gte(1, 3))
-- --> false
-- _.print(_.gte(3, 3))
-- --> true
--
-- @param value The value to compare.
-- @param other The other value to compare.
_.gte = function (value, other, ...)
    if _.isNil(value) or _.isBoolean(value) then
        return value == other
    end
    local value, other = _.cast(value, other)
    if _.isString(value) or _.isNumber(value) then
        return value >= other
    elseif _.isFunction(value) then
        return value(...) >= other(...)
    elseif _.isTable(value) then
        return false
    end
    return false
end

---
-- Checks if value is classified as a boolean primitive.
-- @usage _.print(_.isBoolean(false))
-- --> true
-- _.print(_.isBoolean('x'))
-- --> false
--
-- @param value the value to check
-- @return Returns true if value is correctly classified, else false.
_.isBoolean = function(value)
    return type(value) == 'boolean'
end

---
-- Checks if value is empty. A value is considered empty unless it’s an
-- arguments table, array, string with a length greater than 0 or an
--object with own enumerable properties.
--@usage _.print(_.isEmpty(true))
-- --> true
-- _.print(_.isEmpty(1))
-- --> true
-- _.print(_.isEmpty({1, 2, 3}))
-- --> false
-- _.print(_.isEmpty({a= 1}))
-- --> false
--
-- @param value The value to inspect.
-- @return Returns true if value is empty, else false.
_.isEmpty = function (value)
    if _.isString(value) then
        return #value == 0
    elseif _.isTable(value) then
        local i = 0
        for k, v in _.iter(value) do
            i = i + 1
        end
        return i == 0
    else
        return true
    end
end

---
-- Checks if parm1 and parm2 are equal. Parm can be a boolean, string, number, function or table
---
--@usage _.print(_.isEqual(true))
-- --> true
-- _.print(_.isEmpty(1))
-- --> true
-- _.print(_.isEmpty({1, 2, 3}))
-- --> false
-- _.print(_.isEmpty({a= 1}))
-- --> false
--
-- @param parm1, parm2 The values to compare
-- @return Returns true if values are equal, else false.

_.isEqual = function (parm1, parm2)
    local avoid_loops = {}
    local function recurse(t1, t2)
        if type(t1) ~= type(t2) then return false end
        if type(t1) ~= "table" then return t1 == t2 end
        if avoid_loops[t1] then return avoid_loops[t1] == t2 end
        avoid_loops[t1] = t2
        local t2keys = {}
        local t2tablekeys = {}
        for k, _ in pairs(t2) do
            if type(k) == "table" then table.insert(t2tablekeys, k) end
            t2keys[k] = true
        end
        for k1, v1 in pairs(t1) do
            local v2 = t2[k1]
            if type(k1) == "table" then
                local ok = false
                for i, tk in ipairs(t2tablekeys) do
                    if _.isEqual(k1, tk) and recurse(v1, t2[tk]) then
                        table.remove(t2tablekeys, i)
                        t2keys[tk] = nil
                        ok = true
                        break
                    end
                end
                if not ok then return false end
            else
                if v2 == nil then return false end
                t2keys[k1] = nil
                if not recurse(v1, v2) then return false end
            end
        end
        if next(t2keys) then return false end
        return true
    end
    return recurse(parm1, parm2)
end


---
-- Checks if value is classified as a function primitive.
-- @usage _.print(_.isFunction(function() end))
-- --> true
-- _.print(_.isFunction(1))
-- --> false
--
-- @param value the value to check
-- @return Returns true if value is correctly classified, else false.
_.isFunction = function(value)
    return type(value) == 'function'
end

---
-- Checks if value is classified as a nil primitive.
-- @usage _.print(_.isNil(variable)
-- --> true
-- variable = 1
-- _.print(_.isNil(variable))
-- --> false
--
-- @param value the value to check
-- @return Returns true if value is correctly classified, else false.
_.isNil = function(value)
    return type(value) == 'nil'
end

---
-- Checks if value is classified as a number primitive.
-- @usage _.print(_.isNumber(1))
-- --> true
-- _.print(_.isNumber('1'))
-- --> false
--
-- @param value the value to check
-- @return Returns true if value is correctly classified, else false.
_.isNumber = function(value)
    return type(value) == 'number'
end

---
-- Checks if value is classified as a string primitive.
-- @usage _.print(_.isString('1'))
-- --> true
-- _.print(_.isString(1))
-- --> false
--
-- @param value the value to check
-- @return Returns true if value is correctly classified, else false.
_.isString = function(value)
    return type(value) == 'string'
end

---
-- Checks if value is classified as a table primitive.
-- @usage _.print(_.isTable({'1'}))
-- --> true
-- _.print(_.isString(1))
-- --> false
--
-- @param value the value to check
-- @return Returns true if value is correctly classified, else false.
_.isTable = function(value)
    return type(value) == 'table'
end

-- local implicitCast function (...)
--     if
-- end

---
-- Checks if value is less than other.
-- @usage _.print(_.lt(1, 3))
-- --> true
-- _.print(_.lt(3, 3))
-- --> false
--
-- @param value The value to compare.
-- @param other The other value to compare.
_.lt = function (value, other, ...)
    local value, other = _.cast(value, other)
    if _.isString(value) or _.isNumber(value) then
        return value < other
    elseif _.isFunction(value) then
        return value(...) < other(...)
    end
    return false
end

---
-- Checks if value is less than or euqal to other.
-- @usage _.print(_.lt(4, 3))
-- --> false
-- _.print(_.lt(3, 3))
-- --> true
-- @param value The value to compare.
-- @param other The other value to compare.
_.lte = function (value, other, ...)
    if _.isNil(value) or _.isBoolean(value) then
        return value == other
    end
    local value, other = _.cast(value, other)
    if _.isString(value) or _.isNumber(value) then
        return value <= other
    elseif _.isFunction(value) then
        return value(...) <= other(...)
    elseif _.isTable(value) then
        return false
    end
    return false
end

_.cast = function (a, b)
    if type(a) == type(b) then return a, b end
    local cast
    if _.isString(a) then cast = _.str
    elseif _.isBoolean(a) then cast = _.bool
    elseif _.isNumber(a) then cast = _.num
    elseif _.isFunction(a) then cast = _.func
    elseif _.isTable(a) then cast = _.table
    end
    return a, cast(b)
end

---
-- Cast parameters to a function that return passed parameters.
-- @usage local f = _.func(1, 2, 3)
-- _.print(f())
-- --> 1    2   3
--
-- @param value value to cast
-- @param ... The parameters to pass to any detected function
-- @return casted value
_.func = function (...)
    local t = _.table(...)
    return function ()
        return _.args(t)
    end
end

---
-- Cast parameters to table using table.pack
-- @usage print(_.table(1, 2, 3))
-- --> {1, 2, 3}
-- print(_.table("123"))
-- --> {"123"}
-- print(_.table(0))
-- --> {0}
--
-- @param value value to cast
-- @param ... The parameters to pass to any detected function
-- @return casted value
_.table = function (...)
    return table.pack(...)
end

---
-- Cast anything to boolean. If any function detected, call and cast its
-- result. Return false for 0, nil, table and empty string.
-- @usage print(_.bool({1, 2, 3}))
-- --> false
-- print(_.bool("123"))
-- --> true
-- print(_.bool(0))
-- --> false
-- print(_.bool(function(a) return a end, "555"))
-- --> true
--
-- @param value value to cast
-- @param ... The parameters to pass to any detected function
-- @return casted value
_.bool = function (value, ...)
    local bool = false
    if _.isString(value) then
        bool = #value > 0
    elseif _.isBoolean(value) then
        bool = value
    elseif _.isNumber(value) then
        bool = value ~= 0
    elseif _.isFunction(value) then
        bool = _.bool(value(...))
    end
    return bool
end

---
-- Cast anything to number. If any function detected, call and cast its
-- result. Return 0 for nil and table.
-- @usage print(_.num({1, 2, 3}))
-- --> 0
-- print(_.num("123"))
-- --> 123
-- print(_.num(true))
-- --> 1
-- print(_.num(function(a) return a end, "555"))
-- --> 555
--
-- @param value value to cast
-- @param ... The parameters to pass to any detected function
-- @return casted value
_.num = function (value, ...)
    local num = 0
    if _.isString(value) then
        ok = pcall(function()
            num = value + 0
        end)
        if not ok then
            num = math.huge
        end
    elseif _.isBoolean(value) then
        num = value and 1 or 0
    elseif _.isNumber(value) then
        num = value
    elseif _.isFunction(value) then
        num = _.num(value(...))
    end
    return num
end


local dblQuote = function (v)
    return '"'..v..'"'
end

---
-- Cast anything to string. If any function detected, call and cast its
-- result.
-- @usage print(_.str({1, 2, 3, 4, {k=2, {'x', 'y'}}}))
-- --> {1, 2, 3, 4, {{"x", "y"}, ["k"]=2}}
-- print(_.str({1, 2, 3, 4, function(a) return a end}, 5))
-- --> {1, 2, 3, 4, 5}
--
-- @param value value to cast
-- @param ... The parameters to pass to any detected function
-- @return casted value
_.str = function (value, ...)
    local str = '';
    -- local v;
    if _.isString(value) then
        str = value
    elseif _.isBoolean(value) then
        str = value and 'true' or 'false'
    elseif _.isNil(value) then
        str = 'nil'
    elseif _.isNumber(value) then
        str = value .. ''
    elseif _.isFunction(value) then
        str = _.str(value(...))
    elseif _.isTable(value) then
        str = '{'
        for k, v in pairs(value) do
            v = _.isString(v) and dblQuote(v) or _.str(v, ...)
            if _.isNumber(k) then
                str = str .. v .. ', '
            else
                str = str .. '[' .. dblQuote(k) .. ']=' .. v .. ', '
            end
        end
        str = str:sub(0, #str - 2) .. '}'
    end
    return str
end


--- Number
-- @section Number

---
-- Checks if n is between start and up to but not including, end.
-- If end is not specified it’s set to start with start then set to 0.
-- @usage print(_.inRange(-3, -4, 8))
-- --> true
--
-- @param n The number to check.
-- @param start The start of the range.
-- @param stop The end of the range.
-- @return Returns true if n is in the range, else false.
_.inRange = function (n, start, stop)
    local _start = _.isNil(stop) and 0 or start or 0
    local _stop = _.isNil(stop) and start or stop or 1
    return n >= _start and n < _stop
end

---
-- Produces a random number between min and max (inclusive).
-- If only one argument is provided a number between 0 and the given
-- number is returned. If floating is true, a floating-point number is
-- returned instead of an integer.
-- @usage _.print(_.random())
-- --> 1
-- _.print(_.random(5))
-- --> 3
-- _.print(_.random(5, 10, true))
-- --> 8.8120200577248
--
-- @param[opt=0] min the minimum possible value.
-- @param[opt=1] max the maximum possible value.
-- @param[opt=false] floating Specify returning a floating-point number.
-- @return Returns the random number.
_.random = function (min, max, floating)
    local minim = _.isNil(max) and 0 or min or 0
    local maxim = _.isNil(max) and min or max or 1
    math.randomseed(os.clock() * math.random(os.time()))
    local r = math.random(minim, maxim)
    if floating then
        r = r + math.random()
    end
    return r
end

---
-- Rounds a float number to the required number of decimals
-- If only one argument is provided it's round to an integer
-- @usage _print(_.round(math.pi)
-- --> 3
-- _.print(_.round(math.pi, 2)
-- --> 3.14
--
-- @param the float to convert
-- @param[opt=1] the number of deceimals to round to
-- @return Returns the round number.
_.round = function (x, n)
    x = tonumber(x)
    if not(_.isNumber(x)) then return x end
    n = 10^(n or 0)
	x = x * n
	if x >= 0 then
		x = math.floor(x + 0.5)
	else
		x = math.ceil(x - 0.5)
	end
	return x / n
end


--- Object
-- @section Object

---
-- Gets the property value at path of object. If the resolved value
-- is nil the defaultValue is used in its place.
-- @usage local object = {a={b={c={d=5}}}}
-- _.print(_.get(object, {'a', 'b', 'c', 'd'}))
-- --> 5
--
-- @param object The object to query.
-- @param path The path of the property to get.
-- @param[opt=nil] defaultValue The value returned if the resolved value is nil.
-- @return Returns the resolved value.
_.get = function (object, path, defaultValue)
    if _.isTable(object) then
        local value = object
        local c = 1
        while not _.isNil(path[c]) do
            if not _.isTable(value) then return defaultValue end
            value = value[path[c]]
            c = c + 1
        end
        return value or defaultValue
    elseif _.isString(object) then
        local index = path[1]
        return object:sub(index, index)
    end
end

---
-- Checks if path is a direct property.
-- @usage local object = {a={b={c={d}}}}
-- print(_.has(object, {'a', 'b', 'c'}))
-- --> true
--
-- @param object The object to query
-- @param path The path to check (Array)
_.has = function (object, path)
    local obj = object
    local c = 1
    local exist = true
    while not _.isNil(path[c]) do
        obj = obj[path[c]]
        if _.isNil(obj) then
            exist = false
            break
        end
        c = c + 1
    end
    return exist
end

---
-- This method is like _.find except that it returns the key of the
-- first element predicate returns truthy for instead of the element itself.
-- @usage _.print(_.findKey({a={a = 1}, b={a = 2}, c={a = 3, x = 1}},
-- function(v)
--     return v.a == 3
-- end))
-- --> c
--
-- @param object The collection to search. (table|string)
-- @param predicate The function invoked per iteration
-- @param selfArg The self binding of predicate.
-- @return Returns the key of the matched element, else nil
_.findKey = function (object, predicate, selfArg)
    for k, v in _.iter(object) do
        if callIteratee(predicate, selfArg, v, k, object) then
            return k
        end
    end
end

---
-- This method is like _.find except that it returns the key of the
-- first element predicate returns truthy for instead of the element itself.
-- @usage _.print(_.findLastKey({a={a=3}, b={a = 2}, c={a=3, x = 1}},
-- function(v)
--     return v.a == 3
-- end))
-- --> c
--
-- @param object The object to search. (table|string)
-- @param predicate The function invoked per iteration
-- @param selfArg The self binding of predicate.
-- @return Returns the key of the matched element, else nil
_.findLastKey = function (object, predicate, selfArg)
    for k, v in _.iterRight(object) do
        if callIteratee(predicate, selfArg, v, k, object) then
            return k
        end
    end
end

---
-- Creates an array of function property names from all enumerable
-- properties, own and inherited, of object.
-- @usage _.print(_.functions(table))
-- --> {"concat", "insert", "maxn", "pack", "remove", "sort", "unpack"}
--
-- @param object The object to inspect.
-- @return Returns the new array of property names.
_.functions = function(object)
    local t = {}
    for k, v in _.iter(object) do
        if _.isFunction(v) then
            _.push(t, k)
        end
    end
    return t
end

---
-- Creates an object composed of the inverted keys and values of object.
-- If object contains duplicate values, subsequent values overwrite
-- property assignments of previous values unless multiValue is true.
-- @usage _.print(_.invert({a='1', b='2', c='3', d='3'}))
-- --> {[2]="b", [3]="d", [1]="a"}
-- _.print(_.invert({a='1', b='2', c='3', d='3'}, true))
-- --> {[2]="b", [3]={"c", "d"}, [1]="a"}
--
-- @param object The object to invert.
-- @param multiValue Allow multiple values per key.
-- @return Returns the new inverted object.
_.invert = function (object, multiValue)
    local t = {}
    for k, v in _.iter(object) do
        if multiValue and not _.isNil(t[v]) then
            t[v] = { t[v] }
            _.push(t[v], k)
        else
            t[v] = k
        end
    end
    return t

end

local getSortedKeys = function(collection, desc)
    local sortedKeys = {}
    for k, v in pairs(collection) do
        table.insert(sortedKeys, k)
    end
    if desc then
        table.sort(sortedKeys, _.gt)
    else
        table.sort(sortedKeys, _.lt)
    end
    return sortedKeys
end

---
-- Creates an array of the own enumerable property names of object.
-- @usage _.print(_.keys("test"))
-- --> {1, 2, 3, 4}
-- _.print(_.keys({a=1, b=2, c=3}))
-- --> {"c", "b", "a"}
--
-- @param object The object to query. (table|string)
-- @return Returns the array of property names.
_.keys = function (object)
    if _.isTable(object) then
        return getSortedKeys(object)
    elseif _.isString(object) then
        local keys = {}
        for i=1, #object do
            keys[i] = i
        end
        return keys
    end
end

---
-- Creates a two dimensional array of the key-value pairs for object.
-- @usage  _.print(_.pairs({1, 2, 'x', a='b'}))
-- --> {{1, 1}, {2, 2}, {3, "x"}, {"a", "b"}}
--
-- @param object The object to query
-- @return Returns the new array of key-value pairs.
_.pairs = function (object)
    local t = {}
    for k, v in _.iter(object) do
        _.push(t, {k, v})
    end
    return t
end

---
-- This method is like _.get except that if the resolved value is a
-- function it’s invoked with additional parameters and its result is returned.
-- @usage local object = {a=5, b={c=function(a) print(a) end}}
-- _.result(object, {'b', 'c'}, nil, 5)
-- --> 5
--
-- @param object The object to query.
-- @param path The path of the property to get.
-- @param[opt=nil] defaultValue The value returned if the resolved value is nil.
-- @param ... Additional parameters to pass to function
-- @return Returns the resolved value.
_.result = function (object, path, defaultValue, ...)
    local result = _.get(object, path, defaultValue)
    if _.isFunction(result) then
        return result(...)
    else
        return result
    end
end


---
-- Creates an array of the own enumerable property values of object.
-- @usage _.print(_.values("test"))
-- --> {"t", "e", "s", "t"}
-- _.print(_.values({a=1, b=2, c=3}))
-- --> {1, 3, 2}
--
-- @param object The object to query. (table|string)
-- @return Returns the array of property values.
_.values = function (object)
    local t = {}
    for k, v in _.iter(object) do
        _.push(t, v)
    end
    return t
end

--- String
-- @section String


--- Utility
-- @section Utility

---
-- Creates a function that returns value.
-- @usage local object = {x=5}
-- local getter = _.constant(object)
-- _.print(getter() == object);
-- --> true
--
-- @param value Any value.
-- @return Returns the new function.
_.constant = function(value)
    return _.func(value)
end

---
-- This method returns the first argument provided to it.
-- @usage local object = {x=5}
-- _.print(_.identity(object) == object);
-- --> true
--
-- @param value Any value.
-- @return Returns value.
_.identity = function(...) return ... end

local iterCollection = function(table, desc)
    local sortedKeys = getSortedKeys(table, desc)
    local i = 0
    return function ()
        if _.lt(i, #sortedKeys) then
            i = i + 1
            local key = sortedKeys[i]
            return key, table[key]
        end
    end
end

_.iter = function(value)
    if _.isString(value) then
        local i = 0
        return function()
            if _.lt(i, #value) then
                i = i + 1
                local c = value:sub(i, i)
                return i, c
            end
        end
    elseif _.isTable(value) then
        return iterCollection(value)
    else
        return function() end
    end
end

_.iterRight = function(value)
    if _.isString(value) then
        local i = #value + 1
        return function()
            if _.gt(i, 1) then
                i = i - 1
                local c = value:sub(i, i)
                return i, c
            end
        end
    elseif _.isTable(value) then
        return iterCollection(value, true)
    else
        return function() end
    end
end

---
-- A no-operation function that returns nil regardless of the arguments
-- it receives.
-- @usage local object = {x=5}
-- _.print(_.noop(object) == nil);
-- --> true
--
-- @param ... Any arguments
-- @return Returns nil
_.noop = function(...) return nil end


---
-- Print more readable representation of arguments using _.str
-- @usage _.print({1, 2, 3, 4, {k=2, {'x', 'y'}}})
-- --> {1, 2, 3, 4, {{"x", "y"}, [k]=2}}
--
-- @param ... values to print
-- @return Return human readable string of the value
_.print = function(...)
    local t = _.table(...)
    for i, v in ipairs(t) do
        t[i] = _.str(t[i])
    end
    return print(_.args(t))
end

---
-- Creates an array of numbers (positive and/or negative) progressing
-- from start up to, including, end.
-- If end is not specified it’s set to start with start then set to 1.
-- @usage _.print(_.range(5, 20, 3))
-- --> {5, 8, 11, 14, 17, 20}
--
-- @param[opt=1] start The start of the range.
-- @param stop Then end of the range.
-- @param[opt=1] step The value to increment or decrement by
-- @return Returns the new array of numbers
_.range = function(start, ...)
    local start = start
    local args = _.table(...)
    local a, b, c
    if #args == 0 then
        a = 1   -- according to lua
        b = start
        c = 1
    else
        a = start
        b = args[1]
        c = args[2] or 1
    end
    local t = {}
    for i = a, b, c do
        _.push(t, i)
    end
    return t
end

return _

