local socket = require("socket")
local json = require('json')
local math = require('math')
require("common")

local STATE_OK = "Ok"
local STATE_TENTATIVELY_CONNECTED = "Tentatively Connected"
local STATE_INITIAL_CONNECTION_MADE = "Initial Connection Made"
local STATE_UNINITIALIZED = "Uninitialized"

local SCRIPT_VERSION = 1

local APItemValue = 0xA2
local APItemRam = 0xE7
local BatAPItemValue = 0xAB
local BatAPItemRam = 0xEA
local PlayerRoomAddr = 0x8A -- if in number room, we're not in play mode
local WinAddr = 0xDE -- if not 0 (I think if 0xff specifically), we won (and should update once, immediately)

-- If any of these are 2, that dragon ate the player (should send update immediately
-- once, and reset that when none of them are 2 again)

local DragonState = {0xA8, 0xAD, 0xB2}
local last_dragon_state = {0, 0, 0}
local carryAddress = 0x9D -- uses rom object table
local batRoomAddr = 0xCB
local batCarryAddress = 0xD0 -- uses ram object location
local batInvalidCarryItem = 0x78
local batItemCheckAddr = 0xf69f
local batMatrixLen = 11 -- number of pairs
local last_carry_item = 0xB4
local frames_with_no_item = 0
local ItemTableStart = 0xfe9d
local PlayerSlotAddress = 0xfff9

local nullObjectId = 0xB4
local ItemsReceived = nil
local sha256hash = nil
local foreign_items = nil
local foreign_items_by_room = {}
local bat_no_touch_locations_by_room = {}
local bat_no_touch_items = {}
local autocollect_items = {}
local localItemLocations = {}

local prev_bat_room = 0xff
local prev_player_room = 0
local prev_ap_room_index = nil

local pending_foreign_items_collected = {}
local pending_local_items_collected = {}
local rendering_foreign_item = nil
local skip_inventory_items = {}

local inventory = {}
local next_inventory_item = nil

local input_button_address = 0xD7

local deathlink_rec = nil
local deathlink_send = 0

local deathlink_sent = false

local prevstate = ""
local curstate =  STATE_UNINITIALIZED
local atariSocket = nil
local frame = 0

local ItemIndex = 0

local yorgle_speed_address = 0xf725
local grundle_speed_address = 0xf740
local rhindle_speed_address = 0xf70A

local read_switch_a = 0xf780
local read_switch_b = 0xf764

local yorgle_speed = nil
local grundle_speed = nil
local rhindle_speed = nil

local slow_yorgle_id = tostring(118000000 + 0x103)
local slow_grundle_id = tostring(118000000 + 0x104)
local slow_rhindle_id = tostring(118000000 + 0x105)

local yorgle_dead = false
local grundle_dead = false
local rhindle_dead = false

local diff_a_locked = false
local diff_b_locked = false

local bat_logic = 0

local is_dead = 0
local freeincarnates_available = 0
local send_freeincarnate_used = false
local current_bat_ap_item = nil

local was_in_number_room = false

function uRangeRam(address, bytes)
	data = memory.read_bytes_as_array(address, bytes, "Main RAM")
	return data
end
function uRangeRom(address, bytes)
	data = memory.read_bytes_as_array(address+0xf000, bytes, "System Bus")
	return data
end
function uRangeAddress(address, bytes)
	data = memory.read_bytes_as_array(address, bytes, "System Bus")
	return data
end

local function createForeignItemsByRoom()
    foreign_items_by_room = {}
    if foreign_items == nil then
        return
    end
    for _, foreign_item in pairs(foreign_items) do
        if foreign_items_by_room[foreign_item.room_id] == nil then
            foreign_items_by_room[foreign_item.room_id] = {}
        end
        new_foreign_item = {}
        new_foreign_item.room_id = foreign_item.room_id
        new_foreign_item.room_x = foreign_item.room_x
        new_foreign_item.room_y = foreign_item.room_y
        new_foreign_item.short_location_id = foreign_item.short_location_id

        table.insert(foreign_items_by_room[foreign_item.room_id], new_foreign_item)
    end
end

function debugPrintNoTouchLocations()
    for room_id, list in pairs(bat_no_touch_locations_by_room) do
        for index, notouch_location in ipairs(list) do
            print("ROOM "..tostring(room_id).. "["..tostring(index).."]: "..tostring(notouch_location.short_location_id))
        end
    end
end

function processBlock(block)
    if block == nil then
        return
    end
    local block_identified = 0
    local msgBlock = block['messages']
    if msgBlock ~= nil then
        block_identified = 1
        for i, v in pairs(msgBlock) do
            if itemMessages[i] == nil then
                local msg = {TTL=450, message=v, color=0xFFFF0000}
                itemMessages[i] = msg
            end
        end
    end
    local itemsBlock = block["items"]
    if itemsBlock ~= nil then
        block_identified = 1
	    ItemsReceived = itemsBlock
    end
    local apItemsBlock = block["foreign_items"]
    if apItemsBlock ~= nil then
        block_identified = 1
        print("got foreign items block")
        foreign_items = apItemsBlock
        createForeignItemsByRoom()
    end
    local autocollectItems = block["autocollect_items"]
    if autocollectItems ~= nil then
        block_identified = 1
        autocollect_items = {}
        for _, acitem in pairs(autocollectItems) do
            if autocollect_items[acitem.room_id] == nil then
                autocollect_items[acitem.room_id] = {}
            end
            table.insert(autocollect_items[acitem.room_id], acitem)
        end
    end
    local localLocalItemLocations = block["local_item_locations"]
    if localLocalItemLocations ~= nil then
        block_identified = 1
        localItemLocations = localLocalItemLocations
        print("got local item locations")
    end
    local checkedLocationsBlock = block["checked_locations"]
    if checkedLocationsBlock ~= nil then
        block_identified = 1
        for room_id, foreign_item_list in pairs(foreign_items_by_room) do
            for i, foreign_item in pairs(foreign_item_list) do
                short_id = foreign_item.short_location_id
                for j, checked_id in pairs(checkedLocationsBlock) do
                    if checked_id == short_id then
                        table.remove(foreign_item_list, i)
                        break
                    end
                end
            end
        end
        if foreign_items ~= nil then
            for i, foreign_item in pairs(foreign_items) do
                short_id = foreign_item.short_location_id
                for j, checked_id in pairs(checkedLocationsBlock) do
                    if checked_id == short_id then
                        foreign_items[i] = nil
                        break
                    end
                end
            end
        end
    end
    local dragon_speeds_block = block["dragon_speeds"]
    if dragon_speeds_block ~= nil then
        block_identified = 1
        yorgle_speed = dragon_speeds_block[slow_yorgle_id]
        grundle_speed = dragon_speeds_block[slow_grundle_id]
        rhindle_speed = dragon_speeds_block[slow_rhindle_id]
    end
    local diff_a_block = block["difficulty_a_locked"]
    if diff_a_block ~= nil then
        block_identified = 1
        diff_a_locked = diff_a_block
    end
    local diff_b_block = block["difficulty_b_locked"]
    if diff_b_block ~= nil then
        block_identified = 1
        diff_b_locked = diff_b_block
    end
    local freeincarnates_available_block = block["freeincarnates_available"]
    if freeincarnates_available_block ~= nil then
        block_identified = 1
        if freeincarnates_available ~= freeincarnates_available_block then
            freeincarnates_available = freeincarnates_available_block
            local msg = {TTL=450, message="freeincarnates: "..tostring(freeincarnates_available), color=0xFFFF0000}
            itemMessages[-2] = msg
        end
    end
    local bat_logic_block = block["bat_logic"]
    if bat_logic_block ~= nil then
        block_identified = 1
        bat_logic = bat_logic_block
    end
    local bat_no_touch_locations_block = block["bat_no_touch_locations"]
    if bat_no_touch_locations_block ~= nil then
        block_identified = 1
        for _, notouch_location in pairs(bat_no_touch_locations_block) do
            local room_id = tonumber(notouch_location.room_id)
            if bat_no_touch_locations_by_room[room_id] == nil then
                bat_no_touch_locations_by_room[room_id] = {}
            end
            table.insert(bat_no_touch_locations_by_room[room_id], notouch_location)

            if notouch_location.local_item ~= nil and notouch_location.local_item ~= 255 then
                bat_no_touch_items[tonumber(notouch_location.local_item)] = true
                -- print("no touch: "..tostring(notouch_location.local_item))
            end
        end
        -- debugPrintNoTouchLocations()
    end
    deathlink_rec = deathlink_rec or block["deathlink"]
    if( block_identified == 0 ) then
        print("unidentified block")
        print(block)
    end
end

function getAllRam()
    uRangeRAM(0,128);
    return data
end

local function alive_mode()
    return (u8(PlayerRoomAddr) ~= 0x00 and u8(WinAddr) == 0x00)
end

local function generateLocationsChecked()
    list_of_locations = {}
    for s, f in pairs(pending_foreign_items_collected) do
        table.insert(list_of_locations, f.short_location_id + 118000000)
    end
    for s, f in pairs(pending_local_items_collected) do
        table.insert(list_of_locations, f + 118000000)
    end
    return list_of_locations
end

function receive()
    l, e = atariSocket:receive()
    if e == 'closed' then
        if curstate == STATE_OK then
            print("Connection closed")
        end
        curstate = STATE_UNINITIALIZED
        return
    elseif e == 'timeout' then
        return
    elseif e ~= nil then
        print(e)
        curstate = STATE_UNINITIALIZED
        return
    end
    if l ~= nil then
        processBlock(json.decode(l))
    end
    -- Determine Message to send back

    newSha256 = memory.hash_region(0xF000, 0x1000, "System Bus")
    if (sha256hash ~= nil and sha256hash ~= newSha256) then
        print("ROM changed, quitting")
        curstate = STATE_UNINITIALIZED
        return
    end
    sha256hash = newSha256
    local retTable = {}
    retTable["scriptVersion"] = SCRIPT_VERSION
    retTable["romhash"] = sha256hash
    if (alive_mode()) then
        retTable["locations"] = generateLocationsChecked()
    end
    if (u8(WinAddr) ~= 0x00) then
        retTable["victory"] = 1
    end
    if( deathlink_sent or deathlink_send == 0 ) then
        retTable["deathLink"] = 0
    else
        print("Sending deathlink "..tostring(deathlink_send))
        retTable["deathLink"] = deathlink_send
        deathlink_sent = true
    end
    deathlink_send = 0

    if send_freeincarnate_used == true then
        print("Sending freeincarnate used")
        retTable["freeincarnate"] = true
        send_freeincarnate_used = false
    end

    msg = json.encode(retTable).."\n"
    local ret, error = atariSocket:send(msg)
    if ret == nil then
        print(error)
    elseif curstate == STATE_INITIAL_CONNECTION_MADE then
        curstate = STATE_TENTATIVELY_CONNECTED
    elseif curstate == STATE_TENTATIVELY_CONNECTED then
        print("Connected!")
        curstate = STATE_OK
    end
end

function AutocollectFromRoom()
    if autocollect_items ~= nil and autocollect_items[prev_player_room] ~= nil then
        for _, item in pairs(autocollect_items[prev_player_room]) do
            pending_foreign_items_collected[item.short_location_id] = item
        end
    end
end

function SetYorgleSpeed()
    if yorgle_speed ~= nil then
        emu.setregister("A", yorgle_speed);
    end
end

function SetGrundleSpeed()
    if grundle_speed ~= nil then
        emu.setregister("A", grundle_speed);
    end
end

function SetRhindleSpeed()
    if rhindle_speed ~= nil then
        emu.setregister("A", rhindle_speed);
    end
end

function SetDifficultySwitchB()
    if diff_b_locked then
        local a = emu.getregister("A")
        if a < 128 then
            emu.setregister("A", a + 128)
        end
    end
end

function SetDifficultySwitchA()
    if diff_a_locked then
        local a = emu.getregister("A")
        if (a > 128 and a < 128 + 64) or (a < 64) then
            emu.setregister("A", a + 64)
        end
    end
end

function TryFreeincarnate()
    if freeincarnates_available > 0 then
        freeincarnates_available = freeincarnates_available - 1
        for index, state_addr in pairs(DragonState) do
            if last_dragon_state[index] == 1 then
                send_freeincarnate_used = true
                memory.write_u8(state_addr, 1, "System Bus")
                local msg = {TTL=450, message="used freeincarnate", color=0xFF00FF00}
                itemMessages[-1] = msg
            end
        end

    end
end

function GetLinkedObject()
    if emu.getregister("X") == batRoomAddr then
        bat_interest_item = emu.getregister("A")
        -- if the bat can't touch that item, we'll switch it to the number item, which should never be
        -- in the same room as the bat.
        if bat_no_touch_items[bat_interest_item] ~= nil then
            emu.setregister("A", 0xDD )
            emu.setregister("Y", 0xDD )
        end
    end
end

function CheckCollectAPItem(carry_item, target_item_value, target_item_ram, rendering_foreign_item)
    if( carry_item == target_item_value and rendering_foreign_item ~= nil ) then
        memory.write_u8(carryAddress, nullObjectId, "System Bus")
        memory.write_u8(target_item_ram, 0xFF, "System Bus")
        pending_foreign_items_collected[rendering_foreign_item.short_location_id] = rendering_foreign_item
        for index, fi in pairs(foreign_items_by_room[rendering_foreign_item.room_id]) do
            if( fi.short_location_id == rendering_foreign_item.short_location_id ) then
                table.remove(foreign_items_by_room[rendering_foreign_item.room_id], index)
                break
            end
        end
        for index, fi in pairs(foreign_items) do
            if( fi.short_location_id == rendering_foreign_item.short_location_id ) then
                foreign_items[index] = nil
                break
            end
        end
        prev_ap_room_index = 0
        return true
    end
    return false
end

function BatCanTouchForeign(foreign_item, bat_room)
    if bat_no_touch_locations_by_room[bat_room] == nil or bat_no_touch_locations_by_room[bat_room][1] == nil then
        return true
    end

    for index, location in ipairs(bat_no_touch_locations_by_room[bat_room]) do
        if location.short_location_id == foreign_item.short_location_id then
            return false
        end
    end
    return true;
end

function main()
    memory.usememorydomain("System Bus")
    if not checkBizHawkVersion() then
        return
    end
    local playerSlot = memory.read_u8(PlayerSlotAddress)
    local port = 17242 + playerSlot
    print("Using port"..tostring(port))
    server, error = socket.bind('localhost', port)
    if( error ~= nil ) then
        print(error)
    end
    event.onmemoryexecute(SetYorgleSpeed, yorgle_speed_address);
    event.onmemoryexecute(SetGrundleSpeed, grundle_speed_address);
    event.onmemoryexecute(SetRhindleSpeed, rhindle_speed_address);
    event.onmemoryexecute(SetDifficultySwitchA, read_switch_a)
    event.onmemoryexecute(SetDifficultySwitchB, read_switch_b)
    event.onmemoryexecute(GetLinkedObject, batItemCheckAddr)
    -- TODO: Add an onmemoryexecute event to intercept the bat reading item rooms, and don't 'see' an item in the
    -- room if it is in bat_no_touch_locations_by_room.  Although realistically, I may have to handle this in the rom
    -- for it to be totally reliable, because it won't work before the script connects (I might have to reset them?)
    -- TODO: Also remove those items from the bat_no_touch_locations_by_room if they have been collected
    while true do
        frame = frame + 1
        drawMessages()
        if not (curstate == prevstate) then
            print("Current state: "..curstate)
            prevstate = curstate
        end

        local current_player_room = u8(PlayerRoomAddr)
        local bat_room = u8(batRoomAddr)
        local bat_carrying_item = u8(batCarryAddress)
        local bat_carrying_ap_item = (BatAPItemRam == bat_carrying_item)

        if current_player_room == 0x1E then
            if u8(PlayerRoomAddr + 1) > 0x4B then
                memory.write_u8(PlayerRoomAddr + 1, 0x4B)
            end
        end

        if current_player_room == 0x00 then
            if not was_in_number_room then
                print("reset "..tostring(bat_carrying_ap_item).." "..tostring(bat_carrying_item))
                memory.write_u8(batCarryAddress, batInvalidCarryItem)
                memory.write_u8(batCarryAddress+ 1, 0)
                createForeignItemsByRoom()
                memory.write_u8(BatAPItemRam, 0xff)
                memory.write_u8(APItemRam, 0xff)
                prev_ap_room_index = 0
                prev_player_room = 0
                rendering_foreign_item = nil
                was_in_number_room = true
            end
        else
            was_in_number_room = false
        end

        if bat_room ~= prev_bat_room then
            if bat_carrying_ap_item then
                if foreign_items_by_room[prev_bat_room] ~= nil then
                    for r,f in pairs(foreign_items_by_room[prev_bat_room]) do
                        if f.short_location_id == current_bat_ap_item.short_location_id then
                            -- print("removing item from "..tostring(r).." in "..tostring(prev_bat_room))
                            table.remove(foreign_items_by_room[prev_bat_room], r)
                            break
                        end
                    end
                end
                if foreign_items_by_room[bat_room] == nil then
                    foreign_items_by_room[bat_room] = {}
                end
                -- print("adding item to "..tostring(bat_room))
                table.insert(foreign_items_by_room[bat_room], current_bat_ap_item)
            else
                -- set AP item room and position for new room, or to invalid room
                if foreign_items_by_room[bat_room] ~= nil and foreign_items_by_room[bat_room][1] ~= nil
                            and BatCanTouchForeign(foreign_items_by_room[bat_room][1], bat_room) then
                    if current_bat_ap_item ~= foreign_items_by_room[bat_room][1] then
                        current_bat_ap_item = foreign_items_by_room[bat_room][1]
                        -- print("Changing bat item to "..tostring(current_bat_ap_item.short_location_id))
                    end
                    memory.write_u8(BatAPItemRam, bat_room)
                    memory.write_u8(BatAPItemRam + 1, current_bat_ap_item.room_x)
                    memory.write_u8(BatAPItemRam + 2, current_bat_ap_item.room_y)
                else
                    memory.write_u8(BatAPItemRam, 0xff)
                    if current_bat_ap_item ~= nil then
                        -- print("clearing bat item")
                    end
                    current_bat_ap_item = nil
                end
            end
        end
        prev_bat_room = bat_room

        -- update foreign_items_by_room position and room id for bat item if bat carrying an item
        if bat_carrying_ap_item then
            -- this is setting the item using the bat's position, which is somewhat wrong, but I think
            -- there will be more problems with the room not matching sometimes if I use the actual item position
            current_bat_ap_item.room_id = bat_room
            current_bat_ap_item.room_x = u8(batRoomAddr + 1)
            current_bat_ap_item.room_y = u8(batRoomAddr + 2)
        end

        if (alive_mode()) then
            if (current_player_room ~= prev_player_room) then
                memory.write_u8(APItemRam, 0xFF, "System Bus")
                prev_ap_room_index = 0
                prev_player_room = current_player_room
                AutocollectFromRoom()
            end
            local carry_item = memory.read_u8(carryAddress, "System Bus")
            bat_no_touch_items[carry_item] = nil
            if (next_inventory_item ~= nil) then
                if ( carry_item == nullObjectId and last_carry_item == nullObjectId ) then
                    frames_with_no_item = frames_with_no_item + 1
                    if (frames_with_no_item > 10) then
                        frames_with_no_item = 10
                        local input_value = memory.read_u8(input_button_address, "System Bus")
                        if( input_value >= 64 and input_value < 128 ) then -- high bit clear, second highest bit set
                            memory.write_u8(carryAddress, next_inventory_item)
                            local item_ram_location = memory.read_u8(ItemTableStart + next_inventory_item)
                            if( memory.read_u8(batCarryAddress) ~= 0x78 and
                                    memory.read_u8(batCarryAddress) == item_ram_location) then
                                memory.write_u8(batCarryAddress, batInvalidCarryItem)
                                memory.write_u8(batCarryAddress+ 1, 0)
                                memory.write_u8(item_ram_location, current_player_room)
                                memory.write_u8(item_ram_location + 1, memory.read_u8(PlayerRoomAddr + 1))
                                memory.write_u8(item_ram_location + 2, memory.read_u8(PlayerRoomAddr + 2))
                            end
                            ItemIndex = ItemIndex + 1
                            next_inventory_item = nil
                        end
                    end
                else
                    frames_with_no_item = 0
                end
            end
            if( carry_item ~= last_carry_item ) then
                if ( localItemLocations ~= nil and localItemLocations[tostring(carry_item)] ~= nil ) then
                    pending_local_items_collected[localItemLocations[tostring(carry_item)]] =
                        localItemLocations[tostring(carry_item)]
                    localItemLocations[tostring(carry_item)] = nil
                    skip_inventory_items[carry_item] = carry_item
                end
            end
            last_carry_item = carry_item

            CheckCollectAPItem(carry_item, APItemValue, APItemRam, rendering_foreign_item)
            if CheckCollectAPItem(carry_item, BatAPItemValue, BatAPItemRam, current_bat_ap_item) and bat_carrying_ap_item then
                memory.write_u8(batCarryAddress, batInvalidCarryItem)
                memory.write_u8(batCarryAddress+ 1, 0)
            end


            rendering_foreign_item = nil
            if( foreign_items_by_room[current_player_room] ~= nil ) then
                if( foreign_items_by_room[current_player_room][prev_ap_room_index] ~= nil ) and memory.read_u8(APItemRam) ~= 0xff then
                    foreign_items_by_room[current_player_room][prev_ap_room_index].room_x = memory.read_u8(APItemRam + 1)
                    foreign_items_by_room[current_player_room][prev_ap_room_index].room_y = memory.read_u8(APItemRam + 2)
                end
                prev_ap_room_index = prev_ap_room_index + 1
                local invalid_index = -1
                if( foreign_items_by_room[current_player_room][prev_ap_room_index] == nil ) then
                    prev_ap_room_index = 1
                end
                if( foreign_items_by_room[current_player_room][prev_ap_room_index] ~= nil and current_bat_ap_item ~= nil and
                    foreign_items_by_room[current_player_room][prev_ap_room_index].short_location_id == current_bat_ap_item.short_location_id) then
                    invalid_index = prev_ap_room_index
                    prev_ap_room_index = prev_ap_room_index + 1
                    if( foreign_items_by_room[current_player_room][prev_ap_room_index] == nil ) then
                        prev_ap_room_index = 1
                    end
                end

                if( foreign_items_by_room[current_player_room][prev_ap_room_index] ~= nil and prev_ap_room_index ~= invalid_index ) then
                    memory.write_u8(APItemRam, current_player_room)
                    rendering_foreign_item = foreign_items_by_room[current_player_room][prev_ap_room_index]
                    memory.write_u8(APItemRam + 1, rendering_foreign_item.room_x)
                    memory.write_u8(APItemRam + 2, rendering_foreign_item.room_y)
                else
                    memory.write_u8(APItemRam, 0xFF, "System Bus")
                end
            end
            if is_dead == 0 then
                dragons_revived = false
                player_dead = false
                new_dragon_state = {0,0,0}
                for index, dragon_state_addr in pairs(DragonState) do
                    new_dragon_state[index] = memory.read_u8(dragon_state_addr, "System Bus" )
                    if last_dragon_state[index] == 1 and new_dragon_state[index] ~= 1 then
                        dragons_revived = true
                    elseif last_dragon_state[index] ~= 1 and new_dragon_state[index] == 1 then
                        dragon_real_index = index - 1
                        print("Killed dragon: "..tostring(dragon_real_index))
                        local dragon_item = {}
                        dragon_item["short_location_id"] = 0xD0 + dragon_real_index
                        pending_foreign_items_collected[dragon_item.short_location_id] = dragon_item
                    end
                    if new_dragon_state[index] == 2 then
                        player_dead = true
                    end
                end
                if dragons_revived and player_dead == false then
                    TryFreeincarnate()
                end
                last_dragon_state = new_dragon_state
            end
        elseif (u8(PlayerRoomAddr) == 0x00) then -- not alive mode, in number room
            ItemIndex = 0  -- reset our inventory
            next_inventory_item = nil
            skip_inventory_items = {}
        end
        if (curstate == STATE_OK) or (curstate == STATE_INITIAL_CONNECTION_MADE) or (curstate == STATE_TENTATIVELY_CONNECTED) then
            if (frame % 5 == 0) then
                receive()
                if alive_mode() then
                    local was_dead = is_dead
                    is_dead = 0
                    for index, dragonStateAddr in pairs(DragonState) do
                        local dragonstateval = memory.read_u8(dragonStateAddr, "System Bus")
                        if ( dragonstateval == 2) then
                            is_dead = index
                        end
                    end
                    if was_dead ~= 0 and is_dead == 0 then
                        TryFreeincarnate()
                    end
                    if deathlink_rec == true and is_dead == 0 then
                        print("setting dead from deathlink")
                        deathlink_rec = false
                        deathlink_sent = true
                        is_dead = 1
                        memory.write_u8(carryAddress, nullObjectId, "System Bus")
                        memory.write_u8(DragonState[1], 2, "System Bus")
                    end
                    if (is_dead > 0 and deathlink_send == 0 and not deathlink_sent) then
                        deathlink_send = is_dead
                        print("setting deathlink_send to "..tostring(is_dead))
                    elseif (is_dead == 0) then
                        deathlink_send = 0
                        deathlink_sent = false
                    end
                    if ItemsReceived ~= nil and ItemsReceived[ItemIndex + 1] ~= nil then
                        while ItemsReceived[ItemIndex + 1] ~= nil and skip_inventory_items[ItemsReceived[ItemIndex + 1]] ~= nil do
                            print("skip")
                            ItemIndex = ItemIndex + 1
                        end
                        local static_id = ItemsReceived[ItemIndex + 1]
                        if static_id ~= nil then
                            inventory[static_id] = 1
                            if next_inventory_item == nil then
                                next_inventory_item = static_id
                            end
                        end
                    end
                end
            end
        elseif (curstate == STATE_UNINITIALIZED) then
            if  (frame % 60 == 0) then

                print("Waiting for client.")

                emu.frameadvance()
                server:settimeout(2)
                print("Attempting to connect")
                local client, timeout = server:accept()
                if timeout == nil then
                    print("Initial connection made")
                    curstate = STATE_INITIAL_CONNECTION_MADE
                    atariSocket = client
                    atariSocket:settimeout(0)
                end
            end
        end
        emu.frameadvance()
    end
end

main()