#!/usr/bin/env python3

import fcntl
import gi
import json
import os
import shutil
import signal
import subprocess
import sys
import syslog
import time

gi.require_version('Gtk', '3.0')

from gi.repository import GLib
from gi.repository import Gtk

EXAMMODE_DIR = '/var/lib/puavo-exammode'
EXAMMODE_USER_DIR = os.path.join(EXAMMODE_DIR, 'user')
SESSION_PATH = os.path.join(EXAMMODE_DIR, 'session.json')

def logmsg(priority, message):
  print(message, file=sys.stderr)
  syslog.syslog(priority, message)


class AppRunner:
  def __init__(self, cmd):
    self.command = cmd
    self.run_app()


  def run_app(self):
    logmsg(syslog.LOG_NOTICE, 'starting up %s' % (' '.join(self.command)))

    (pid, stdin, stdout, stderr) = GLib.spawn_async(self.command,
      flags=GLib.SpawnFlags.DO_NOT_REAP_CHILD|GLib.SpawnFlags.STDERR_TO_DEV_NULL,
      standard_input=True, standard_output=True)

    self.pid = pid

    fl = fcntl.fcntl(stdout, fcntl.F_GETFL)
    fcntl.fcntl(stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)
    GLib.io_add_watch(stdout, GLib.IO_HUP|GLib.IO_IN,
                      self.handle_app, os.fdopen(stdout))


  def handle_app(self, fd, condition, channel):
    try:
      if condition & GLib.IO_IN:
        channel.read()

      if condition & GLib.IO_HUP:
        channel.close()
        (pid, status) = os.waitpid(self.pid, 0)
        if status != 0:
          logmsg(syslog.LOG_ERR,
                 'app pid %d returned exit status %s' % (pid, status))
        else:
          logmsg(syslog.LOG_NOTICE, 'app exited with success')

        time.sleep(3)
        self.run_app()
        return False

    except Exception as e:
      syslog.syslog(syslog.LOG_ERR, 'error when handling app: %s' % e)
      return False

    return True



class NextExam:
  NEXT_EXAM_PATH = '/opt/next-exam-puavo-student/Next-Exam-Puavo-Student.AppImage'
  NEXT_EXAM_TARGET_DIR = os.path.join(EXAMMODE_USER_DIR, 'next-exam')
  NEXT_EXAM_BIN_PATH = os.path.join(NEXT_EXAM_TARGET_DIR,
                                    'next-exam-puavo-student')

  def __init__(self, params):
    self.extract_appimage(self.NEXT_EXAM_PATH, self.NEXT_EXAM_TARGET_DIR)
    self.run_nextexam()
    subprocess.run([ self.NEXT_EXAM_BIN_PATH ], check=True)


  def extract_appimage(self, appimage_path, target_dir):
    if os.path.exists(self.NEXT_EXAM_BIN_PATH):
      # If the binary exists, we assume that it is extracted correctly.
      return

    tmp_target_dir = target_dir + '.tmp'
    os.makedirs(tmp_target_dir)
    current_dir = os.getcwd()
    os.chdir(tmp_target_dir)

    try:
      logmsg(syslog.LOG_NOTICE, 'extracting %s' % appimage_path)
      subprocess.run([ appimage_path, '--appimage-extract' ], check=True)
      extracted_dir = os.path.join(tmp_target_dir, 'squashfs-root')
      os.rename(extracted_dir, target_dir)
    finally:
      os.chdir(current_dir)
      shutil.rmtree(tmp_target_dir)


  def run_nextexam(self):
    cmd = [ self.NEXT_EXAM_BIN_PATH ]
    self.app = AppRunner(cmd)


class ExamBrowser:
  def run_browser(self, cmd_args):
    cmd = [ '/usr/bin/puavo-exam-browser' ] + cmd_args
    self.app = AppRunner(cmd)


class Abitti2Exam(ExamBrowser):
  def __init__(self, params):
    if not ('url' in params and isinstance(params['url'], str)):
      raise Exception('url missing from Abitti 2 exam')

    cmd_args = [ '--kiosk', '--url=%s' % params['url'] ]
    self.run_browser(cmd_args)


class SingleWindowWebpage(ExamBrowser):
  def __init__(self, params):
    cmd_args = []
    if 'url' in params and isinstance(params['url'], str):
      cmd_args += [ '--url=%s' % params['url'] ]

    self.run_browser(cmd_args)



# XXX currently this is actually not an applet, but might become one?
class PuavoExamModeSessionApplet:
  def __init__(self):
    # XXX should verify session format?
    with open(SESSION_PATH) as file:
      self.session = json.load(file)


  def handle_session(self):
    if self.session['type'] == 'abitti-2-exam':
      return Abitti2Exam(self.session['params'])
    if self.session['type'] == 'free-exammode-session':
      return SingleWindowWebpage(self.session['params'])
    if self.session['type'] == 'next-exam-session':
      return NextExam(self.session['params'])
    if self.session['type'] == 'single-window-webpage':
      return SingleWindowWebpage(self.session['params'])

    raise Exception('unsupported exam session type %s' % self.session['type'])


  def main(self):
    self.session_handler = self.handle_session()
    Gtk.main()


exitstatus = 0

syslog.openlog('puavo-exammode-session-applet')

applet = PuavoExamModeSessionApplet()

try:
  signal.signal(signal.SIGINT, signal.SIG_DFL)
  applet.main()
except Exception as e:
  logmsg(syslog.LOG_ERR, 'unexpected error: %s' % e)
  exitstatus = 1

syslog.closelog()

sys.exit(exitstatus)
