local ed = package.loaded.ed or {}
ed.bfunc = ed.bfunc or {}
ed.ifunc = ed.ifunc or setmetatable({},{__index=ed.bfunc})
ed.buffers = ed.buffers or {}

function ed.bfunc:load(fpath)
 local f = io.open(fpath,"rb")
 if not f then return false, "unable to open file" end
 for line in f:lines() do
  self[#self+1] = line
 end
 f:close()
 self.path = fpath
 return true, #self
end

function ed.bfunc:save(fpath)
 local path = fpath or self.path
 if not path then return false, "no path" end
	self.path = path
 local f = io.open(path,"wb")
 if not f then return false, "unable to open file" end
 for k,v in ipairs(self) do
  f:write(v.."\n")
 end
 f:close()
 self.dirty = false
 return true
end

function ed.bfunc:close(discard)
 if not discard and self.dirty then
  local saved = self:save()
  if not saved then return false, "unable to save buffer" end
 end
 for k,v in pairs(ed.buffers) do
  if v == self then
   table.remove(ed.buffers,k)
   return true
  end
 end
 return false, "unable to find buffer handle"
end

function ed.bfunc:range(start,finish)
 start, finish = math.max(1,tonumber(start) or 1), math.min(#self,tonumber(finish) or #self)
 local rt = {}
 for i = start, finish do
  rt[#rt+1] = self[i]
 end
 return rt
end

function ed.bfunc:checkCursor()
 self.y = math.max(self.y,1)
 self.y = math.min(self.y,#self)
 self.x = math.max(self.x,1)
 self.x = math.min(self.x,math.max(self[self.y]:len()+1,1))
end

function ed.ifunc.buffers()
 for k,v in pairs(ed.buffers) do
  print(string.format("\27[31m%4i\27[0m %s",k,v.path or "?"))
 end
end

function ed.ifunc.help()
 print("Available commands:")
 for k,v in pairs(ed.bfunc) do
  print(k)
 end
 for k,v in pairs(ed.ifunc) do
  print(k)
 end
end

function ed.ifunc:list(start,finish)
 start, finish = math.max(1,tonumber(start) or 1), math.min(#self,tonumber(finish) or #self)
 local lt = self:range(start,finish)
 for k,v in pairs(lt) do
  print(string.format("\27[31m%4d\27[0m %s",k+start-1,v))
 end
end

function ed.ifunc:insert(p,o)
 ed.ifunc.pointer(self,p)
 while true do
  io.write(string.format("\27[31m%4d\27[0m ",self.y + (o or 0)))
  local line = io.read()
  if line == "." then break end
  table.insert(self,self.y + (o or 0),line)
  self.dirty = true
  self.y = self.y + 1
 end
end

function ed.ifunc:append(p)
 ed.ifunc.insert(self,p,1)
end

function ed.ifunc:pointer(n)
 self.y = math.max(1,tonumber(n) or 1)
 self.y = math.min(#self,self.y)
 print(self.y)
end

function ed.newBuffer()
 local nb = setmetatable({},{__index=ed.bfunc})
 nb.x,nb.y = 1, 1
 ed.buffers[#ed.buffers+1] = nb
 return nb
end 

function ed.open(buffer)
 if ed.buffers[buffer] then
  buffer = ed.buffers[buffer]
 end
 if type(buffer) == "string" then
  nb = ed.newBuffer()
  nb:load(buffer)
  buffer = nb
 end
 if type(buffer) ~= "table" then buffer = ed.newBuffer() buffer[1] = "" end
 return buffer
end

function ed.interactive(buffer)
 buffer=ed.open(buffer)
 while true do
  io.write("\27[34mced:\27[0m ")
  local line = io.read()
  local words = {}
  for word in line:gmatch("[^%s]+") do
   words[#words+1] = word
  end
  local cmd = table.remove(words,1)
  if ed.ifunc[cmd] then
   print(ed.ifunc[cmd](buffer,table.unpack(words)))
  elseif cmd == "quit" then
   break
  else
   print("Unknown command.")
  end
  if cmd == "close" then
   break
  end
 end
end

function ed.visual(buffer)
 buffer=ed.open(buffer)
 local mx, my = 40, 13
 local cx,cy = math.max(1,buffer.x-mx//2), math.max(1,buffer.y-my//2)
 local ox, oy
 local mode = "c"
 local mult, multstr = 1, ""
 local resized = false
 io.write("\27[999;999H\27[6n")
 local function drawBuffer(force)
  if cx ~= ox or cy ~= oy or force then
   io.write("\27[2J\27[H")
   for i = cy, cy+my do
    print(string.format("\27[31m%4i \27[0m%s",i,(buffer[i] or "\27[36m~"):sub(cx,cx+mx-6)))
   end
  elseif mode == "i" then
   print(string.format("\27[2K\27[999D\27[31m%4i \27[0m%s",buffer.y,(buffer[buffer.y] or "\27[36m~"):sub(cx,cx+mx-6)))
  end
  io.write(string.format("\27[1;%iH\27[0;36;%im\27[2K[%s] ced visual: %i,%i/%i, %iK free %i",my+2,(mode == "c" and 7) or 0, mode, buffer.x, buffer.y, #buffer, computer.freeMemory()//1024,mult))
  io.write(string.format("\27[%i;%iH\27[0m",buffer.x+6-cx,buffer.y-cy+1))
 end
 os.setTimeout(0.0005)
 while true do
  drawBuffer()
  ox, oy = cx, cy
  io.write("\27[100;101m")
  local c=io.read(1)
  if c == "\27" then
   local b = ""
   repeat
    c=io.read(1)
    b=b..c
   until c:match("%a")
   dprint(b)
   local nx, ny = b:match("%[(%d+);(%d+)")
   if nx and ny then
    mx, my = math.max(mx, tonumber(nx)), math.max(my, tonumber(ny)-2)
    drawBuffer(true)
   end
  end
  if mode == "c" then
   if c == "q" then
    io.write("\27[0m\27[2J\27[H")
    break
   elseif c == "h" then
    buffer.x = buffer.x - mult
   elseif c == "l" then
    buffer.x = buffer.x + mult
   elseif c == "j" then
    buffer.y = buffer.y + mult
   elseif c == "k" then
    buffer.y = buffer.y - mult
   elseif c == "d" then
    for i = 1, mult do
     table.remove(buffer,buffer.y)
    end
    drawBuffer(true)
   elseif c == "i" or c == "\t" then
    mode = "i"
   elseif c == "a" then
    buffer.x = buffer.x + 1
    mode = "i"
   elseif c == ":" then
    io.write("\27[1;999H\27[2K")
    ed.interactive(buffer)
    drawBuffer(true)
   elseif c == "w" then
    buffer:save()
   elseif c:match("%d") then
    multstr = multstr .. c
    mult = tonumber(multstr)
   end
   if c:match("%D") then
    multstr = ""
    mult = 1
   end
  else
   if c == "\t" then
    mode = "c"
   elseif c == "\8" and buffer.x == 1 and buffer.y > 1 then
    local lblen = buffer[buffer.y-1]:len()
    buffer[buffer.y-1] = buffer[buffer.y-1] .. table.remove(buffer,buffer.y)
    buffer.x, buffer.y = lblen+1, buffer.y - 1
    drawBuffer(true)
   elseif c == "\8" then
    buffer[buffer.y] = buffer[buffer.y]:sub(1,buffer.x - 2)..buffer[buffer.y]:sub(buffer.x)
    buffer.x = buffer.x - 1
   else
    buffer[buffer.y] = buffer[buffer.y]:sub(1,buffer.x-1)..c..buffer[buffer.y]:sub(buffer.x)
    buffer.x = buffer.x + 1
    local fh,sh = buffer[buffer.y]:match("(.*)\n(.*)")
    if fh and sh then
     buffer[buffer.y] = fh
     table.insert(buffer,buffer.y+1,sh)
     buffer.x, buffer.y = 1, buffer.y + 1
     drawBuffer(true)
    end
   end
  end
  buffer:checkCursor()
  local ax, amx = buffer.x + 5, mx - 5
  if cy + my < buffer.y + 3 then
   cy = math.min(#buffer-my,buffer.y + 6 - my)
  end
  if cy + 3 > buffer.y then
   cy = math.max(1,buffer.y - 6)
  end
  if buffer.x + 5 > cx + mx - 4 then
   cx = math.max(1,(buffer.x + 6) - (mx - 6))
  end
  if buffer.x < cx + 3 then
   cx = math.max(1,buffer.x - 6)
  end
 end
end

return ed