text: """Core application engine"""
  
  import os
  import json
  import time
  import pkgutil
  import logging
  import importlib
  import bumblebee.error
  import bumblebee.modules
  
  log = logging.getLogger(__name__)
  
  try:
      from ConfigParser import RawConfigParser
  except ImportError:
      from configparser import RawConfigParser
  
  def all_modules():
      """Return a list of available modules"""
      result = []
      path = os.path.dirname(bumblebee.modules.__file__)
      for mod in [name for _, name, _ in pkgutil.iter_modules([path])]:
          result.append({
              "name": mod
          })
      return result
  
  class Module(object):
      """Module instance base class
  
      Objects of this type represent the modules that
      the user configures. Concrete module implementations
      (e.g. CPU utilization, disk usage, etc.) derive from
      this base class.
      """
      def __init__(self, engine, config={}, widgets=[]):
          self.name = config.get("name", self.__module__.split(".")[-1])
          self._config = config
          self.id = self.name
          self.error = None
          self._next = int(time.time())
          self._default_interval = 0
  
          self._configFile = None
          for cfg in [os.path.expanduser("~/.bumblebee-status.conf"), os.path.expanduser("~/.config/bumblebee-status.conf")]:
              if os.path.exists(cfg):
                  self._configFile = RawConfigParser()
                  self._configFile.read(cfg)
                  log.debug("reading configuration file {}".format(cfg))
                  break
  
          if self._configFile is not None and self._configFile.has_section("module-parameters"):
              log.debug(self._configFile.items("module-parameters"))
          self._widgets = []
          if widgets:
              self._widgets = widgets if isinstance(widgets, list) else [widgets]
  
      def widgets(self, widgets=None):
          """Return the widgets to draw for this module"""
          if widgets:
              self._widgets = widgets
          return self._widgets
  
      def hidden(self):
          return False
  
      def widget(self, name):
          for widget in self._widgets:
              if widget.name == name:
                  return widget
  
      def errorWidget(self):
          msg = self.error
          if len(msg) > 10:
              msg = "{}...".format(msg[0:7])
          return bumblebee.output.Widget(full_text="error: {}".format(msg))
  
      def widget_by_id(self, uid):
          for widget in self._widgets:
              if widget.id == uid:
                  return widget
          return None
  
      def update(self, widgets):
          """By default, update() is a NOP"""
          pass
  
      def update_wrapper(self, widgets):
          if self._next > int(time.time()):
              return
          try:
              self.error = None
              self.update(self._widgets)
          except Exception as e:
              log.error("error updating '{}': {}".format(self.name, str(e)))
              self.error = str(e)
          self._next += int(self.parameter("interval", self._default_interval))*60
  
      def interval(self, intvl):
          self._default_interval = intvl
          self._next = int(time.time())
  
      def update_all(self):
          self.update_wrapper(self._widgets)
  
      def has_parameter(self, name):
          v = self.parameter(name)
          return v is not None
  
      def parameter(self, name, default=None):
          """Return the config parameter 'name' for this module"""
          name = "{}.{}".format(self.name, name)
          value = self._config["config"].get(name, default)
          if value == default:
              try:
                  value = self._configFile.get("module-parameters", name)
              except:
                  pass
          return value
  
      def threshold_state(self, value, warn, crit):
          if value > float(self.parameter("critical", crit)):
              return "critical"
          if value > float(self.parameter("warning", warn)):
              return "warning"
          return None
  
  class Engine(object):
      """Engine for driving the application
  
      This class connects input/output, instantiates all
      required modules and drives the "event loop"
      """
      def __init__(self, config, output=None, inp=None, theme=None):
          self._output = output
          self._config = config
          self._running = True
          self._modules = []
          self.input = inp
          self._aliases = self._read_aliases()
          self.load_modules(config.modules())
          self._current_module = None
          self._theme = theme
  
          if bumblebee.util.asbool(config.get("engine.workspacewheel", "true")):
              if bumblebee.util.asbool(config.get("engine.workspacewrap", "true")):
                  self.input.register_callback(None, bumblebee.input.WHEEL_UP,
                      "i3-msg workspace prev_on_output")
                  self.input.register_callback(None, bumblebee.input.WHEEL_DOWN,
                      "i3-msg workspace next_on_output")
              else:
                  self.input.register_callback(None, bumblebee.input.WHEEL_UP,
                      cmd=self._prev_workspace)
                  self.input.register_callback(None, bumblebee.input.WHEEL_DOWN,
                      cmd=self._next_workspace)
          if bumblebee.util.asbool(config.get("engine.collapsible", "true")):
              self.input.register_callback(None, bumblebee.input.MIDDLE_MOUSE,
                  cmd=self._toggle_minimize)
  
          self.input.start()
  
      def _toggle_minimize(self, event):
          for module in self._modules:
              widget = module.widget_by_id(event["instance"])
              if widget:
                  log.debug("module {} found - toggle minimize".format(module.id))
                  widget.toggle_minimize()
  
      def _prev_workspace(self, event):
          self._change_workspace(-1)
  
      def _next_workspace(self, event):
          self._change_workspace(1)
  
      def _change_workspace(self, amount):
          try:
              active_output = None
              active_index = -1
              output_workspaces = {}
              data = json.loads(bumblebee.util.execute("i3-msg -t get_workspaces"))
              for workspace in data:
                  output_workspaces.setdefault(workspace["output"], []).append(workspace)
                  if workspace["focused"]:
                      active_output = workspace["output"]
                      active_index = len(output_workspaces[workspace["output"]]) - 1
              if (active_index + amount) < 0:
                  return
              if (active_index + amount) >= len(output_workspaces[active_output]):
                  return
  
              while amount != 0:
                  if amount > 0:
                      bumblebee.util.execute("i3-msg workspace next_on_output")
                      amount = amount - 1
                  if amount < 0:
                      bumblebee.util.execute("i3-msg workspace prev_on_output")
                      amount = amount + 1
          except Exception as e:
              log.error("failed to change workspace: {}".format(e))
  
      def modules(self):
          return self._modules
  
      def load_modules(self, modules):
          """Load specified modules and return them as list"""
          for module in modules:
              mod = self._load_module(module["module"], module["name"])
              self._modules.append(mod)
              self._register_module_callbacks(mod)
          return self._modules
  
      def _register_module_callbacks(self, module):
          buttons = [
              {"name": "left-click", "id": bumblebee.input.LEFT_MOUSE},
              {"name": "middle-click", "id": bumblebee.input.MIDDLE_MOUSE},
              {"name": "right-click", "id": bumblebee.input.RIGHT_MOUSE},
              {"name": "wheel-up", "id": bumblebee.input.WHEEL_UP},
              {"name": "wheel-down", "id": bumblebee.input.WHEEL_DOWN},
          ]
          for button in buttons:
              if module.parameter(button["name"], None):
                  self.input.register_callback(obj=module,
                      button=button["id"], cmd=module.parameter(button["name"]))
  
      def _read_aliases(self):
          result = {}
          for module in all_modules():
              try:
                  mod = importlib.import_module("bumblebee.modules.{}".format(module["name"]))
                  for alias in getattr(mod, "ALIASES", []):
                      result[alias] = module["name"]
              except Exception as error:
                  log.warning("failed to import {}: {}".format(module["name"], str(error)))
          return result
  
      def _load_module(self, module_name, config_name=None):
          """Load specified module and return it as object"""
          if module_name in self._aliases:
              config_name is config_name if config_name else module_name
              module_name = self._aliases[module_name]
          if config_name is None:
              config_name = module_name
          err = None
          try:
              module = importlib.import_module("bumblebee.modules.{}".format(module_name))
          except ImportError as error:
              err = error
              log.fatal("failed to import {}: {}".format(module_name, str(error)))
          if err:
              raise bumblebee.error.ModuleLoadError("unable to load module {}: {}".format(module_name, str(err)))
          return getattr(module, "Module")(self, {
              "name": config_name,
              "config": self._config
          })
  
      def running(self):
          """Check whether the event loop is running"""
          return self._running
  
      def stop(self):
          """Stop the event loop"""
          self._running = False
  
      def current_module(self):
          return self._current_module.__module__
  
      def run(self):
          """Start the event loop"""
          self._output.start()
          while self.running():
              self.write_output()
              if self.running():
                  self.input.wait(float(self._config.get("interval", 1)))
  
          self._output.stop()
          self.input.stop()
  
      def write_output(self):
          self._output.begin()
          for module in self._modules:
              self._current_module = module
              module.update_wrapper(module.widgets())
              if module.error is None:
                  for widget in module.widgets():
                      widget.link_module(module)
                      self._output.draw(widget=widget, module=module, engine=self)
              else:
                  self._output.draw(widget=module.errorWidget(), module=module, engine=self)
          self._output.flush()
          self._output.end()
  
  # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
  