--[[ PCAN CAN TRC file recorder

Author:  Michael Fitzmayer
License: Public domain

--]]

local core = require "core"
local obd2 = require "obd2.init"

local initial_timestamp_us
local trace_filename
local message_number = 1
local write_trace    = true

local function generate_trace_filename()
    local  timestamp = os.date("%Y%m%d_%H%M%S")

    if os.getenv("OS") == "Windows_NT" then
        local  user_path = os.getenv("USERPROFILE")
        local  filename  = string.format("trace_%s.trc", timestamp)
        local  filepath  = user_path .. "\\" .. filename
        return filepath
    else
        return string.format("/tmp/trace_%s.trc", timestamp)
    end
end

function print_data(data, length)
    for i = length-1, 0, -1 do
        local byte = (data >> (8 * i)) & 0xFF
        io.write(string.format("%02X ", byte))
    end
    for i = length, 7 do
        io.write("   ")
    end
end

local function write_trc_header(start_time)
    local header = string.format(";$FILEVERSION=1.1\n;$STARTTIME=%.6f\n;\n", start_time)
    header = header .. string.format(";   Start time: %s\n", os.date("%d/%m/%Y %H:%M:%S.0"))
    header = header .. ";   Generated by CANopenTerm\n;\n"
    header = header .. ";   Message Number\n"
    header = header .. ";   |         Time Offset (ms)\n"
    header = header .. ";   |         |        Type\n"
    header = header .. ";   |         |        |        ID (hex)\n"
    header = header .. ";   |         |        |        |     Data Length\n"
    header = header .. ";   |         |        |        |     |   Data Bytes (hex) ...\n"
    header = header .. ";   |         |        |        |     |   |\n"
    header = header .. ";---+--   ----+----  --+--  ----+---  +  -+ -- -- -- -- -- -- --\n"

    local file = assert(io.open(trace_filename, "w"))
    file:write(header)
    file:close()
end

local function write_to_trc(timestamp_ms, timestamp_fraction, id, length, data, message_number)
    local success, err = pcall(function()
        local file = assert(io.open(trace_filename, "a"))

        local line = string.format("%5d) %10.1f  Rx     %08X  %d  ", message_number, timestamp_ms + (timestamp_fraction or 0) / 1000, id, length)

        if data and length >= 1 and length <= 8 then
            local bytes = {}
            for i = length-1, 0, -1 do
                local byte = (data >> (8 * i)) & 0xFF
                table.insert(bytes, string.format("%02X", byte))
            end

            line = line .. table.concat(bytes, " ") .. " "
        end

        line = line .. "\n"
        file:write(line)
        file:close()
    end)

    if not success then
        write_trace = false
    end
end

local is_obd2_data = core.select_variable("Is the data provided OBD-II? [y/N]")
if is_obd2_data == nil then
  print("Exiting.")
  return
end

is_obd2_data = is_obd2_data:lower()
if is_obd2_data ~= "yes" and is_obd2_data ~= "y" then
  is_obd2_data = false
else
  is_obd2_data = true
end

can_flush()
print("\nTime         CAN-ID  Length  Data                     Description")

trace_filename = generate_trace_filename()

while not key_is_hit() do
    local id, length, data, timestamp_us = can_read()

    if data then
        data = core.swap_bytes(data, length)

        if not initial_timestamp_us then
            initial_timestamp_us = timestamp_us
            write_trc_header(initial_timestamp_us / 1000000)
        end

        local elapsed_us = timestamp_us - initial_timestamp_us

        local timestamp_ms       = math.floor(elapsed_us / 1000)
        local timestamp_fraction = math.floor(((elapsed_us / 1000) % 1) * 1000)
        local can_data_desc      = nil

        if is_obd2_data and obd2 then
            can_data_desc = obd2.parse(id, length, data)
        elseif is_obd2_data and not obd2 then
            can_data_desc = "OBD-II module not available"
        else
            can_data_desc = dict_lookup_raw(id, length, data)
        end

        if can_data_desc == nil then
            can_data_desc = " "
        end

        io.write(string.format("%6d.%03d   %03X     %1d       ", timestamp_ms, timestamp_fraction, id, length))
        print_data(data, length)
        io.write(string.format(" " .. can_data_desc))
        io.write("\n")

        -- Write to .trc file
        if write_trace then
            write_to_trc(timestamp_ms, timestamp_fraction, id, length, data, message_number)
        end
        message_number = message_number + 1
    end
end

if write_trace then
    io.write("\nSave trace file? [Y/n]: ")
    io.flush()
    local response = io.read()

    if response == nil or response == "" or response:lower() == "y" then
        print(string.format("Saved as %s", trace_filename))
    elseif response:lower() == "n" then
        os.remove(trace_filename)
        print("Trace file not saved.")
    else
        print(string.format("Saved as %s", trace_filename))
    end
else
    print("\nCould not write trace file %s", trace_filename)
end
