--is multishell already running?
if goroutine and goroutine.findNamedCoroutine("termdaemon") then
  print("multishell is already running!")
  return
end

if not redirect then
  os.loadAPI("redirect")
end
if not goroutine then
  os.loadAPI("goroutine")
end
if not ctrlkeys then
  os.loadAPI("ctrlkeys")
end

local shellPath="rom/programs/shell"
local shellArgs={}

local tArgs={...}
if tArgs[1] then
  --try to resolve
  shellPath=shell.resolveProgram(tArgs[1])
  if not shellPath then
    print("Shell program '"..tArgs[1].."' not found!")
    return
  end
  shellArgs={select(2,...)}
end



local width,height=term.getSize()

local terms={}

local function termFunc()
  shell.run(shellPath,unpack(shellArgs))
end

local w,h=term.getSize()

local function newTerm(index)
  local ok,buf=pcall(redirect.createRedirectBuffer,w,h)
  if not ok then
    return
  end
  
  local routine=goroutine.spawnWithRedirect("term"..index,termFunc,buf)
  return routine  
end

local function termDaemon()
  --start a term at 1
  terms[1]=newTerm(1)
  local activeTerm=1
  
  terms[1].redirect[1].makeActive()

  local inputEvents={
    ctrl_key=true,
    key=true,
    char=true,
    mouse_click=true,
    mouse_drag=true,
    mouse_wheel=true,
  }
  
  local function endTask(taskNum)
    terms[taskNum]=nil
    if taskNum==activeTerm then
      activeTerm=0
      for i=1,10 do
        if terms[i] then
          activeTerm=i
          break
        end
      end
      if activeTerm==0 then
        activeTerm=1
        terms[1]=newTerm(1)
      end
      terms[activeTerm].redirect[1].makeActive()
    end  
  end
  
  while true do
    local event={os.pullEventRaw()}
    if event[1]=="ctrl_key" then      
      local key=event[2]
      if key>=keys.one and key<=keys.zero then
        local termNum=key-1
        if termNum~=activeTerm then
          if terms[termNum]==nil then
            terms[termNum]=newTerm(termNum)
          end
          terms[activeTerm].redirect[1].isActive=false
          activeTerm=termNum
          terms[termNum].redirect[1].makeActive()
        elseif terms[termNum].errored then
          terms[activeTerm]=newTerm(termNum)
          terms[activeTerm].redirect[1].makeActive()
        end
      elseif key==keys.r then
        terms[termNum].redirect[1].blit()
      else
        goroutine.passEvent("term"..activeTerm)
      end
    elseif event[1]=="coroutine_error" then
      local deadNum=tonumber(string.match(event[2],"(%d+)"))
      if deadNum then
        terms[deadNum].errored=true
        term.redirect(terms[deadNum].redirect)
        print(event[3])
        term.restore()
      end
    elseif event[1]=="coroutine_end" then
      local deadNum=tonumber(string.match(event[2],"(%d+)"))
      if deadNum and terms[deadNum] and not terms[deadNum].errored then
        endTask(deadNum)
      end
    elseif event[1]=="terminate" then
      goroutine.passEvent("term"..activeTerm)
    elseif inputEvents[event[1] ] then
      goroutine.passEvent("term"..activeTerm)
    end
  end  
end


function logCoroutineErrors()
  while true do
    local _, name, err=os.pullEvent("coroutine_error")
    local file=fs.open("go.log","a")
    file.write("coroutine '"..name.."' crashed with the following error: "..err)
    file.close()
  end
end


function main()
  goroutine.spawnBackground("errLogger",logCoroutineErrors)
  goroutine.spawnBackground("ctrldaemon",ctrlkeys.coGenCtrlKeyEvents)
  goroutine.spawnBackground("termdaemon",termDaemon)
  os.startTimer(0)
  os.pullEvent()
  --assign all input events to the termdaemon, except for the key+char events which go to
  --the ctrldaemon first
  goroutine.assignEvent("termdaemon","ctrl_key")
  goroutine.assignEvent("ctrldaemon","key")
  goroutine.assignEvent("ctrldaemon","char")
  goroutine.assignEvent("termdaemon","mouse_click")
  goroutine.assignEvent("termdaemon","mouse_drag")
  goroutine.assignEvent("termdaemon","mouse_wheel")
  goroutine.assignEvent("termdaemon","terminate")  

  --tell ctrlkeys who to pass key and char events on to
  ctrlkeys.setEventPassTarget("termdaemon")
end

goroutine.run(main,false)