local Nylon = require 'nylon.core'()

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

local function WindowDim( x, y, w, h )
   return { x = x, y = y, w = w, h = h }
end

local function WindowMoveResize( w, dim )
   w:resize( dim.h, dim.w )
   w:mvwin( dim.y, dim.x )
end

local win_focused

local function entryfn_winman( cord, env )

   local tiled = {}
   local modal = {}
   local landscape = false
   local fullscreened = false

   local function moveTiledRight_portrait(nexisting)
      local halfscreen = math.floor(env.curses.ncols/2)
      local h = env.curses.nrows / nexisting
      local lasttop = env.curses.nrows
      for i = 1, nexisting do
         local prev = tiled[i]
         local newtop = math.floor(env.curses.nrows-(i*h))
         local newdim = WindowDim( halfscreen, newtop, halfscreen, (lasttop-newtop) )
         lasttop = newtop
         WindowMoveResize( prev.win, newdim )
         wv.log('debug','moveTiledRight resized')
         prev.on.resized( newdim )
      end
   end

   local function moveTiledRight_landscape(nexisting)
      local halfscreen = math.floor(env.curses.nrows/2)
      local width = env.curses.ncols / nexisting
      local lasttop = env.curses.ncols
      for i = 1, nexisting do
         local prev = tiled[i]
         local newtop = math.floor(env.curses.ncols-(i*width))
         local newdim = WindowDim( newtop, halfscreen, (lasttop-newtop), halfscreen )
         lasttop = newtop
         WindowMoveResize( prev.win, newdim )
         wv.log('debug','moveTiledRight resized')
         prev.on.resized( newdim )
      end
   end


   local function moveTiledRight(nexisting)
      if  landscape then
         moveTiledRight_landscape(nexisting)
      else
         moveTiledRight_portrait(nexisting)
      end
   end

   local function resizeAllTiled_portrait()
      local top = tiled[#tiled]

      local halfscreen = math.floor(env.curses.ncols/2)
      local newdim = WindowDim(0,0,halfscreen,env.curses.nrows)
      WindowMoveResize( top.win, newdim )

      local nexisting = #tiled -1
      moveTiledRight(nexisting)
      if nexisting > 0 then
         moveTiledRight( nexisting )
      else
         env.curses.screen:clear()
         env.curses.screen:refresh()
      end
      
      top.on.resized( newdim )
   end


   local function resizeAllTiled_landscape()
      local top = tiled[#tiled]

      local halfscreen = math.floor(env.curses.nrows/2)
      local newdim = WindowDim(0,0,env.curses.ncols,halfscreen)
      WindowMoveResize( top.win, newdim )

      local nexisting = #tiled -1
      moveTiledRight(nexisting)
      if nexisting > 0 then
         moveTiledRight( nexisting )
      else
         env.curses.screen:clear()
         env.curses.screen:refresh()
      end
      
      top.on.resized( newdim )
   end

   local function fullscreen_thisWindow( top )
      local newdim = WindowDim(0,0,env.curses.ncols,env.curses.nrows)
      if top.win then
         WindowMoveResize( top.win, newdim )
      else
         wv.log('debug','weird, window has no window top=%s', top)
      end
      if top.on and top.on.resized then
         top.on.resized( newdim )
      else
         wv.log('debug','weird, th top=%s', top)
      end
   end


   local d_minimized = WindowDim( 0, 0, 1, 1 )
   
   local function resizeAllTiled()
      if not tiled[1] then -- no windows
         env.curses.screen:mvaddstr(0,0,"No buffers; press C-l to open or create new record")
         env.curses.screen:refresh()
         return 
      end

      if #tiled > 1 then
         if fullscreened then
            fullscreen_thisWindow( fullscreened )
--            for _, w in ipairs(tiled) do
--               if w ~= fullscreened then
--                  WindowMoveResize(w.win, d_minimized)
--                  if w.on.resized then w.on.resized(d_minimized) end
--               end
--            end
         elseif landscape then
            resizeAllTiled_landscape()
         else
            resizeAllTiled_portrait()
         end
      else -- only one window
         fullscreen_thisWindow( tiled[1] )
      end

      for _, w in ipairs(modal) do
         if w.on.resized then
            w.on.resized()
         end
      end
   end

   local function change_focus(towin)
      if win_focused ~= towin then
         local prev = win_focused
         win_focused = towin
         local _ = prev and prev.on.resized and prev.on.resized()
         local _ = towin and towin.on.resized and towin.on.resized()
      end
   end


   local function set_best_focus()
      change_focus( (#modal > 0) and modal[#modal] or tiled[#tiled] )
   end

   ------------------------------------------------------------------
   ------------------------------------------------------------------

   function cord.event.toggle_landscape()
      landscape = not landscape
      resizeAllTiled()
   end

   function cord.event.toggle_fullscreen()
      if fullscreened then
         fullscreened = false
      else
         if #tiled > 0 then
            fullscreened = tiled[#tiled]
         end
      end
      resizeAllTiled()
   end

   function cord.event.focused_window_to_primary()
      for i, w in ipairs(tiled) do
         if w == win_focused then
            table.remove(tiled,i)
            table.insert(tiled,win_focused)
            resizeAllTiled()
            return
         end
      end
   end

   function cord.event.this_window_to_primary(win)
      for i, w in ipairs(tiled) do
         if w == win then
            table.remove(tiled,i)
            table.insert(tiled,win)
            resizeAllTiled()
            return
         end
      end
   end
   
   function cord.event.other_window()
      if #modal > 0 then
         wv.log('debug','no window switching when modal dialog is up')
      elseif win_focused and #tiled > 1 then
         local donext = false
         for n = #tiled, 1, -1 do
            if donext then
               change_focus( tiled[n] )
               return
            end
            donext = (win_focused == tiled[n])
         end

         change_focus(tiled[#tiled]) -- we wrapped, last focused was probably window 1
      end
   end

   -- add a managed window to the tiled list
   function cord.event.push_tiled( mw )
--      if #tiled > 0 then
--         moveTiledRight(#tiled)
--         -- mw.cord.event.refresh(true)
--      end
      table.insert( tiled, mw )
      resizeAllTiled()
      change_focus( mw )
   end

   function cord.event.remove_tiled( mw )
      local remain = {}
      for i, v in ipairs(tiled) do
         wv.log('debug','winman remove_tiled mw=%s v=%s',mw, v)
         if v ~= mw then
            table.insert( remain, v )
         end
      end
      tiled = remain
      resizeAllTiled()
      set_best_focus()
   end

   function cord.event.Refresh()
      resizeAllTiled()
   end

   function cord.event.swap_tiled()
      wv.log('debug','cord.event.swap_tiled #tiled=%d',#tiled)
      if #tiled > 1 then
         local prevtop = tiled[#tiled]
         tiled[#tiled] = tiled[#tiled-1]
         tiled[#tiled-1] = prevtop
         if fullscreened == prevtop then
            fullscreened = tiled[#tiled]
         end
         resizeAllTiled()
         set_best_focus()
      end
   end

   function cord.event.push_modal( mw )
      table.insert( modal, mw )
      change_focus( mw )
   end

   function cord.event.remove_modal( mw )
      if modal[#modal] ~= mw then
         wv.log('error','removed modal dialog not active, fixme')
      else
         table.remove(modal,#modal)
         resizeAllTiled() -- just to force refresh
         set_best_focus()
      end
   end

   while true do
      cord:sleep(1)
   end

end






local Malwin = {}


--[[--
-- "opt" fields
---- :win    pdcurses window
---- :on     table of event callbacks (see below)
---- :parent receives 'bubble up' of unhandled keys
---- :modal? indicates this window should grab input events first while it is active; also,
             this probably indicates the window should be kept free-floating and not be
             managed by the tiler.

-- "opt.on" callbacks
---- hidden
---- shown
---- resized
---- destroyed
---- links   : window should return a list of x,y coordinates which are links.  When user requests
               visual link activation, all visible windows are queried for targets and assigned 
               a alphanumeric sequence.  If more than 36 links are active, all links must be
               two characters.  Generally, link enumeration should increase left to right and
               then top to bottom within a window.  < WATCHU TALKING BOUT WILLIS ???
--]]--

function Malwin:new( opt )
   local o = setmetatable( {}, { __index = self } )

   if not opt.win then
      wv.log('abnorm','creating winman managed window with no curses window?')
   end
   
   o.win = opt.win
   o.on  = opt.on or {}

   return o
end



local function entryfn_key( cord, mainkeyhandler, unhandled_keys )
   wv.log '001 entryfn_key'

   cord.event.key = function(k)
         local mapped = mainkeyhandler(k)
         
         if mapped then -- not handled or mapped by main key handle
            if win_focused and win_focused.on.key then
               wv.log('norm','input key/map [%s->%s] not handled by main app, passing to focused win',k,mapped)
               local rc = win_focused.on.key(mapped)
               if rc and unhandled_keys then
                  unhandled_keys(rc)
               end
            else
               wv.log('abnorm','input key/map [%s->%s] not handled by main app, no focused win with keyhandler',k,mapped)
            end
         else
            wv.log('norm','input key/map [%s] handled by main app',k)
         end
   end
   cord.event.never.wait()
end



local function entryfn_input( cord, cord_key )
   wv.log '001 entryfn_input'

   Pdcurses.Static.keypad(true);

   cord:cthreaded_multi( Pdcurses.Static.cthread_getch_loop(), 
      function( k )
         cord_key.event.key( k )
      end )
end



local cord_key

local module = {
   win = Malwin,

   set_input = function(mainkeyhandler,unhandled_keys)
      cord_key = Nylon.cord( 'key', entryfn_key, mainkeyhandler,unhandled_keys )
      local cord_input = Nylon.cord( 'input', entryfn_input, cord_key )
      -- return cord_key
   end,
   
   inject_key = function(k)
      cord_key.event.key(k)
   end
}

local function malwin_cleanup(malwin)
   if not malwin.win then
      wv.log('abnorm','removing win twice')
   else
      malwin.win:clear()
      malwin.win:refresh()
      malwin.win:forcedelete()
      malwin.win = nil
   end
end

function module.create( env )
   local cord_winman = Nylon.cord( 'winman', entryfn_winman, env )

   function module.push(malwin)
      cord_winman.event.push_tiled(malwin)
      return function()
         wv.log('debug','remove pushed win=%s',malwin)
         cord_winman.event.remove_tiled(malwin)
         malwin_cleanup(malwin)
      end
   end

   function module.swap_tiled()
      wv.log('debug','Winman.swap_tiled fn')
      cord_winman.event.swap_tiled()
   end

   function module.remove_focused()
      cord_winman.event.remove_focused()
   end

   -- returns a function which can be used to remove the window
   function module.push_modal(malwin)
      cord_winman.event.push_modal( malwin )
      return function()
         wv.log('debug','remove modal win=%s',malwin)
         cord_winman.event.remove_modal(malwin)
         malwin_cleanup( malwin )
      end
   end

   function module.other_window()
      cord_winman.event.other_window()
   end

   function module.toggle_landscape()
      cord_winman.event.toggle_landscape()
   end

   function module.toggle_fullscreen()
      cord_winman.event.toggle_fullscreen()
   end

   function module.isFocused(w)
      return w == win_focused
   end

   function module.Refresh()
      cord_winman.event.Refresh()
   end

   function module.focused_window_to_primary()
      cord_winman.event.focused_window_to_primary()
   end

   function module.this_window_to_primary(win)
      cord_winman.event.this_window_to_primary(win)
   end
   
end


return module