-- Buffer provides a set of higher level buffer operations to operate on the
-- backing "textstore", of which Buffer is a subclass.
-- 

local NEWLINE=string.byte('\n',1)

local wv = require 'nylon.debug'{ name = 'pls-buffer' }

local Store = require 'pls-textstore'
local Buffer = setmetatable({}, { __index = Store })

function Buffer:new()
   return Store.new(Buffer)
end

function Buffer.search_forward_to( buffer, startPoint, pat )

--   local okay, err = pcall( function() string.find('so',pat) end )
--   if not okay then
--      wv.log('error','bad pattern or something: %s', err)
--      return
--   else
--      wv.log('debug','pattern checked okay, pat=%s', pat)
--   end

   local point = startPoint
   if #pat == 1 or (#pat==2 and pat:sub(1,1) == '%') then
      while not buffer:char_at_point(point):find(pat) do
         if point < buffer:end_point() then
            point = point + 1
         else
            return
         end
      end
      return point
   else
      -- this is probably not real efficient, but it is quick and keeps patterns working (sort of)
      -- this could probably be done more efficiently with 'walkFragments'
      for i = startPoint, buffer:end_point() do
         local sub = buffer:sub( i, i+#pat-1 )
         wv.log('debug',"search-forward pt=%d pat='%s' sub='%s'", i, pat, sub)
         if sub:find(pat) then
            return i
         end
      end
   end
end


function Buffer.search_forward_past( buffer, startPoint, pat )
   local point = startPoint
   if #pat == 1 or (#pat==2 and pat:sub(1,1) == '%') then
      while buffer:char_at_point(point):find(pat) and
         point <= buffer:end_point() do
            point = point + 1
      end
      return point <= buffer:end_point() and point
   else
      -- this is probably not real efficient, but it is quick and keeps patterns working (sort of)
      -- this could probably be done more efficiently with 'walkFragments'
      for i = startPoint, buffer:end_point() do
         local sub = buffer:sub( i, i+(#pat*2) )
         if not sub:find(pat) then
            return point
         end
      end
   end
end


function Buffer.search_backward_to( buffer, startPoint, pat )
   local point = startPoint
   if #pat == 1 then
      local c = string.byte(pat,1)
      while buffer:char_at_point_dec(point) ~= c do
         if point <= 1 then 
            return 
         end
         point = point - 1
      end
      return point
   elseif #pat==2 and pat:sub(1,1) == '%' then
      while not buffer:char_at_point(point):find(pat) do
         if point <= 1 then 
            return 
         end
         point = point - 1
      end
      return point
   else
      error 'not implemented, multi-char search forward'
   end
end

function Buffer.search_backward_past( buffer, startPoint, pat )
   local point = startPoint
   if #pat == 1 or (#pat==2 and pat:sub(1,1) == '%') then
      while buffer:char_at_point(point):find(pat) do
         if point <= 1 then 
            return 
         end
         point = point - 1
      end
      return point
   else
      error 'not implemented, multi-char search forward'
   end
end

function Buffer:beginning_of_line( startPoint )
   local point = (self:char_at_point_dec(startPoint) == NEWLINE and startPoint > 1) and (startPoint-1) or startPoint
   local found = self:search_backward_to(point,'\n')
   if found then
      return (found + 1)
   else
      return 1
   end
end

function Buffer.end_of_line( buffer, startPoint )
    local nexteol = buffer:search_forward_to(startPoint, '\n')
    return nexteol and nexteol or buffer:end_point() + 1
end


--- function Buffer.forward_lines( buffer, startPoint, nlines )
---    local point = startPoint
---    local lasteol
---    for i = 1, nlines do
---       local eol = Buffer.search_forward_to(buffer, point, '\n')
---       if eol and eol < buffer:end_point() then
---          point = eol + 1
---       else
---          ding()
---          return point
---       end
---    end
---    return point

function Buffer.backward_lines( buffer, startPoint, nlines )
   local point
   if startPoint > buffer:end_point() +1 then
      point = buffer:end_point()
      nlines = nlines - 1
   else
      point = startPoint
   end
   local linestart = point
   while buffer:char_at_point_dec(point) == NEWLINE do
      if point ~= linestart then
         if nlines > 0 then
            nlines = nlines - 1
         else
            return point
         end
      end
      point = point - 1
   end
   local lastfound
   while nlines >= 0 do
      local found = Buffer.search_backward_to( buffer, point, '\n')
      if not found or found == point then
         break
      end
      lastfound = found
      if found <= 1 then
         break
      end
      point = found
      while (buffer:char_at_point_dec(point) == NEWLINE) and nlines >= 0 and point > 1 do
         point = point - 1
         nlines = nlines - 1
      end
   end
   if lastfound then
      return lastfound + 1
   end
end

function Buffer.forward_word(buffer,point)
   if not buffer:char_at_point(point):find('%w') then
      local p = buffer:search_forward_to(point,'%w')
      point = p or point 
   end
   return buffer:search_forward_past( point, '%w' )
end

function Buffer:backward_word(point)
   if point <= 2 then
      return 1
   end

   if self:char_at_point(point):find('%w') then
      if self:char_at_point(point-1):find('%w') then
         local justBeforeWord = self:search_backward_past(point-1, '%w')
         return justBeforeWord and justBeforeWord + 1 or 1
      else
         -- we are at the first letter in a word.
      end
   end
   
   local lastLetterOfPreviousWord = self:search_backward_to( point-1, '%w' )
   if not lastLetterOfPreviousWord or lastLetterOfPreviousWord < 2 then
      return 1 -- no more words, beginning of buffer
   end
   local bpw = self:search_backward_past( lastLetterOfPreviousWord-1, '%w'  )
   return bpw and (bpw+1) or 1
end

function Buffer:word_at_point(point)
   local endOfWord = self:forward_word(point) or self:end_point()
--   wv.log('debug','word_at_point endOfWord=%s', endOfWord)
   local beginningOfWord = self:backward_word(endOfWord)
   if endOfWord > beginningOfWord then
      return self:sub(beginningOfWord,endOfWord-1), beginningOfWord, endOfWord-1
   end
end


function Buffer.asOneString( buffer )
   local all = {}
   for l in buffer:walkFragments(1) do
      table.insert(all,l)
   end
   return table.concat(all)
end

function Buffer.forward_lines( buffer, startPoint, nlines )
   local point = startPoint
   for l, col, nl in buffer:walkFragmentsEOL(startPoint) do
      if nl then
         point = point + (nl-col) + 1
         if nlines <= 1 then
            return point
         else
            nlines = nlines - 1
         end
      else
         if not l then
            return (point ~= startPoint) and point
         else
            point = point + (#l - col) + 1
         end
      end
   end
   return point
end


function Buffer:insertFileAtPoint( point, fname )
   local f = io.open(fname,'r')
   -- local buffer = Buffer:new()
   if f then
      for l in f:lines() do
         local fix = l:gsub('\t','    ') .. '\n'
         self:insert( point, fix )
         point = point + #fix
      end
      f:close()
   end
end



local function buffer_openFile( fname )
   local f = io.open(fname,'r')
   if f then
      local buffer = Buffer:new()
      for l in f:lines() do
         buffer:append(l:gsub('\t','    ') .. '\n')
      end
      f:close()
   end
   return buffer
end


local function buffer_withText(t)
   local buffer = Buffer:new()
   if t then
      buffer:append(t)
   end
   return buffer
end

return {
   openFile = buffer_openFile,
   withText = buffer_withText
}