require('bit')
require('strings')
require('tables')
require('lists')
local files = require('files')
local settings = require('search_settings')

local windower_settings = windower.get_windower_settings()

local file = files.new('data/message_ids.lua')

local load_messages = function()
    if not file:exists() then
        return nil, nil
    end

    return loadstring(assert(file:read()))()
end

local get_versions = function(version)
    local tokens = version:split('_')
    local date = tonumber(tokens[1])
    local minor = tonumber(tokens[2])
    return date, minor
end

local outdated = function(old)
    if not old then
        return true
    end

    local old_date, old_minor = get_versions(old)
    local date, minor = get_versions(windower_settings.ffxi_version)
    return date > old_date or date == old_date and minor > old_minor
end

local get_dat_path = function(dat)
    local ffxi_path = windower.ffxi_path
    for i = 1, 9 do
        local vtable = assert(io.open(ffxi_path .. (i == 1 and 'VTABLE.DAT' or 'ROM' .. i .. '/VTABLE' .. i .. '.DAT'), 'rb'))
        vtable:seek('set', dat)
        local flag = vtable:read(1):unpack('C')
        vtable:close()
        if flag == i then
            local ftable = assert(io.open(ffxi_path .. (i == 1 and 'FTABLE.DAT' or 'ROM' .. i .. '/FTABLE' .. i .. '.DAT'), 'rb'))
            ftable:seek('set', 2 * dat)
            local file_id, dir_id = ftable:read(2):unpack('b7b9')
            return ffxi_path .. (i == 1 and 'ROM' or 'ROM' .. i) .. '/' .. dir_id .. '/' .. file_id .. '.DAT'
        end
    end

    return nil, 'Could not determine path for DAT \"' .. dat .. '\"'
end

local get_message_id = function(dat_path, search)
    local dat = assert(io.open(dat_path, 'rb'))
    local size, first = dat:read(8):unpack('II')
    size = size - 0x10000000
    first = bit.bxor(first, 0x80808080)
    dat:seek('set', 4)
    local offset_data = dat:read(first)
    local data = dat:read('*a')
    local previous = 0
    local last = first / 4
    for i = 1, last do
        local next = (i == last and size or bit.bxor(offset_data:unpack('I', 4 * i + 1), 0x80808080)) - first
        if data:sub(previous + 1, next) == search then
            return i - 1
        end
        previous = next
    end

    return nil, 'Message not found for zone: \"' .. search .. '\"'
end

local old_version, old_messages = load_messages()

local messages
if outdated(old_version) then
    local message_ids = L{}

    local search = settings.search:split():map(string.char ..  bit.bxor+{0x80} .. string.byte):concat()
    local zones = table.keyset(settings.zones):sort()
    for zone in zones:it() do
        local dat = settings.zones[zone]
        message_ids:append({zone = zone, message_id = assert(get_message_id(assert(get_dat_path(dat)), search))})
    end

    local version = windower_settings.ffxi_version
    local disclaimer = '-- This file is automatically generated.\n\n'
    file:write(disclaimer .. 'return \'' .. version ..'\', {\n' .. message_ids:map(function(id) return '    [' .. id.zone .. '] = ' .. id.message_id .. ',\n' end):concat() .. '}\n')

    messages = {}
    for id in message_ids:it() do
        messages[id.zone] = id.message_id
    end
else
    messages = old_messages
end

local offsets = {
    greater_less = 1,
    failure = 2,
    success = 4,
    second_even_odd = 5,
    first_even_odd = 6,
    range = 7,
    less = 8,
    greater = 9,
    equal = 10,
    second_multiple = 11,
    first_multiple = 12,
    tool_failure = 13,
}

return {messages, offsets}
