function init(opts)
    tex.print([[\newwrite\timetableATdataOutput \immediate\openout\timetableATdataOutput=\jobname-data.dat]])
    tex.print([[\immediate\write\timetableATdataOutput{return \iftrue\string{\else}\fi}]])
    if(not checkKeys(opts, {"days", "min", "max", "dayse"})) then
        error("missing argument")
    end
    -- clean up first
    -- global variables
    EVENTS={}
    DAYS  = prepareDays(opts.days) -- header with names of the days set from tex currently
    DAYSE = prepareDays(opts.dayse) -- day representation in source code
    MIN = 25*60 -- bigger than any allowed value could be
    MAX = 0
    MIN_BYPASS = false -- weather min is fixed by the user
    MAX_BYPASS = false -- weather max is fixed by the user

    if(opts.min == "") then
    else
        assert(opts.min:match("^%d+"), "start time has to be an integer representing the HH*60+MM of the desired start time")
        MIN = tonumber(opts.min)
        MIN_BYPASS = true
    end

    if(opts.max == "") then
    else
        assert(opts.max:match("^%d+"), "end time has to be an integer representing the HH*60+MM of the desired end time")
        MAX = tonumber(opts.max)
        MAX_BYPASS = true
    end
end

function defaultFormatter(opts)
    local ret = ""
    for k,v in pairs(opts) do
        if type(k) == "string" then k = k:gsub("[_^]", "") end
        if type(v) == "string" then v = v:gsub("[_^]", "") end
        ret = string.format("%s, %s: %s", ret, tostring(k), tostring(v))
    end
    -- print(ret)
    return ret
end

function timetableformatter(opts)
    return string.format(
        [[\textcolor{%s}{\textbf{%s}\\[.2em]\raggedright{%s}\\[0.5em]\raggedright{%s}\hfil\raggedright{%s}\\[0.5em]\raggedright{%s}}]],
            opts.textcolor, opts.title, opts.speaker, opts.prio, opts.location, opts.time)
end
-- result are the global variables EVENTS, MIN and MAX
function addEvent(opts)
    -- print("Reading event on line ", tex.inputlineno)
    opts.inputlineno = tex.inputlineno
    if(not checkKeys(opts, {"time", "day", "tikz"})) then
        error("missing argument")
    end

    if opts.content == nil then
        if opts.formatter == nil then
            opts.content = defaultFormatter(opts)
        else
            opts.content = opts.formatter(opts)
        end
    end

    opts.from,opts.to = dur2Int(opts.time)

    tex.print(string.format(
        [[\immediate\write\timetableATdataOutput{\unexpanded{{["start"]=%q, ["end"]=%q, ["title"]=%q, ["location"]=%q, ["password"]=%q, ["type"]=%q},}}]],
        opts.from + 24*60*day2Int(opts.day),
        opts.to + 24*60*day2Int(opts.day),
        opts.title,
        opts.location:match([[\href{(.*)}{.*}]]) or opts.location,
        opts.password,
        opts.type
    ))

    if(not MIN_BYPASS and opts.from < MIN) then MIN = opts.from end
    if(not MAX_BYPASS and opts.to   > MAX) then MAX = opts.to   end
    assert(opts.from < opts.to, "From has to be before to")

    table.insert(EVENTS, opts)
end
-- parameters are all global variables
function draw(length, width)
    -- copy relevant variables for working on local copies
    local events = copy_array(EVENTS)
    local days = copy_array(DAYS)
    local min, minH, max, maxH = prepareMinMax(MIN, MAX)

    assert(length:match("%d*%.?%d*"), "Length must be a valid length measured in cm")
    length = tonumber(length)

    textwidth = width

    tex.print([[\begin{tikzpicture}]])
    tex.print([[\tikzset{defStyle/.style={font=\tiny,anchor=north west,fill=blue!50,draw=black,rectangle}}]])
    -- print the tabular with the weekday headers
    tex.print(string.format(
        [[\foreach \week [count=\x from 0, evaluate=\x as \y using \x+0.5] in {%s}{ ]],
        table.concat(days, ",")
        )
    )
    tex.print(string.format(
        [[\node[anchor=south] at (\y/%d* %s, 0) {\week};]], #days, textwidth))
    tex.print(string.format(
        [[\draw (\x/%d * %s, 0cm) -- (\x/%d * %s, %dcm);]],
        #days,
        textwidth,
        #days,
        textwidth, -length
        )
    )
    tex.print("}")
    tex.print(string.format(
        [[\draw (%s, 0) -- (%s,%dcm);]],
        textwidth,
        textwidth,
        -length
        )
    )

    for i=minH,maxH do
        tex.print(string.format(
            [[\node[anchor=east] at (0,%fcm ) {%d:00};]],
            minuteToFrac(i*60,min,max)*-length, i
            )
        )
        tex.print(string.format(
            [[\draw (0,%fcm ) -- (%s,%fcm );]],
            minuteToFrac(i*60,min,max)*-length,
            textwidth,
            minuteToFrac(i*60,min,max)*-length
            )
        )
    end

    local d
    local red = 0.3333 -- calculated in em from inner sep
    local red_y = 0.25 -- calculated in em
    for _,e in ipairs(events) do
        if e.from < max and e.to > min then -- only draw if event is in scope (part of the comp is done in addEvent from < to
            if e.to   > max then e.to   = max end
            if e.from < min then e.from = min end
            -- print("Drawing event on line ", e.inputlineno)
            d = day2Int(e.day)
            tex.print(string.format(
                [[\node[defStyle,text width=-%fem+%f%s/%d, text depth=%fcm-%fem, text height=%fem, %s] at (%f*%s,%fcm) {%s};]],
                2*red, -- text width
                e.scale_width, -- text width
                textwidth,
                #days, -- text width
                length*(e.to-e.from)/(max-min), -- text depth
                2*red+red_y, -- text depth
                red_y, -- text height
                e.tikz, -- free tikz code
                (d+e.offset)/#days, -- xcoord
                textwidth,
                minuteToFrac(e.from,min,max)*-length, -- ycoord
                e.content -- content
                )
            )
        end
    end
    tex.print([[\end{tikzpicture}]])
    tex.print([[\immediate\write\timetableATdataOutput{\iffalse{\else\string}\fi}]])
end
function search_array(t, s)
    for k,v in ipairs(t) do
        if(v == s) then return k end
    end
    return nil
end

function minuteToFrac(minute, min, max)
    return (minute-min)/(max-min)
end
function prepareMinMax(min, max)
    local minH = math.floor(min/60)
    local maxH = math.ceil(max/60)
    local min = minH*60
    local max = maxH*60
    return min, minH, max, maxH
end
function checkKeys(t, k)
    for _,x in ipairs(k) do
        if(t[x] == nil) then
            return false
        end
    end
    return true
end
function dur2Int(clk)
    local f1,f2, t1,t2 = clk:match("^(%d%d?):(%d%d)-(%d%d?):(%d%d)$")
    if(f1 ~= nil and f2 ~= nil and t1 ~= nil and t2 ~= nil) then
        f1 = tonumber(f1) f2 = tonumber(f2)
        t1 = tonumber(t1) t2 = tonumber(t2)
        assert(f1 >= 0 and f1 < 24, "Hours have to be >= 0 && < 24")
        assert(f2 >= 0 and f2 < 60, "Mins have to be >= 0 && < 60")
        assert(t1 >= 0 and t1 < 24, "Hours have to be >= 0 && < 24")
        assert(t2 >= 0 and t2 < 60, "Mins have to be >= 0 && < 60")
        return f1*60 + f2, t1*60 + t2
    else
        error("clk string \"" .. clk .. "\" was no valid clock string")
    end
end
function prepareDays(days)
    local ret = {}
    for m in days:gmatch("[^,]+") do
        table.insert(ret, m)
    end
    return ret
end
function day2Int(day)
    return search_array(DAYSE, day) - 1
end

function copy_array(obj)
    if type(obj) ~= 'table' then return obj end
    local res = {}
    for k, v in pairs(obj) do
        local c = copy_array(v)
        res[copy_array(k)] = c
    end
    return res
end

semesterplannerLua = {
    init = init,
    addEvent = addEvent,
    draw = draw,
    day2Int = day2Int,
}
return semesterplannerLua