
local tArgs = { ... }

local function printUsage()
	print( "Usages:" )
	print( "chat host <hostname>" )
	print( "chat join <hostname> <username>" )
end

local sOpenedModem = nil
local function openModem()
    for n,sModem in ipairs( peripheral.getNames() ) do
        if peripheral.getType( sModem ) == "modem" then
            if not rednet.isOpen( sModem ) then
                rednet.open( sModem )
                sOpenedModem = sModem
            end
            return true
        end
    end
    print( "No modems found." )
    return false
end

local function closeModem()
    if sOpenedModem ~= nil then
        rednet.close( sOpenedModem )
        sOpenedModem = nil
    end
end

-- Colours
local highlightColour, textColour
if term.isColour() then
	textColour = colours.white
	highlightColour = colours.yellow
else
	textColour = colours.white
	highlightColour = colours.white
end

local sCommand = tArgs[1]
if sCommand == "host" then
    -- "chat host"
    -- Get hostname
    local sHostname = tArgs[2]
    if sHostname == nil then
        printUsage()
        return
    end

    -- Host server
    if not openModem() then
        return
    end
    rednet.host( "chat", sHostname )
    print( "0 users connected." )

    local tUsers = {}
    local nUsers = 0
    function send( sText )
        for nUserID, tUser in pairs( tUsers ) do
            rednet.send( tUser.nID, {
                sType = "text",
                nUserID = nUserID,
                sText = sText,
            }, "chat" )
        end
    end

    -- Setup ping pong
    local tPingPongTimer = {}
    function ping( nUserID )
        local tUser = tUsers[ nUserID ]
        rednet.send( tUser.nID, {
            sType = "ping to client",
            nUserID = nUserID,
        }, "chat" )

        local timer = os.startTimer( 15 )
        tUser.bPingPonged = false
        tPingPongTimer[ timer ] = nUserID
    end

    function printUsers()
        local x,y = term.getCursorPos()
        term.setCursorPos( 1, y - 1 )
        term.clearLine()
        if nUsers == 1 then
            print( nUsers .. " user connected." )
        else
            print( nUsers .. " users connected." )
        end
    end

    -- Handle messages
    local ok, error = pcall( function()
        parallel.waitForAny( function()
            while true do
                local sEvent, timer = os.pullEvent( "timer" )
                local nUserID = tPingPongTimer[ timer ]
                if nUserID and tUsers[ nUserID ] then
                    local tUser = tUsers[ nUserID ]
                    if tUser then
                        if not tUser.bPingPonged then
                            send( "* "..tUser.sUsername.." has timed out" )
                            tUsers[ nUserID ] = nil
                            nUsers = nUsers - 1
                            printUsers()
                        else
                            ping( nUserID )
                        end
                    end
                end
            end
        end,
        function()
            while true do
                local nSenderID, tMessage = rednet.receive( "chat" )
                if type( tMessage ) == "table" then
                    if tMessage.sType == "login" then
                        -- Login from new client
                        local nUserID = tMessage.nUserID
                        local sUsername = tMessage.sUsername
                        if nUserID and sUsername then
                            tUsers[ nUserID ] = {
                                nID = nSenderID,
                                sUsername = sUsername,
                            }
                            nUsers = nUsers + 1
                            printUsers()
                            send( "* "..sUsername.." has joined the chat" )
                            ping( nUserID )
                        end

                    else
                        -- Something else from existing client
                        local nUserID = tMessage.nUserID
                        local tUser = tUsers[ nUserID ]
                        if tUser and tUser.nID == nSenderID then
                            if tMessage.sType == "logout" then
                                send( "* "..tUser.sUsername.." has left the chat" )
                                tUsers[ nUserID ] = nil
                                nUsers = nUsers - 1
                                printUsers()

                            elseif tMessage.sType == "chat" then
                                local sMessage = tMessage.sText
                                if sMessage then
                                    send( "<"..tUser.sUsername.."> "..tMessage.sText )
                                end

                            elseif tMessage.sType == "ping to server" then
                                rednet.send( tUser.nID, {
                                    sType = "pong to client",
                                    nUserID = nUserID,
                                }, "chat" )

                            elseif tMessage.sType == "pong to server" then
                                tUser.bPingPonged = true

                            end
                        end
                    end
                 end
            end
        end )
    end )
    if not ok then
        printError( error )
    end

    -- Unhost server
    for nUserID, tUser in pairs( tUsers ) do
        rednet.send( tUser.nID, {
            sType = "kick",
            nUserID = nUserID,
        }, "chat" )
    end
    rednet.unhost( "chat" )
    closeModem()

elseif sCommand == "join" then
    -- "chat join"
    -- Get hostname and username
    local sHostname = tArgs[2]
    local sUsername = tArgs[3]
    if sHostname == nil or sUsername == nil then
        printUsage()
        return
    end

    -- Connect
    if not openModem() then
        return
    end
    write( "Looking up " .. sHostname .. "... " )
    local nHostID = rednet.lookup( "chat", sHostname )
    if nHostID == nil then
        print( "Failed." )
        return
    else
        print( "Success." )
    end

    -- Login
    local nUserID = math.random( 1, 2147483647 )
    rednet.send( nHostID, {
        sType = "login",
        nUserID = nUserID,
        sUsername = sUsername,
    }, "chat" )

    -- Setup ping pong
    local bPingPonged = true
    local pingPongTimer = os.startTimer( 0 )

    function ping()
        rednet.send( nHostID, {
            sType = "ping to server",
            nUserID = nUserID,
        }, "chat" )
        bPingPonged = false
        pingPongTimer = os.startTimer( 15 )
    end

    -- Handle messages
    local w,h = term.getSize()
    local parentTerm = term.current()
    local titleWindow = window.create( parentTerm, 1, 1, w, 1, true )
    local historyWindow = window.create( parentTerm, 1, 2, w, h-2, true )
    local promptWindow = window.create( parentTerm, 1, h, w, 1, true )
    historyWindow.setCursorPos( 1, h-2 )

    term.clear()
    term.setTextColour( textColour )
    term.redirect( promptWindow )
    promptWindow.restoreCursor()

    function drawTitle()
        local x,y = titleWindow.getCursorPos()
        local w,h = titleWindow.getSize()
        local sTitle = sUsername.." on "..sHostname
        titleWindow.setTextColour( highlightColour )
        titleWindow.setCursorPos( math.floor( w/2 - string.len(sTitle)/2 ), 1 )
        titleWindow.clearLine()
        titleWindow.write( sTitle )
        promptWindow.restoreCursor()
    end

    function printMessage( sMessage )
        term.redirect( historyWindow )
        print()
        if string.match( sMessage, "^\*" ) then
            -- Information
            term.setTextColour( highlightColour )
            write( sMessage )
            term.setTextColour( textColour )
        else
            -- Chat
            local sUsernameBit = string.match( sMessage, "^\<[^\>]*\>" )
            if sUsernameBit then
                term.setTextColour( highlightColour )
                write( sUsernameBit )
                term.setTextColour( textColour )
                write( string.sub( sMessage, string.len( sUsernameBit ) + 1 ) )
            else
                write( sMessage )
            end
        end
        term.redirect( promptWindow )
        promptWindow.restoreCursor()
    end

    drawTitle()

    local ok, error = pcall( function()
        parallel.waitForAny( function()
            while true do
                local sEvent, timer = os.pullEvent()
                if sEvent == "timer" then
                    if timer == pingPongTimer then
                        if not bPingPonged then
                            printMessage( "Server timeout." )
                            return
                        else
                            ping()
                        end
                    end

                elseif sEvent == "term_resize" then
                    local w,h = parentTerm.getSize()
                    titleWindow.reposition( 1, 1, w, 1 )
                    historyWindow.reposition( 1, 2, w, h-2 )
                    promptWindow.reposition( 1, h, w, 1 )
                end
            end
        end,
        function()
            while true do
                local nSenderID, tMessage = rednet.receive( "chat" )
                if nSenderID == nHostID and type( tMessage ) == "table" and tMessage.nUserID == nUserID then
                    if tMessage.sType == "text" then
                        local sText = tMessage.sText
                        if sText then
                            printMessage( sText )
                        end

                    elseif tMessage.sType == "ping to client" then
                        rednet.send( nSenderID, {
                            sType = "pong to server",
                            nUserID = nUserID,
                        }, "chat" )

                    elseif tMessage.sType == "pong to client" then
                        bPingPonged = true

                    elseif tMessage.sType == "kick" then
                        return

                    end
                end
            end
        end,
        function()
            local tSendHistory = {}
            while true do
                promptWindow.setCursorPos( 1,1 )
                promptWindow.clearLine()
                promptWindow.setTextColor( highlightColour )
                promptWindow.write( ": ")
                promptWindow.setTextColor( textColour )

                local sChat = read( nil, tSendHistory )
                rednet.send( nHostID, {
                    sType = "chat",
                    nUserID = nUserID,
                    sText = sChat,
                }, "chat" )
                table.insert( tSendHistory, sChat )
            end
        end )
    end )

    -- Close the windows
    term.redirect( parentTerm )

    -- Print error notice
    local w,h = term.getSize()
    term.setCursorPos( 1, h )
    term.clearLine()
    term.setCursorBlink( false )
    if not ok then
        printError( error )
    end

    -- Logout
    rednet.send( nHostID, {
        sType = "logout",
        nUserID = nUserID,
    }, "chat" )
    closeModem()

    -- Print disconnection notice
    print( "Disconnected." )

else
    -- "chat somethingelse"
    printUsage()

end
