local wv = require 'nylon.debug' { name = 'pls-ed' }
local ok,e = pcall(function()
                      require 'NylonOs'
                   end)
if not ok then
   wv.log('abnorm','NylonOs not found')
end
local Nylon = require 'nylon.core'()
local edopts = {
   replaceMark = true
}
local function wait_first_event( cord, handlers )
   
   local rc
   local done = false
      for nEvent, fHandler in pairs(handlers) do
            cord.event[nEvent] = function(...)
         rc = fHandler(...)
         done = true
      end
   end
   while not done do
      cord:yield_to_sleep()
   end
      for k, v in pairs(handlers) do
      cord.event[k] = nil
   end
   return rc
end
local killring = {}
local killring_yankndx = 0
local function killring_add( t )
   table.insert( killring, t )
   killring_yankndx = #killring
   if NylonOs then
      NylonOs.Static.setclipboard(killring[#killring])
   end
end
local function killring_append(t)
   killring[#killring] = killring[#killring] .. t
   if NylonOs then
      NylonOs.Static.setclipboard(killring[#killring])
   end
end
local function killring_getyanktext()
   if NylonOs then
      return NylonOs.Static.getclipboard()
   else
      return killring[killring_yankndx]
   end
end
local function watch_cursor_pos( ptTopLeft, thePoint, wdim )
   local charsInto = thePoint - ptTopLeft
   local ptRow, ptCol
   local drow = 0
   local dcol = 0
   local charsDrawn = 0
   return function( l, col, eol )
      local toDraw = eol and (eol - col + 1) or (#l-col+1)       if (charsDrawn + toDraw) > charsInto then
         return (dcol + charsInto-charsDrawn), drow
      else
         charsDrawn = charsDrawn + toDraw
      end
      if eol then
         dcol = 0
         drow = drow + 1
      else
         dcol = dcol + toDraw
      end
   end
end
local Theme = require 'pls-theme'
local function entryfn_edit( cord, env, buffer, opt )
   
   local wdim = opt.wdim
   local w = env.w
   env.on = env.on or {}    env.report = env.report or function() end
   opt.plug = opt.plug or {}
   local wordWrap = false
   local walkFun = wordWrap and buffer.walkFragmentsEOL_orWidth or buffer.walkFragmentsEOL
      if true then
      local refresh_cord = Nylon.cord('refreshedit', function(refcord)
                                     while true do
                                        refcord:sleep(10)
                                        cord.event.refresh()
                                     end
      end )
   end
   local function cursorpos( ptTopLeft, thePoint, wdim )
      local f = watch_cursor_pos(ptTopLeft,thePoint,wdim)
      for l, col, eol in walkFun(buffer, ptTopLeft, wdim.w) do
         local cx, cy = f( l, col, eol )
         if cx then
            return cx, cy
         end
      end
   end
   
   local ptTopLeft = 1
   local thePoint = 1
   local theMark
   local isearch_fstring = nil
   local wasding                  
   local function get_marked()
      local markbeg = theMark and (thePoint < theMark) and thePoint or theMark
      local markend = theMark and (theMark < thePoint) and thePoint or theMark
      return markbeg, markend
   end
   local function get_line_marked( drawPoint, drawPointAfterLine )
      local markbeg, markend = get_marked()
      local lineMarkBeg, lineMarkEnd 
      if markbeg and drawPointAfterLine > markbeg and drawPoint < markend then
         lineMarkBeg = drawPoint >= markbeg and 1 or (markbeg-drawPoint+1)
         lineMarkEnd = drawPointAfterLine <= markend and (drawPointAfterLine-drawPoint) or (markend - drawPoint)
      end
      return lineMarkBeg, lineMarkEnd
   end
   local function pdcur_draw_window( wdim )
      local charsInto = thePoint - ptTopLeft
      local c = opt and opt.theme and opt.theme.text and Theme[opt.theme.text] or Theme.normal
      w:attron( c )
      local function draw_line( drow, l, markBeg, markEnd )
         w:move(drow+wdim.y,wdim.x)
         local s, e
         if isearch_fstring and #isearch_fstring > 0 then
            if isearch_fstring:find '[A-Z]' then
               s,e = l:find(isearch_fstring) 
            else
               s,e = l:lower():find(isearch_fstring) 
            end
         end
         local ndrawn = #l
         if s then
            local Theme = require 'pls-theme'
            w:addstr( l:sub(1,s-1) )
            w:attroff( c )
            Theme.with.classicmenu( w, function()
                                       w:addstr( l:sub(s,e) )
            end )
            w:attron( c )
            w:addstr( l:sub(e+1,wdim.w) )
         else
            if opt.plug.drawline then
               ndrawn = opt.plug.drawline( w, wdim.x, drow+wdim.y, l, markBeg, markEnd  ) or ndrawn
            else
               if not markBeg then
                  w:addstr((#l > wdim.w) and l:sub(1,wdim.w) or l)
               else
                  local endPoint = markEnd > wdim.w and wdim.w or markEnd
                  w:addstr( l:sub(1,markBeg-1) )
                  local function drawMarked() w:addstr( l:sub(markBeg,endPoint) ) end
                  
                  if opt and opt.theme and opt.theme.text == 'inverse' then
                                          w:attroff(c)
                     Theme.with.classicmenu( w, drawMarked )
                     w:attron(c)
                  else
                     Theme.with.inverse( w, drawMarked )
                  end
                  w:addstr( l:sub(endPoint+1,wdim.w) )
               end
            end
         end
         if ndrawn < wdim.w then
                                    w:mvaddstr( drow+wdim.y,wdim.x+ndrawn,string.rep(' ', (wdim.w-ndrawn)) )
         end
      end
      local drow = 0
      local linefrags = {}
      local drawPoint = ptTopLeft
      for l, col, eol in walkFun( buffer, ptTopLeft, wdim.w ) do
         
         if eol then
            table.insert( linefrags, l:sub(col,eol-1) )
            local wholeLine = table.concat(linefrags)
            local drawPointAfterLine = drawPoint + #wholeLine + 1
            local lineMarkBeg, lineMarkEnd = get_line_marked( drawPoint, drawPointAfterLine )
            draw_line( drow, wholeLine, lineMarkBeg, lineMarkEnd )
            drawPoint = drawPointAfterLine
            drow = drow + 1
            linefrags = {}
         elseif col > 1 then
            table.insert( linefrags, l:sub(col) )
         else
            table.insert( linefrags, l )
         end
         if drow >= wdim.h then
            break
         end
      end
      if #linefrags > 0 then
         draw_line( drow, table.concat(linefrags) )
         drow = drow + 1
      end
      while drow < wdim.h do
         w:mvaddstr(drow+wdim.x,wdim.y,string.rep(' ',wdim.w))
         drow = drow + 1
      end
      local cx,cy=cursorpos(ptTopLeft,thePoint,wdim)
      w:attroff(c)
      if isearch_fstring then
         env.report('isearch','i-search: ' .. isearch_fstring)
      else
                  if not wasding then
            env.report( 'navstatus', string.format('sz=%d pt=%d top=%d x,y=%02d,%02d',
                                                   buffer:end_point(), thePoint, ptTopLeft, cx or -1, cy or -1))
         end
      end
   end
   local keybindings  = (require 'pls-keys').edit
   local function ding(msg,p,...)
      Pdcurses.Static.beep()
      if msg then
         wasding = true
         env.report('oops',p and string.format(msg,p,...) or msg)
      end
   end
   local function report(...)
      env.report(...)
      wasding = true
   end
   local function next_isearch_forward( startPoint )
      local nextpoint = buffer:search_forward_to( startPoint, isearch_fstring )
      wv.log('debug','isearch got nextpoint=%s pt=%d', nextpoint, thePoint)
      if nextpoint then
         thePoint = nextpoint
         local cx, cy = cursorpos(ptTopLeft,thePoint,wdim)
         if cy >= wdim.h then             local adjust = math.floor(cy - (wdim.h / 3))             wv.log('debug','isearch moved beyond screen! wdim.h=%d cy=%d adjust=%d', wdim.h, cy, adjust)
            local st = buffer:forward_lines(ptTopLeft, adjust)
            if st then
               ptTopLeft = st
            end
         end
      else
         ding 'i-search failed'
      end
      cord.event.refresh()
   end
         cord.event.key = function(k)
      wv.log('debug','edit got key=%s => %s', k, keybindings[k] )
      if keybindings[k] then
         cord.event[ keybindings[k] ]()
      elseif type(k) == 'number' then
         if k < 256 and k >= 32 then             if isearch_fstring then
               isearch_fstring = isearch_fstring .. string.char(k)
               next_isearch_forward( thePoint )
               cord.event.refresh()
            else
               cord.event.text(k) 
            end
         elseif k == 27 then             if isearch_fstring then
               isearch_fstring = nil
               cord.event.refresh()
            else
               return k
            end
         else
            return k
         end
      end
   end
   
   local baseline = 1
   local ccol = 1
   local crow = 1
   local function cursor_line()
      return buffer[crow+baseline-1] or ''
   end
   local function char_at_point()
      return buffer:char_at_point(thePoint)
   end
   local function insert_at_point(t)
            buffer:insert( thePoint, t )
      thePoint = thePoint + #t
      if env.on.modified then
         env.on.modified()
      end
   end
   function cord.event.getPoint(ret)
      ret(thePoint)
   end
   function cord.event.replace_marked(cbfun)
      if cbfun then
         local mb, me = get_marked()
         if mb and me > mb then
            local rc = cbfun( buffer:sub(mb,me-1) )
            if rc ~= false then                buffer:remove(mb,me-1)
               buffer:insert(mb,rc or '')
               theMark = nil
               cord.event.refresh()
            end
         else
            cbfun()          end
      end
   end
   function cord.event.kill_buffer()
      if env.on.kill_buffer then
         env.on.kill_buffer()
      else
         ding 'kill-buffer not implemented'
      end
   end
   function cord.event.undo()
      local undopoint = buffer:undo() 
      if undopoint then
         thePoint = undopoint
         if env.on.modified then
            env.on.modified()
         end
         cord.event.refresh()
      else 
         ding 'No further undo information'
      end
   end
   function cord.event.isearch_forward()
                  
      if isearch_fstring then          next_isearch_forward( thePoint + 1 )
      else          isearch_fstring = ''
      end
      cord.event.refresh()
   end
   
   function cord.event.ctrlg()
      isearch_fstring = nil
      theMark = nil
      cord.event.refresh()
   end
   function cord.event.isearch_backward()
      ding 'isearch_backward not implemented'
   end
   function cord.event.jumptotag()
      local w = buffer:word_at_point(thePoint > 1 and (thePoint-1) or thePoint)
      local w2 = buffer:word_at_point( buffer:end_of_line(thePoint)-1 )
      local _ = env.on.tagActivated and env.on.tagActivated( w, w2 )
   end
   function cord.event.query_citations()
      local _ = env.on.queryCitations and env.on.queryCitations()
   end
   
   function cord.event.save_buffer()
      if env.on.save then
         report('major','Saving buffer')
         local one = buffer:asOneString()
         if true then             local f = io.open(string.format('/tmp/pls-r-%f.txt',Nylon.uptime()),"w")
            f:write(one)
            f:close()
         end
         env.on.save( one )
         report('major','Buffer saved')
      else
         report('warn','No buffer save handler')
      end
   end
   function cord.event.insert_at_point( t )
      insert_at_point( t )
      cord.event.refresh()
   end
   local function do_yank( special )
      if env.on.yank and env.on.yank( special ) then
         return
      end
      local yanktext = killring_getyanktext() 
      insert_at_point( yanktext )
      cord.event.refresh()
   end
   function cord.event.yank()
      do_yank()
   end
   
   function cord.event.special_yank()       do_yank(true)
   end
   function cord.event.text(t)
      if t < 256 then
         local theString = string.char(t)
         if theMark and edopts.replaceMark then
            local markbeg = get_marked()
            cord.event.replace_marked( function()
                                         thePoint = markbeg + 1
                                         return theString
            end)
         else
            insert_at_point(theString)
         end
         save_col = true
      else
         ding("unrecognized key=%d",t)
      end
      cord.event.refresh()
   end
   function cord.event.indent_for_tab_command()
      insert_at_point('   ')
      cord.event.refresh()
   end
   function cord.event.delete_char()
      buffer:remove(thePoint)
      cord.event.refresh()
   end
   local lastkill
   local function killring_addorappend(t)
      wv.log('debug','killring_addorappend lastkill=%s',lastkill)
      if lastkill then
         killring_append(t)
      else
         killring_add(t)
      end
   end
   function cord.event.kill_line()
      if buffer:char_at_point(thePoint) == '\n' then
         buffer:remove(thePoint)
         killring_addorappend '\n'
      else
         local eolPoint = buffer:end_of_line(thePoint)
         if buffer:char_at_point(eolPoint) == '\n' then
            eolPoint = eolPoint - 1
         end
         if eolPoint ~= thePoint then
            local deleted = buffer:sub( thePoint, eolPoint )
            buffer:remove( thePoint, eolPoint )
            killring_addorappend( deleted )
         end
      end
      cord.event.refresh_kill()
   end
   function cord.event.escape()
      if isearch_fstring then
         isearch_fstring = nil
         cord.event.refresh()
      end
      if opt.oneLine then
         wv.log 'oneLine edit got escape (cancel)'
         local _ = env.on.cancelled and env.on.cancelled()
      end
   end
   function cord.event.newline()
      if opt.oneLine then
         if env.on.save then
            env.on.save( buffer:asOneString() )
         end
      else
         buffer:insert(thePoint,'\n')
         thePoint = thePoint+1
         local cx, cy = cursorpos( ptTopLeft, thePoint, wdim )
         if cy and (cy + 1 > wdim.h) then
            local nextline = buffer:search_forward_to( ptTopLeft, '\n' )
            if nextline then 
               ptTopLeft = nextline + 1
            end
         end
         cord.event.refresh()
      end
   end
   function cord.event.delete_backward_char()
      if isearch_fstring then
         isearch_fstring = isearch_fstring:sub(1,#isearch_fstring-1)
      else
         if thePoint > 1 then
            thePoint = thePoint - 1
            buffer:remove(thePoint)
         else
            ding '(delete back) beginning of buffer'
         end
      end
      cord.event.refresh()
   end
   local last_col
   local save_col
   local function _go_if(newPoint)
      if newPoint and newPoint ~= thePoint then
         thePoint = newPoint
         save_col = true
         return newPoint
      end
   end
   local function lfn_backward_word()
      _go_if( buffer:backward_word(thePoint) )
      cord.event.refresh()
   end
   
   cord.event.backward_word = lfn_backward_word
   local function lfn_delete_word()
      local endword = buffer:forward_word(thePoint) or (buffer:end_point()+1)
      if endword and endword > thePoint then
         local removed = buffer:sub(thePoint,endword-1)
         killring_addorappend(removed)
         buffer:remove(thePoint,endword-1)
         cord.event.refresh_kill()
      else
         ding 'No more words (and no more promises)'
      end
   end
   cord.event.delete_word = lfn_delete_word
   
   function cord.event.context_menu()
      local w = buffer:word_at_point(thePoint > 1 and (thePoint-1) or thePoint)
      wv.log('debug','context_menu word_at_point=%s',w)
      local function consume_word()
         lfn_backward_word()
         lfn_delete_word()
      end
      local _ = env.on.contextmenu and env.on.contextmenu(w,{ consume_word = consume_word })
      cord.event.refresh()
   end
   
   local function restore_col()
            if last_col then
         local eolpoint = buffer:end_of_line(thePoint)
         wv.log('debug','eolpoint=%d thePoint=%d last_col=%d',eolpoint,thePoint,last_col)
         if (eolpoint - thePoint) > last_col then
            thePoint = thePoint + last_col
         else
            thePoint = eolpoint
         end
      end
   end
   function cord.event.set_mark_command()
      theMark = thePoint
      cord.event.refresh()
   end
   local function _copy_marked_and(fn)
      local mb, me = get_marked()
      if me and me > mb then
         local removed = buffer:sub(mb,me-1)
         killring_addorappend(removed)
         if fn then
            fn(mb,me)
         end
         theMark = nil
         cord.event.refresh()
      else
         ding 'No selection - use [C-x Spc] to mark'
      end
   end
   function cord.event.kill_ring_save()
      _copy_marked_and()
   end
   function cord.event.kill_region()
      _copy_marked_and( function(mb,me)
         buffer:remove(mb,me-1)
         thePoint = mb
      end)
   end
   function cord.event.next_line()
      local point = ptTopLeft
      local nextLineFromTop
      local cx, cy
      local thePointStart = thePoint
      local curfun = watch_cursor_pos(ptTopLeft, thePoint, wdim)
      for l, col, nl in walkFun(buffer,ptTopLeft,wdim.w) do
         if not cx then
            cx, cy = curfun( l, col, nl )
         end
         local toDraw = nl and (nl - col + 1) or (#l-col+1)
         point = point + toDraw
         if nl then
            if not nextLineFromTop then
               nextLineFromTop = point + 1
            end
            if point > thePoint then
               if wordWrap and point >= (thePoint + wdim.w) then
                  thePoint = thePoint + wdim.w
                  break                else
                  thePoint = point
               end
               
               wv.log('debug','point=%d cy=%d nextLineFromTop=%d',point,cy,nextLineFromTop)
               assert(cy)                if cy + 1 >= wdim.h then
                  ptTopLeft = nextLineFromTop
               end
                  restore_col()
               break
            end
         end
      end
      if thePoint == thePointStart then
         thePoint = buffer:end_point() + 2
      end
      cord.event.refresh()
   end
   function cord.event.previous_line()
      if thePoint == 1 then
         ding 'no previous line'
         return
      end
      nextpoint = buffer:beginning_of_line(thePoint)
      if (nextpoint <= 2) and buffer:char_at_point_dec(1) == 10 then
                  thePoint = 1
         restore_col()
      else
         if nextpoint > 1 then
            local bol = buffer:beginning_of_line(nextpoint-1)
            if bol < ptTopLeft then
               ptTopLeft = bol
            end
            thePoint = bol
            restore_col()
         else
            ding 'no previous line'
         end
      end
      cord.event.refresh()
   end
   local function _backward_char() 
      if thePoint > 1 then
         thePoint = thePoint - 1
         save_col = true
      end
   end
   function cord.event.backward_char()
      if thePoint > 1 then
         _backward_char()
         cord.event.refresh()
      else
         ding 'At beginning of buffer'
      end
   end
   local function _forward_char() 
      local ep = buffer:end_point()
      if thePoint <= ep then
         thePoint = thePoint + 1
         save_col = true
      elseif thePoint == (ep + 1) then
                                    if buffer:char_at_point(ep) ~= '\n' then
            thePoint = thePoint + 1
            save_col = true
         end
      else
         ding 'End of buffer'
      end
   end
   function cord.event.forward_char()
      _forward_char()
      cord.event.refresh()
   end
   function cord.event.capitalize_word()
                  local w, bow, eow = buffer:word_at_point(thePoint)
      if bow < thePoint then
         w = w:sub( thePoint-bow+1 )
         bow = thePoint
      end
      if w then
         buffer:replace( bow, bow, w:sub(1,1):upper() )
         buffer:replace( bow+1, eow, w:sub(2):lower() )
      end
      cord.event.forward_word()
   end
   function cord.event.upcase_word()
      local w, bow, eow = buffer:word_at_point(thePoint)
      if w then
         buffer:replace( bow, eow, w:upper() )
      end
      cord.event.forward_word()
   end
   function cord.event.downcase_word()
      local w, bow, eow = buffer:word_at_point(thePoint)
      if w then
         buffer:replace( bow, eow, w:lower() )
      end
      cord.event.forward_word()
   end
   local function _forward_to(pat)
      return _go_if( buffer:search_forward_to(thePoint,pat) )
   end
   local function _forward_past(pat)
      return _go_if( buffer:search_forward_past(thePoint,pat) )
   end
   local function _backward_to(pat)
      return _go_if(buffer:search_backward_to(thePoint,pat))
   end
   local function _backward_past(pat)
      return _go_if(buffer:search_backward_past(thePoint,pat))
   end
   function cord.event.move_beginning_of_line()
      _go_if( buffer:beginning_of_line(thePoint) )
      cord.event.refresh()
   end
   
   function cord.event.move_end_of_line() 
            local eol = buffer:end_of_line(thePoint)
            wv.log('debug','move_end_of_line eol=%d',eol)
            if eol == buffer:end_point() then
               eol = eol + 1
            end
            _go_if( eol )
      cord.event.refresh()            
   end
   
   function cord.event.forward_word()
      local fw = buffer:forward_word(thePoint) or (buffer:end_point() + 1)
      _go_if( fw )
      cord.event.refresh()
   end
   function cord.event.scroll_down_command()
      local start =  (thePoint < buffer:end_point()) and thePoint or (buffer:end_point()-1)
      local prior = buffer:backward_lines(start,wdim.h)
      if prior and prior ~= thePoint then
         thePoint = prior
         restore_col()
         local st = buffer:backward_lines(ptTopLeft,wdim.h)
         if st then ptTopLeft = st end
      else
         ding()
      end
      cord.event.refresh()
   end
   function cord.event.scroll_up_command()
      local s = NylonSysCore.uptime()
      local nextpoint = buffer:forward_lines(thePoint,wdim.h)
            if nextpoint and nextpoint ~= startPoint then
         thePoint = nextpoint
         restore_col()
         local st = buffer:forward_lines(ptTopLeft,wdim.h)
         if st then
            ptTopLeft = st
         end
      end
      local e = NylonSysCore.uptime()
      wv.log('debug','time to page down=%f',(e-s))
      cord.event.refresh()
   end
   
   function cord.event.beginning_of_buffer()
      ptTopLeft = 1
      thePoint = 1
      save_col = true
      cord.event.refresh()
   end
   
   function cord.event.end_of_buffer()
      local b = NylonSysCore.uptime()
      local ep = buffer:end_point()
      local top = buffer:backward_lines(ep,wdim.h-2)
      if top then
         local prevline = buffer:backward_lines(top-1,1)
         wv.log('debug','eob top=%s prevl=%s',top,prevline)
         if prevline then
            ptTopLeft = prevline
         else
            ptTopLeft = 1
         end
      else
         ptTopLeft = 1
      end
      thePoint = ep + (buffer:char_at_point_dec(buffer:end_point())==10 and 1 or 2)
      save_col = true
      local e = NylonSysCore.uptime()
      wv.log('debug','time for end_of_buffer=%f ptl=%d',(e-b),ptTopLeft)
      cord.event.refresh()
   end
   
   local function do_redraw()
      pdcur_draw_window( wdim )
      local curx,cury = cursorpos(ptTopLeft,thePoint,wdim)
      if curx then
         w:move(wdim.y+cury, wdim.x+curx)
      else
         local ep = buffer:end_point()
         if thePoint > ep then
            local curx, cury = cursorpos( ptTopLeft, ep, wdim)
            cury = cury or 0
            curx = curx or 0
            if (thePoint > ep+1) or buffer:char_at_point(ep)=='\n' then
               w:move( wdim.y+cury+1, wdim.x )
            else
               w:move( wdim.y+cury, wdim.x+curx+1 )
            end
         end
         env.report( 'warn', string.format('sz=%d pt=%d tl=%d (end of buffer)', ep, thePoint, ptTopLeft ))
      end
      if save_col then
         last_col = curx
         save_col = false
      end
   end
               
   cord.event.redraw = function(ondone)
      wv.log('debug','window redraw, %dx%d@%d,%d', wdim.w, wdim.h, wdim.x, wdim.y)
      do_redraw()
      w:refresh()
      w:redraw()
      if ondone then
         ondone()
      end
   end
   while true do 
      do_redraw()
      w:refresh()
      local waskill = false
      wasding = false
      wait_first_event( cord, {
         refresh=function( force ) 
            if force then
               w:redraw()
            end
         end,
         refresh_kill=function() 
            waskill = true
         end,
      })
            lastkill = waskill
   end
end
return {
   entryfn_edit = entryfn_edit
}