#!/usr/bin/env lua
                                                                   
-- Ensure lamx.so can be imported from /usr/local/lib
package.cpath = "/usr/local/lib/lua/" .. (_VERSION):gsub("Lua ", "") .. "/?.so;" .. package.cpath

local lamx = require 'lamx'
local functions = { }
local data
local options = { }
local first_arg = 1

-- Only keep command line arguments (remove lua)
arg[-1] = nil

-- Get commandline option
while arg[first_arg] ~= nil and string.sub(arg[first_arg], 1, 1) == '-' do
    if arg[first_arg] == "--wait-time" then
        first_arg = first_arg + 1
        if arg[first_arg] == nil or tonumber(arg[first_arg]) == nil then
            print("\27[31mMissing value or invalid value for --wait-time option\27[0m")
            os.exit(1)
        end
        options.time_out = tonumber(arg[first_arg])
        first_arg = first_arg + 1
    elseif arg[first_arg] == "--stop-on-event" then
        options.stop_on_event = true
        first_arg = first_arg + 1
    elseif arg[first_arg] == "--stop-on-event-count" then
        first_arg = first_arg + 1
        if arg[first_arg] == nil  or tonumber(arg[first_arg]) == nil then
            print("\27[31mMissing value for --stop-on-event-count option\27[0m")
            os.exit(1)
        end
        options.event_count = tonumber(arg[first_arg])
        if options.event_count <= 0 then
            print("\27[31mInvalid value for --stop-on-event-count option, must be higher than 0\27[0m")
            os.exit(1)
        end
        first_arg = first_arg + 1
    end
end

function string:split(sep)
    local sep, fields = sep or ":", {}
    local pattern = string.format("([^%s]+)", sep)
    self:gsub(pattern, function(c) fields[#fields+1] = c end)
    return fields
end

local function parse_parameters(index) 
    local parameters = {}

    while arg[index] ~= nil do
        local splitted = arg[index]:split("=");
        local key = splitted[1] 
        table.remove(splitted,1)
        local value = table.concat(splitted, "=")

        parameters[key] = value
        index = index + 1
    end

    return parameters
end

local function run_command()
    local data
    cmd = arg[first_arg]
    first_arg = first_arg + 1
    data = functions[cmd]()
    if data ~= nil then 
        print(lamx.json.create(data))
    end
end

functions.help = function()
    print("Usage: " .. arg[0] .. " [OPTIONS] <CMD> <ARGS>")
    print("")
    print("Options:")
    print("    --wait-time <TIME>            : timeout time in seconds, only applicable for wait_for and subscribe")
    print("    --stop-on-event               : stop when event is received, only applicable for subscribe")
    print("    --stop-on-event-count <COUNT> : stop when event count is reached, only applicable for subscribe")
    print("")
    print("Available commands:")
    print("    help, who_has, exists, resolve, get,")
    print("    gsdm, gidm, set, add, del, call, wait_for")
    print("")
    print("    who_has <OBJECT PATH>")
    print("    exists <OBJECT PATH>")
    print("    resolve <OBJECT PATH>")
    print("    get <OBJECT PATH> [<DEPTH>]")
    print("    gsdm <OBJECT PATH>")
    print("    gidm <OBJECT PATH>")
    print("    describe <OBJECT PATH>")
    print("    set <OBJECT PATH> <PARAMETER NAME>=<VALUE> [<PARAMETER NAME>=<VALUE> ...]")
    print("    add <OBJECT PATH> <PARAMETER NAME>=<VALUE> [<PARAMETER NAME>=<VALUE> ...]")
    print("    del <OBJECT PATH>")
    print("    call <OBJECT PATH> <METHOD> [<ARGUMENT NAME>=<VALUE> ...]")
    print("    wait_for <OBJECT PATH> [<OBJECT PATH> ...]")
    print("    subscribe <OBJECT PATH> [<FILTER>]")
    print("")
    print("The following commands support search and wildcard paths")
    print("  - get")
    print("  - set")
    print("  - add")
    print("  - del")
    print("  - call")
    print("")
end

functions.who_has = function()
    local path = arg[first_arg]

    assert(path ~= nil, "Missing object path.")

    return lamx.bus.who_has(path)    
end

functions.exists = function()
    local path = arg[first_arg]

    assert(path ~= nil, "Missing object path.")

    return lamx.bus.exists(path)    
end

functions.resolve = function()
    local path = arg[first_arg]
    
    assert(path ~= nil, "Missing object path.")

    return lamx.bus.resolve(path)    
end

functions.get = function()
    local path = arg[first_arg]
    local depth = arg[first_arg + 1] or -1
    
    assert(path ~= nil, "Missing object path.")

    return lamx.bus.get(path, depth)
end

functions.gsdm = function()
    local path = arg[first_arg]
    
    assert(path ~= nil, "Missing object path.")

    return lamx.bus.get_supported_dm(path)
end

functions.gidm = function()
    local path = arg[first_arg]
    
    assert(path ~= nil, "Missing object path.")

    return lamx.bus.get_instantiated_dm(path)
end

functions.describe = function()
    local path = arg[first_arg]
    
    assert(path ~= nil, "Missing object path.")

    return lamx.bus.describe(path)
end

functions.set = function()
    local path = arg[first_arg]
    local parameters = {}

    assert(path ~= nil, "Missing object path.")
    assert(arg[index] ~= nil, "Missing parameters.")

    parameters = parse_parameters(first_arg + 1)
    return lamx.bus.set(path, parameters)
end

functions.add = function()
    local path = arg[first_arg]
    local parameters = {}

    assert(path ~= nil, "Missing object path.")
    assert(arg[index] ~= nil, "Missing parameters.")

    parameters = parse_parameters(first_arg + 1)
    return lamx.bus.add(path, parameters)
end

functions.del = function()
    local path = arg[2]

    assert(path ~= nil, "Missing object path.")

    return lamx.bus.del(path)
end

functions.call = function()
    local path = arg[first_arg]
    local method = arg[first_arg + 1]
    local args = {}

    assert(path ~= nil, "Missing object path.")
    assert(method ~= nil, "Missing method.")

    args = parse_parameters(first_arg + 2)
    return lamx.bus.call(path, method, args)
end

functions.wait_for = function()
    local el = lamx.eventloop.new()
    local index = first_arg

    assert(arg[index] ~= nil, "Missing object path.")
    lamx.bus.wait_for(arg[index], function() el:stop() end)

    index = index + 1
    while arg[index] ~= nil do
        lamx.bus.wait_for(arg[index])
        index = index + 1
    end

    el:start(options.time_out or 0)
end

functions.subscribe = function()
    local el = lamx.eventloop.new()
    local path = arg[first_arg]
    local filter = arg[first_arg + 1] or ""
    local events = 0

    local print_event_data = function(event, data)
        events = events + 1
        print(lamx.json.create(data))
        if options.stop_on_event then
            el:stop()
        end
        if options.event_count then
            options.event_count = options.event_count - 1
            if options.event_count <= 0 then
                el:stop()
            end
        end
    end

    local subscription = lamx.bus.subscribe(path, print_event_data, filter)
    el:start(options.time_out or 0)
end

--[[ 
-- The folowing functions need an event loop
-- Should they be added?
---------------------------------------------
functions.async_call = function()
    print("Not implemented yet.")
end
---------------------------------------------
--]]


lamx.auto_connect()

if functions[arg[first_arg]] == nil then
    local line_nr = 0;
    for line in io.lines() do
        line_nr = line_nr + 1
        arg = line:split(" ");
        first_arg = 1
        if functions[arg[first_arg]] == nil then
            print("\27[31mInvalid command '" .. arg[1] .. "' at line " .. tostring(line_nr) .. "\27[0m")
        else 
            run_command()
        end
    end
    if line_nr == 0 then
        print("")
        print("\27[31mNo command or invalid provided.\27[0m")
        functions["help"]()
    end
else
    run_command()
end

lamx.disconnect_all()
