#!/usr/bin/env python3
# File: logout-manager-ncurses
# License: MIT
# Author: adamlamers, bgstack15
# Startdate: 2020-03-09 17:06
# Title: ncurses based logout manager
# Usage:
#    logout-manager-ncurses
# Reference:
#    https://docs.python.org/3/howto/curses.html
#    ripped straight from http://adamlamers.com/post/FTPD9KNRA8CT
#    https://stackoverflow.com/questions/3061/calling-a-function-of-a-module-by-using-its-name-a-string/12025554#12025554
#    https://robinislam.me/blog/reading-environment-variables-in-python/
# Improve:
# Dependencies:
#    dep-devuan: python3-dotenv
# Documentation:
#    Improvements for CursesMenu class over origin:
#       accepts number key inputs
#       accepts enabled attribute
#       add "zeroindex" bool

import curses, os, sys
from distro import linux_distribution
from dotenv import load_dotenv

# all this to load the libpath
try:
   defaultdir="/etc/sysconfig"
   thisplatform = linux_distribution()[0].lower()
   if 'debian' in thisplatform or 'devuan' in thisplatform:
      defaultdir="/etc/default"
   # load_dotenv keeps existing environment variables as higher precedent
   load_dotenv(os.path.join(defaultdir,"logout-manager"))
except:
   pass
if 'LOGOUT_MANAGER_LIBPATH' in os.environ:
   for i in os.environ['LOGOUT_MANAGER_LIBPATH'].split(":"):
      sys.path.append(i)
sys.path.append("/usr/share/logout-manager")
import lmlib

class CursesMenu(object):

   INIT = {'type' : 'init'}

   def __init__(self, menu_options):
      self.screen = curses.initscr()
      self.menu_options = menu_options
      self.selected_option = 0
      self._previously_selected_option = None
      self.running = True
      self._zero_offset = 1
      try:
         self._zero_offset = 0 if bool(self.menu_options['zeroindex']) else 1
      except:
         pass

      #init curses and curses input
      curses.noecho()
      curses.cbreak()
      curses.start_color()
      curses.curs_set(0) #Hide cursor
      self.screen.keypad(1)

      #set up color pair for highlighted option
      curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE)
      self.hilite_color = curses.color_pair(1)
      self.normal_color = curses.A_NORMAL

   def prompt_selection(self, parent=None):
      if parent is None:
         lastoption = "Cancel"
      else:
         lastoption = "Return to previous menu ({})".format(parent['title'])

      option_count = len(self.menu_options['options'])

      input_key = None

      ENTER_KEY = ord('\n')
      NUM_KEYS = [ord(str(i)) for i in range(self._zero_offset,option_count+1+self._zero_offset)]
      done = False
      while not done:
         if self.selected_option != self._previously_selected_option:
            self._previously_selected_option = self.selected_option

         self.screen.border(0)
         self._draw_title()
         for option in range(option_count):
            if self.selected_option == option:
               self._draw_option(option, self.hilite_color)
            else:
               self._draw_option(option, self.normal_color)

         if self.selected_option == option_count:
            self.screen.addstr(4 + option_count, 4, "{:2} - {}".format(option_count+self._zero_offset,
               lastoption), self.hilite_color)
         else:
            self.screen.addstr(4 + option_count, 4, "{:2} - {}".format(option_count+self._zero_offset,
               lastoption), self.normal_color)

         max_y, max_x = self.screen.getmaxyx()
         if input_key is not None:
            self.screen.addstr(max_y-3, max_x - 5, "{:3}".format(self.selected_option+self._zero_offset))
         self.screen.refresh()


         input_key = self.screen.getch()
         down_keys = [curses.KEY_DOWN, ord('j')]
         up_keys = [curses.KEY_UP, ord('k')]
         exit_keys = [ord('q')]

         if input_key in down_keys:
            if self.selected_option < option_count:
               self.selected_option += 1
            else:
               self.selected_option = 0

         if input_key in up_keys:
            if self.selected_option > 0:
               self.selected_option -= 1
            else:
               self.selected_option = option_count

         if input_key in exit_keys:
            self.selected_option = option_count #auto select exit and return
            break

         if input_key == ENTER_KEY or input_key in NUM_KEYS:
            if input_key in NUM_KEYS:
               self.selected_option=int(chr(input_key))-self._zero_offset
            done = True
            try:
               done = self.menu_options['options'][self.selected_option]['enabled']
            except:
               pass
      return self.selected_option

   def _draw_option(self, option_number, style):
      thistext = self.menu_options['options'][option_number]['title']
      try:
         if self.menu_options['options'][option_number]['enabled'] == False: thistext += " (disabled)"
      except:
         pass
      self.screen.addstr(4 + option_number,
                     4,
                     "{:2} - {}".format(option_number+self._zero_offset, thistext),
                     style)

   def _draw_title(self):
      self.screen.addstr(2, 2, self.menu_options['title'], curses.A_STANDOUT)
      self.screen.addstr(3, 2, self.menu_options['subtitle'], curses.A_BOLD)

   def display(self):
      selected_option = self.prompt_selection()
      i, _ = self.screen.getmaxyx()
      curses.endwin()
      #os.system('clear')
      if selected_option < len(self.menu_options['options']):
         selected_opt = self.menu_options['options'][selected_option]
         return selected_opt
      else:
         self.running = False
         return {'title' : 'Cancel', 'type' : 'exitmenu'}

# load configs
config = lmlib.Initialize_config(os.environ['LOGOUT_MANAGER_CONF'])
actions = lmlib.Actions

# MAIN LOOP
menu = {
   'title' : 'Logout Manager',
   'type' : 'menu',
   'subtitle' : 'Use arrows or number keys',
   'zeroindex' : False,
   'options' : [
      {'title': 'Lock',      'type': 'action', 'action': 'lock'},
      {'title': 'Logout',    'type': 'action', 'action': 'logout'},
      {'title': 'Hibernate', 'type': 'action', 'action': 'hibernate', 'enabled': config.can_hibernate},
      {'title': 'Shutdown',  'type': 'action', 'action': 'shutdown'},
      {'title': 'Reboot',    'type': 'action', 'action': 'reboot'}
   ]
}
m = CursesMenu(menu)
selected_action = m.display()

if selected_action['type'] == 'exitmenu':
   print("Cancel any logout action.")
elif selected_action['type'] == 'command':
   os.system(selected_action['command'])
elif selected_action['type'] == 'action':
   #a = selected_action['action']:
   func = getattr(globals()['actions'],selected_action['action'])
   func(config)
