local a={}local C={}function a.__call(a,P)return C:new(P)end setmetatable(C,a)local function P(a)local C=0 return function()C=C+1 if C>#a then return nil else return a[C]end end end function C.new(B,a)if type(a)=="\116\097\098\108\101"then local C=a a=function()return P(C)end end local Z={}Z.list=a setmetatable(Z,C)B.__index=B Z.__index=Z Z.__call=function()return Z.source()end return Z end function C.print(a)for a in a.list()do print(a)end end local B=type(function() end)local Z=type({})local L=type("")local function y(a)local C=type(a)if C==B then return a elseif C==L then local C,P=string.find(a,"-\062")return(loadstring("\114\101\116\117\114\110 \102\117\110\099\116\105\111\110\040"..(string.sub(a,1,C-1)..("\041 \114\101\116\117\114\110 "..(string.sub(a,P+1).." \101\110\100")))))()end error("\073\110\118\097\108\105\100 \108\097\109\098\100\097 \116\121\112\101")end function C.where(P,a)local B=y(a)local Z=P.list return C:new(function()local a=Z()return function()local C repeat C=a()if C==nil then return nil end until B(C)return C end end)end function C.select(P,a)local B=y(a)local Z=P.list return C:new(function()local a=Z()return function()local C=a()if C==nil then return nil end return B(C)end end)end function C.selectMany(C,a)error("\110\111\116 \105\109\112\108\101\109\101\110\116\101\100")end function C.first(C,a)local P=C.list()local B=P()if B==nil then error("\073\116\101\114\097\116\111\114 \119\097\115 \101\109\112\116\121")end if a then while not a(B)do B=P()if B==nil then error("\073\116\101\114\097\116\111\114 \119\097\115 \101\109\112\116\121")end end end return B end function C.firstOrDefault(C,a)local P=C.list()local B=P()if B==nil then return nil end if a then while not a(B)do B=P()if B==nil then return nil end end end return B end function C.single(C,a)local P=C:list()local B=P()if B==nil then error("\073\116\101\114\097\116\111\114 \119\097\115 \101\109\112\116\121")return nil else if P()~=nil then error("\073\116\101\114\097\116\111\114 \099\111\110\116\097\105\110\101\100 \109\111\114\101 \116\104\097\110 \049 \105\116\101\109")end end return B end function C.orderBy(C,a)error("\110\111\116 \105\109\112\108\101\109\101\110\116\101\100")end function C.orderByDescending(C,a)error("\110\111\116 \105\109\112\108\101\109\101\110\116\101\100")end function C.skip(P,a)local B=P.list return C:new(function()local C=B()local P=false local Z=0 return function()while Z<a do Z=Z+1 if C()==nil then return nil end end return C()end end)end function C.take(P,a)local B=P.list return C:new(function()local C=B()local P=0 return function()if P<a then P=P+1 return C()else return nil end end end)end function C.aggregate(P,a,C)a=y(a)local B=P.list()local Z=B()while Z~=nil do C=a(Z,C)Z=B()end return C end function C.toDictionary(P,a,C)local B=C or function(a)return a end local Z={}local L=P.list()local y=L()while y~=nil do Z[a(y)]=B(y)y=L()end return Z end function C.toTable(a)local C={}local P=a.list()local B=P()while B~=nil do C[#C+1]=B B=P()end return C end function C.range(a,C)return function()local P=a-1 return function()P=P+1 if P<=C then return P end return nil end end end return C
local mt = {}
local query = {}

function mt.__call(self, source)
    return (query:new(source)) 
end
setmetatable(query, mt)

local function tableIter(t)
    local i = 0
    return function()
        i = i + 1
        if i > #t then
            return nil
        else
            return t[i]
        end
    end
end

function query:new(source)
    if type(source) == "table" then
        local tab = source
        source = function() 
            return tableIter(tab) 
        end
    end

    local t = {}    
    t.list = source
    
    setmetatable(t, query)
    self.__index = self    
    t.__index = t
    t.__call = function() return t.source() end
    return t  
end

--[[
    local q = query{1, 2, 3, 4}

    q:print() -- 1, 2, 3, 4

    local q = query{1, 2, 3, 4}
    for item in q.list() do
        print(item) -- 1, 2, 3, 4
    end
end
]]
function query:print()
    for i in self.list() do
      print(i)
    end
end



local typeoffunc = type(function() end)
local typeoftable = type({})
local typeofstring = type("")

local function interpret(lambda)
    local lambtype = type(lambda)
    if lambtype == typeoffunc then
        return lambda
    elseif lambtype == typeofstring then
        local argsEnd, expStart = string.find(lambda, "->")
        return loadstring("return function("..string.sub(lambda, 1, argsEnd - 1)..") return "..string.sub(lambda, expStart + 1).." end")()
    end
    error("Invalid lambda type")
end

--[[
    Example:     
    -- using predicate func
    function even(value)
        return value % 2 == 0
    end

    local q = query{1, 2, 3, 4}:where(even)
    
    q:print() -- 2, 4

    -- anon predicate func
    local q = query{1, 2, 3, 4}:where(function (v) return v % 2 == 0 end)

    q:print() -- 2, 4

]]
function query:where(predicate)    
    local predicate = interpret(predicate)    
    local parent = self.list
    return query:new(function ()
        local iter = parent()
        return function()
            local value
            repeat
                value = iter()
                if value == nil then
                    return nil
                end
            until predicate(value)
            return value
        end
    end)
end

--[[
    Examples:
    -- With named predicate func
    function square(value)
        return value * value
    end

    local q = query{1, 2, 3, 4}:select(square)

    q:print() -- 1, 4, 9, 16

    -- lambda string interp
    local q = query(query.range(1, 10)):select('v -> v * v')
                                       :select('v -> tostring(v)')
                                       :where('s -> string.len(s) ~= 2')
    
    q:print() -- 1, 4, 9, 100
]]
function query:select(func)
    local func = interpret(func)    
    local parent = self.list
    return query:new(function ()
        local iter = parent()
        return function()
            local value = iter()
            if value == nil then
                return nil
            end
            return func(value)        
        end
    end)
end

-- if the results of iter are iterators or tables, select from their items
function query:selectMany(func)
    error("not implemented")
end

function query:first(predicate)        
    local iter = self.list()
    local value = iter()
    if value == nil then
        error("Iterator was empty")        
    end
    if predicate then
        while not predicate(value) do
            value = iter()
            if value == nil then
                error("Iterator was empty")                
            end
        end
    end    
    return value    
end

function query:firstOrDefault(predicate)    
    local iter = self.list()
    local value = iter()
    if value == nil then        
        return nil
    end
    if predicate then
        while not predicate(value) do
            value = iter()
            if value == nil then                
                return nil
            end
        end
    end    
    return value            
end

function query:single(predicate)
    local iter = self:list()
    local value = iter()
    if value == nil then
        error("Iterator was empty")
        return nil
    else
        if iter() ~= nil then
            error("Iterator contained more than 1 item")
        end            
    end
    return value    
end

function query:orderBy(ranker)
    error("not implemented")
end

function query:orderByDescending(ranker)
    error("not implemented")
end

function query:skip(num)
    local parent = self.list
    return query:new(function ()
        local iter = parent()
        local completedEarly = false
        local count = 0
        return function()
            while count < num do
                count = count + 1
                if iter() == nil then
                    return nil
                end
            end
            return iter()
        end
    end)
end

function query:take(num)
    local parent = self.list
    return query:new(function ()
        local iter = parent()
        local count = 0
        return function()
            if count < num then
                count = count + 1
                return iter()
            else
                return nil
            end
        end
    end)
end

function query:aggregate(merge, init)  
    merge = interpret(merge)
    local iter = self.list()    
    local result = iter()
    while result ~= nil do
        init = merge(result, init)
        result = iter()
    end
    return init    
end

function query:toDictionary(keySelector, valueSelector)
    local valueSelector = valueSelector or (function(v) return v end)
    local dict = {}
    local iter = self.list()
    local result = iter()
    while result ~= nil do
        dict[keySelector(result)] = valueSelector(result)
        result = iter()
    end
    return dict
end

function query:toTable()
    local resultTable = {}
    local iter = self.list()
    local result = iter()
    while result ~= nil do
        resultTable[#resultTable + 1] = result
        result = iter()
    end
    return resultTable
end

function query.range(from, to)
    return function()
        local n = from - 1
        return function()
            n = n + 1
            if n <= to then
                return n
            end
            return nil
        end
    end
end

return query