# Copyright (c) 2024  Kent State University CAE-NetLab

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import json
import logging
import os
import os.path
import signal

import blinker
import ipywidgets as widgets

class ConfigItem():
  def __init__ (self, uiname, key, datatype, default, docstr = ""):
    self.uiname = uiname
    self.key = key
    self.datatype = datatype
    self.default = default
    self.docstr = docstr

class ConfigType():
  def __init__ (self):
    self._widget = None

  def getUIElement (self, value):
    self._widget.value = value
    return self._widget

  def validate (self, value):
    return True

class TypePathString(ConfigType):
  def __init__ (self, check_exists = False):
    super().__init__()
    self.check_exists = check_exists
    self._widget = widgets.Text(disabled = False, layout = widgets.Layout(width="auto"))

  def validate (self):
    if self.check_exists:
      if not os.path.exists(self._widget.value):
        return False
    return True

class TypeUnixSignal(ConfigType):
  def __init__ (self):
    super().__init__()
    self._widget = widgets.IntText(disabled = False, layout = widgets.Layout(width="auto"))

  def validate (self):
    if self._widget.value not in signal.valid_signals():
      return False
    return True

class TypeLoglevel(ConfigType):
  def __init__ (self):
    super().__init__()
    choices = [("TRACE", 5), ("DEBUG", logging.DEBUG), ("INFO", logging.INFO), ("WARNING", logging.WARNING),
               ("ERROR", logging.ERROR), ("CRITICAL", logging.CRITICAL)]
    self._widget = widgets.Dropdown(options = choices)

class TypeBoundedInt(ConfigType):
  def __init__ (self, min, max, step=1):
    super().__init__()
    self._widget = widgets.BoundedIntText(min=min, max=max, step=step, disabled=False)



CFGSPEC = {"General" : [
             ConfigItem("Staging directory", "general.staging_dir", TypePathString(), "/tmp", "Temporary storage directory"),
             ConfigItem("Kill signal", "general.kill-signal", TypeUnixSignal(), signal.SIGKILL, "Kill signal sent to external tools")],
           "Fabric" : [
             ConfigItem("fabric_rc path", "fabric.rc_path", TypePathString(check_exists = True), "", "Path to fabric_rc file (if used)")],
           "Logging" : [
             ConfigItem("UI Loglevel", "log.ui-level", TypeLoglevel(), logging.INFO, "Minimum loglevel to display in GUI"),
             ConfigItem("File Loglevel", "log.file-level", TypeLoglevel(), logging.DEBUG, "Minimum loglevel to write to file"),
             ConfigItem("Logfile path", "log.path", TypePathString(), "~/.mtraf/mtraf.log", "Location to write log file"),
             ConfigItem("Max file size", "log.file-maxbytes", TypeBoundedInt(8192, 10000000, 65535), 500000, "Maximum log file size (in bytes)"),
             ConfigItem("File backups", "log.file-backups", TypeBoundedInt(0, 1000), 10, "Number of log files to keep before discarding")]
           }

class _config():
  def __init__ (self):
    self._signal_cfgupdate = blinker.signal("config.update")
    self._signal_status = blinker.signal("status.update")

    self.ddir = os.path.expanduser("~/.mtraf")
    self.opts = {}

    for topic,cfgitems in CFGSPEC.items():
      for citem in cfgitems:
        if isinstance(citem.datatype, TypePathString):
          self.opts[citem.key] = os.path.expanduser(citem.default)
        else:
          self.opts[citem.key] = citem.default
      
#    self.opts["general.kill-signal"] = signal.SIGKILL
#    self.opts["general.staging_dir"] = "/tmp"
#    self.opts["fabric.rc_path"] = None
#    self.opts["log.path"] = "%s/mtraf.log" % (self.ddir)
#    self.opts["log.ui-level"] = logging.INFO
#    self.opts["log.file-level"] = logging.DEBUG
#    self.opts["log.file-maxbytes"] = 500000
#    self.opts["log.file-backups"] = 10

    if not os.path.exists(self.ddir):
      os.makedirs(self.ddir)

    if os.path.exists("%s/config.json" % (self.ddir)):
      self._load()

  def __setitem__ (self, key, value):
    if key not in self.opts:
      self._signal_status.send(level = logging.DEBUG, message = "Attempt to set unknown config key '%s'" % (key))
      return

    self.opts[key] = value
    self._signal_cfgupdate.send()

  def __getitem__ (self, key):
    return self.opts[key]

  def _load (self):
    with open("%s/config.json" % (self.ddir), "r") as cf:
      self.opts.update(json.loads(cf.read()))
    self._signal_cfgupdate.send()

  def _save (self):
    with open("%s/config.json" % (self.ddir), "w+") as cf:
      cf.write(json.dumps(self.opts))

  def get (self, key):
    return self.opts[key]


CFG = _config()
