--[[ 
talk  
simple chat program

by GopherAtl, 2013
do whatever you want with this program/code, just
give me credit for the original code.
--]]

local width,height=term.getSize()

local function debug(...)
  term.redirect(term.native)
  print(...)
  os.pullEvent("char")
  term.restore()
end

local function printUsage()
  print([[Usage:
talk <nick> [channel]
 nick is the name you want to use in chat
 channel is an optional name of the chat channel to chat on
]])
  error()
end

local tArgs={...}
if #tArgs<1 or #tArgs>2 then
  printUsage()
end

--look for a modem
local modem

do 
  local modemSides={}
  for _,v in pairs(rs.getSides()) do
    if peripheral.getType(v)=="modem" then
      modemSides[#modemSides+1]=v
    end
  end

  if #modemSides==0 then
    print("A modem required for chat!")
    return
  end
  if #modemSides>1 then
    term.scroll(#modemSides+3)
    local selected=1
    
    local function printMenu()
      term.setCursorPos(1,height-#modemSides-2)
      for i=1,#modemSides do
        if selected==i then
          term.setTextColor(colors.black)
          term.setBackgroundColor(colors.white)
        end
        print(i..": "..modemSides[i].." - "..(peripheral.call(modemSides[i],"isWireless") and "wireless" or "wired"))
        if selected==i then
          term.setTextColor(colors.white)
          term.setBackgroundColor(colors.black)
        end
      end
    end
      
    print("select a modem to use:")
    printMenu()
    if term.isColor() then
      write("use arrows and enter, press # to select, or click with mouse to select")
    else
      write("use arrows and enter or press # to select")
    end
    while true do
      local e={os.pullEvent()}
      if e[1]=="key" then
        local key=e[2]
        if key==keys.up then
          selected=selected==1 and #modemSides or selected-1
          printMenu()
        elseif key==keys.down then
          selected=(selected%#modemSides)+1
          printMenu()
        elseif key==keys.enter then
          break
        end
      elseif e[1]=="char" then
        local char=e[2]
        local num=tonumber(char)
        if num and num>0 and num<=#modemSides then
          selected=num
          break
        end
      end
    end
    
    term.scroll(2)
    modem=peripheral.wrap(modemSides[selected])    
  else
    modem=peripheral.wrap(modemSides[1])
  end
end

          
local function parseNick(str)
  local nick=str:match("%s*([%w_]+)%s*")
  return nick and nick:sub(1,12) or nil
end

local nick=parseNick(tArgs[1])
if nick==nil then
  print("Invalid nick! can contain only letters, numbers, and _ and must be no more than 12 characters long.")
  return
end

local users={
  [nick]=true
}

local channel, frequency="chat",1337

local function parseChannel(str)
  local chan,freq=str:match("%s*(%w+):(%d+)%s*")
  if not chan then
    freq=frequency
    chan=str:match("(%w+)"):lower():sub(1,16)
    if not chan then
      chan=channel
    end
  else
    chan=chan:lower():sub(1,16)
    freq=tonumber(freq)%65536
  end
  return chan,freq
end


if tArgs[2] then
  channel,frequency=parseChannel(tArgs[2])
end

modem.open(frequency)

local inputCurPos={1,2}
local inputTextColor=colors.white
local inputBGColor=colors.black
local inputCursorBlink=false
local inputBuffer=(""):rep(width)

local statusLine

local function rebuildStatus()
  statusLine=" ccchat v1.0     channel: "..channel..":"..frequency
  statusLine=statusLine..(" "):rep(width-#statusLine)
  term.native.setCursorPos(1,height-1)
  term.native.setTextColor(colors.black)
  term.native.setBackgroundColor(colors.white)
  term.native.write(statusLine)
  term.native.setTextColor(colors.white)
  term.native.setBackgroundColor(colors.black)  
end

local function redrawInput()
  term.redirect(term.native)
  term.setCursorPos(1,height-1)
  term.setTextColor(colors.black)
  term.setBackgroundColor(colors.white)
  term.write(statusLine)
  term.setCursorPos(1,height)
  term.setBackgroundColor(colors.black)
  term.setTextColor(colors.white)
  term.write(inputBuffer)
  term.setCursorPos(unpack(inputCurPos))
  term.restore()
end

  
--input+status redirect
local inputRedirect = {
  getSize=function() return width,2 end,
  setCursorPos=function(x,y) inputCurPos={x,y} term.native.setCursorPos(x,height) end,
  getCursorPos=function() return unpack(inputCurPos) end,
  setTextColor=function(color) inputTextColor=color term.native.setTextColor(color) end,
  setTextColour=function(color) inputTextColor=color term.native.setTextColor(color) end,
  setBackgroundColor=function(color) inputBGColor=color term.native.setBackgroundColor(color) end,
  setBackgroundColour=function(color) inputBGColor=color term.native.setBackgroundColor(color) end,
  setCursorBlink=function(state) inputCursorBlink=state term.native.setCursorBlink(state) end,
  isColor=function() return term.native.isColor() end,
  isColour=function() return term.native.isColour() end,
  scroll=function() term.clearLine() end,
  write=function(val)
      inputBuffer=inputBuffer:sub(1,inputCurPos[1]-1)..val..inputBuffer:sub(inputCurPos[1]+#val)
      inputCurPos[1]=inputCurPos[1]+#val
      term.native.write(val)
    end,
  clear=function()
      inputBuffer=(""):rep(width) 
    end,
  clearLine=function()
      inputBuffer=(""):rep(width) 
    end,
}
 

local isNewLogLine=true
--main log redirect
local logRedirect = { 
  getSize=function() return width,height-1 end,
  setCursorBlink=function() end,
  scroll=function(amt)       
      term.native.scroll(amt)      
      isNewLogLine=true
    end,
  write=function(text)
      if isNewLogLine then
        term.native.clearLine()
        isNewLogLine=false
      end      
      term.native.write(text)
    end,
}

for k,v in pairs(term.native) do
  if logRedirect[k]==nil then
    logRedirect[k]=v
  end
end


local function printLine(line)
  local x,y=term.getCursorPos()
  term.redirect(logRedirect)
  term.setCursorPos(1,height-1)
  print(line)
  term.restore()
  redrawInput()
  term.setCursorPos(x,y)
  --queue a dummy event to trigger coRead update?
  --os.queueEvent("key")  
end

local function printHelp()
  printLine([[CCChat v1.0
commands:
  /quit [msg]   exits chat with optional message
  /join <name>  leaves the current chat channel 
                and joins <name>. <name> can end
                with :<freq> to change both,
                ex: /join foo:1338
  /freq <freq>  switches chat frequencies
  /help         displays this help
  /nick <nick>  changes your nick to <nick>
  /users        lists users in channel
  
  anything typed without a / will be said in the
  channel and heard by all in range on the same
  channel and frequency.]])
  
end


local function sendMsg(msg)
  local out="CHAT "..channel.." "..nick.." "..msg
  modem.transmit(frequency,frequency,out)
end

local function showUsers()
  local first=true
  local str="** Users in channel "..channel..":"
  for k,v in pairs(users) do
    if first then
      first=false
    else
      str=str..","
    end
    str=str.." "..k
  end
  printLine(str)
end

local autoUsersTimer

local function coRead()
  history={}
  while true do
    term.setCursorPos(1,height)
    local input=read(nil,history)
    history[#history+1]=input
    if #history==256 then
      table.remove(history,1)
    end
    
    if input:sub(1,1)=="/" then
      local cmd,args=input:match("/(%S+) ?(.*)")
      if cmd=="quit" then
        printLine("Exiting chat")
        sendMsg("quit "..args:match("^%s*(.*)%s*$"))
        return
      elseif cmd=="me" then
        printLine("* "..nick.." "..args)
        sendMsg("me "..args)
      elseif cmd=="join" then
        local chan,freq=parseChannel(args)
        if chan then
          if chan==channel then
            printLine("** Yer already in that channel!")
          elseif args~="" then
            printLine("** switching to channel "..chan..(freq==frequency and "" or (":"..freq)))
            sendMsg("quit leftChannel")
            channel=chan
            if frequency~=freq then
              modem.open(freq)
              modem.close(frequency)
              frequency=freq
            end
            sendMsg("join")
            rebuildStatus()
            users={[nick]=true}
            autoUsersTimer=os.startTimer(.25)
          else
            printLine("** invalid channel!")
          end 
        else
          printLine("** invalid syntax")
        end
      elseif cmd=="freq" then
        args=tonumber(args)
        if args==nil then
          printLine("** frequency must be a number.")
        elseif frequency~=args then
          printLine("** switching to frequency "..args)
          sendMsg("quit leftChannel")
          modem.open(args)
          modem.close(frequency)
          frequency=args
          sendMsg("join")
          rebuildStatus()
          users={[nick]=true}
          autoUsersTimer=os.startTimer(.25)
        end
      elseif cmd=="help" then
        printHelp()
      elseif cmd=="nick" then
        local newNick=parseNick(args)
        if newNick then          
          printLine("** nick changed to "..newNick)
          sendMsg("nick "..nick.." "..newNick)
          nick=newNick
        end
      elseif cmd=="users" then
        showUsers()
      else
        printLine("** Unknown command")
      end
    else
      printLine("<"..nick.."> "..input)
      sendMsg("say "..input)
    end
  end
end


local function coMain()
  while true do   
    e={os.pullEvent()}
    if e[1]=="modem_message" then
      local msgChannel, msgNick, msgCmd, msgArgs=e[5]:match("CHAT (%S+) (%S+) (%S+) ?(.*)")
      if msgChannel==channel then
        --it's a chat message on the right channel 
        if msgCmd=="say" then
          printLine("<"..msgNick.."> "..msgArgs)
        elseif msgCmd=="me" then
          printLine("* "..msgNick.." "..msgArgs)
        elseif msgCmd=="join" then
          printLine("** "..msgNick.." has joined the chat")
          sendMsg("ident")
        elseif msgCmd=="ident" then
          users[msgNick]=true
        elseif msgCmd=="quit" then
          printLine("** "..msgNick.." has left the channel ("..(msgArgs=="" and "quit" or msgArgs)..")")          
        elseif msgCmd=="nick" then
          local old, new=msgArgs:match("(%S+) (%S+)")
          users[old]=nil
          users[new]=true
          printLine("** "..old.." now known as "..new)          
        end
      end
    elseif e[1]=="timer" and e[2]==autoUsersTimer then
      showUsers()      
    end    
  end
end

sendMsg("join")

autoUsersTimer=os.startTimer(.25)

term.clear()
rebuildStatus()

term.redirect(inputRedirect)
printLine([[CCChat v1.0
/help for a list of commands
/quit quits
just type to talk.]])

parallel.waitForAny(coRead,coMain)

modem.close(frequency)

term.restore()
term.scroll(1)
print("\ngoodbye!\n")

